7 мая 2008

asp.net: полезные вещи, часть третья

.NET
Я уже привел пример использования атрибутов в C# и конкретно в asp.net в предыдущей статье. Там был объявлен простейший атрибут и выполнялась некоторая логика по его проверке. В этот раз я хотел бы показать другой полезный атрибут, который немного сложнее предыдущего, но гораздо полезнее.

В asp.net практически всегда приходится иметь дело с параметрами строки запроса. Доступ к ним, как известно, осуществляется через Request, например Request.QueryString[“id”] вернет значение параметра id или null, если параметр в строке не определен. Это достаточно удобно до тех пор, пока не надоест каждый раз проверять на null перед присвоением переменной. Например, если мы хотим инициализировать int id значением параметра id строки запроса, нам необходимо написать примерно следующий код:
int id = Request.QueryString[“id”] == null ? 0 : Convert.ToInt32(Request.QueryString[“id”]);


Немного утомительно, не находите? К тому же, если стоит задача предварительной проверки инициализации обязательного параметра строки запроса, то работа увеличивается. В одном месте мы проверяем, в другом присваиваем. Я предлагаю, объединить всю работу по получению, проверки и инициализации параметров строки запроса в одном месте. И тут опять помогут атрибуты. Объявим следующий атрибут:
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    sealed public class QueryStringBindingAttribute : Attribute
    {
        public string QueryStringItem
        {
            get;
            private set;
        }
        public bool ThrowOnNull
        {
            get;
            private set;
        }
        public object DefaultValue
        {
            get;
            private set;
        }

        public QueryStringBindingAttribute(string p_queryStringItem)
        {
            SetQueryStringBindingAttribute(p_queryStringItem, false, null);
        }

        public QueryStringBindingAttribute(string p_queryStringItem, bool p_throwOnNull)
        {
            SetQueryStringBindingAttribute(p_queryStringItem, p_throwOnNull, null);
        }

        public QueryStringBindingAttribute(string p_queryStringItem, bool p_throwOnNull, object p_defaultValue)
        {
            SetQueryStringBindingAttribute(p_queryStringItem, p_throwOnNull, p_defaultValue);
        }

        void SetQueryStringBindingAttribute(string p_queryStringItem, bool p_throwOnNull, object p_defaultValue)
        {
            if (String.IsNullOrEmpty(p_queryStringItem))
                throw new Exception(Properties.Settings.Default.InvalidParamsError);

            this.QueryStringItem = p_queryStringItem;
            this.ThrowOnNull = p_throwOnNull;
            this.DefaultValue = p_defaultValue;
        }
    }


Здесь говорится, что атрибут будет применим для полей и свойств класса, не будет наследоваться и будет применим только один раз. В атрибуте объявляются следующие свойства: собственно имя параметра строки запроса, болевое значение «нужно ли вызывать исключение» и значение по умолчанию. Последнее свойство – это еще одна полезная вещь. Часто необходимо при отсутствующем значении параметра задать некоторое значение по умолчанию и произвести построение страницы на его основе. Свойство DefaultValue именно для этого и определено. В атрибуте определены три конструктора для разных ситуаций, в самом простейшем случае атрибут указывается для выборки из строки запроса конкретного параметра, исключение не генерируется и значения по умолчанию нет.

Для обработки такого атрибута необходима логика. Привожу ее ниже:
    public class QueryStringBinding
    {
        Page page;

        public QueryStringBinding(Page p_page)
        {
            if (p_page == null)
                throw new Exception("Недопустимый параметр");

            this.page = p_page;
        }

        private void SetMemberValue(MemberInfo p_member, object value)
        {
            if (p_member == null)
                throw new Exception("Недопустимый параметр");

            Type _memberType = p_member.GetMemberType();

            if (_memberType == typeof(string))
            {
                p_member.SetValue(page, value.ToString());
            }
            else if (_memberType == typeof(bool))
            {
                p_member.SetValue(page, Convert.ToBoolean(value));
            }
            else if (_memberType == typeof(int))
            {
                p_member.SetValue(page, Convert.ToInt32(value));
            }
            else if (_memberType == typeof(int?))
            {
                p_member.SetValue(page, Convert.ToInt32(value));
            }
            else if (_memberType == typeof(bool?))
            {
                p_member.SetValue(page, Convert.ToBoolean(value));
            }
            else
                throw new Exception(Properties.Settings.Default.UsupportedTypeError);
        }

        public void InitQueryStringProperties()
        {
            Type _type = page.GetType().Namespace == null ? page.GetType() : page.GetType().BaseType;

            MemberInfo[] _members = _type.FindMembers(MemberTypes.Field | MemberTypes.Property,
                BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
                null, null);

            foreach (MemberInfo member in _members)
            {
                bool _isDef = Attribute.IsDefined(member, typeof(QueryStringBindingAttribute));
                if (_isDef)
                {
                    Attribute _attr = Attribute.GetCustomAttribute(member, typeof(QueryStringBindingAttribute));
                    string _requestItem = page.Request.QueryString[(_attr as QueryStringBindingAttribute).QueryStringItem];

                    if (!String.IsNullOrEmpty(_requestItem))
                    {
                        SetMemberValue(member, _requestItem);
                    }
                    else
                    {
                        object _defaultValue = (_attr as QueryStringBindingAttribute).DefaultValue;
                        if (_defaultValue != null)
                        {
                            SetMemberValue(member, _defaultValue);
                        }
                        else
                        {
                            if ((_attr as QueryStringBindingAttribute).ThrowOnNull)
                                throw new Exception(String.Format(«В строке запроса не указан параметр {0}»,
                                    (_attr as QueryStringBindingAttribute).QueryStringItem));
                        }
                    }
                }
            }
        }
    }


Здесь широко применяются методы описанные в первой статье. Как видно из реализации поддерживаются только типы string, int, bool, int? и bool?.. Для меня этого достаточно, но уверен, что можно и нужно список расширить. Не знаю, насколько, интересны детали реализации, если кому-то непонятно, я обязательно распишу, а пока без расписывания что к чему, сразу приведу пример использования:
    [QueryStringBinding("bankid")]
    public int? BankId { get; set; }

    [QueryStringBinding("cityid", true)]
    public int CityId { get; set; }

    [QueryStringBinding("branchId", false, 0)]
    public int BranchId { get; set; }

    protected override void OnInit(EventArgs e)
    {
        QueryStringBinding qsbObject = new QueryStringBinding(this);
        qsbObject.InitQueryStringProperties();            
        base.OnInit(e);
    }


В данном примере при инициализации страницы свойствам класса присвоятся значения параметров строки запроса. Если cityid в строке запроса нет возникнет исключение. Если branchId нет, то свойству присвоится значение по умолчанию = 0;
Комментарий: в очередной раз пишу, что данная реализация не претендует на совершенный код. С вашей помощью, я хотел бы сделать его еще лучше.
Комментарий2: Конструкция «Type _type = page.GetType().Namespace == null? page.GetType(): page.GetType().BaseType;» введена для решении проблемы описанной во второй моей статьи. Если кто знает как решить ее более элегантно, прошу прокомментировать.
Теги:asp.netC sharpатрибуты
Хабы: .NET
+3
4,3k 15
Комментарии 22
Реклама
Лучшие публикации за сутки

Рекомендуем