Andrey on .NET | Пара примеров, не вошедшие в цикл "ASP.NET MVC 3 в деталях"

Пара примеров, не вошедшие в цикл "ASP.NET MVC 3 в деталях"

В начале небольшое отступление. Данный примеры создавались для цикла статей про ASP.NET MVC. Они не вошли в него по разным причинам. Однако возможно кто-то еще работает с сайтами на основе ASP.NET MVC 2 или кому-то может пригодиться пример переопределения атрибута. Поэтому я решил опубликовать их отдельно.

Пример переопределения атрибута

Данный пример был разработан во время, когда актуальной была версия 2.0. В ASP.NET MVC 3 появилась поддержка атрибутов из пространства имен DataAnnotations. А вместе с ней и возможность использования атрибута [Display], который поддерживает получение строки из ресурсов.

Итак, задача заключается в следующем. Необходимо создать аналог атрибута [DisplayName], но с возможностью указания данных из ресурсов веб-приложения. Задача может показаться сложной, но самом деле все достаточно просто.

Реализация атрибута это класс, унаследованный от Attribute. Переписывать её с нуля, изобретая велосипед, нет смысла. Поэтому, в  данном случае, в качестве базового класса будем использовать уже упомянутый выше [DisplayName].

В .NET существует соглашение, обязывающее классы, которые реализуют атрибуты, иметь окончание Attribute. Однако, его можно не указывать при присвоении атрибута объекту. Таким образом, реализация атрибута [DisplayName] на самом деле размещается в классе DisplayNameAttribute.

В проекте создадим для атрибутов специальную папку Attributes. В ней разместим класс PropertyNameAttribute:

namespace MVCDemo.Attributes
{
    using System;
    using System.ComponentModel;
    using System.Reflection;

    public class PropertyNameAttribute : DisplayNameAttribute
    {
        private PropertyInfo _propertyInfo;
        private Type _resourceType;

        public PropertyNameAttribute(string resourceName)
            : base(resourceName) { }

        public Type NameResourceType
        {
            get { return this._resourceType; }
            set
            {
                this._resourceType = value;
                this._propertyInfo = this._resourceType.GetProperty(
                    base.DisplayName, BindingFlags.Static | BindingFlags.Public);
            }
        }

        public override string DisplayName
        {
            get
            {
                if (this._propertyInfo == null) {
                    return base.DisplayName;
                }

                return (string)_propertyInfo.GetValue(
                    this._propertyInfo.DeclaringType, null);
            }
        }
    }
}

Рассмотрим, как работает данный код:

Конструктор получает имя ресурса и передает его в базовый класс. Таким образом оно сохраняется в его свойстве base.DisplayName и может быть использовано позже.

Ресурсы в приложении представлены классами, выполняющих как прокси для настоящих файлов с данными.  Указать тип такого объекта можно используя NameResourceType. При этом, используя метод отражения (reflection) GetProperty(), получим информацию о свойстве в котором содержится нужный текст. Обратите внимание что в данный момент не запрашивается само значение. Причина этого в возможности изменения самих ресурсов во время работы приложения.

И, наконец, переопределим свойство DisplayName. В нем с помощью метода GetValue() считаем текущее значение из ресурса и передадим его для отображения. Если ресурс не был найден, то просто вернем его имя, которое было сохранено в свойстве base.DisplayName.

Согласитесь, все достаточно просто.

Использование универсального адаптера jQuery для передачи группы параметров

Следующий, относящийся уже ASP.NET MVC 3, пример иллюстрирует как можно использовать универсальный адаптер правил для jQuery для передачи параметров. Из статей данный пример был убран в пользу использования специализированного адаптера для диапазонов значений.

Рассмотрим следующий атрибут проверки данных, который ограничивает длину строки:

namespace MVCDemo.Attributes.Validation
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class StringLengthRangeAttribute : ValidationAttribute, IClientValidatable
    {
        private readonly int _minLength;
        private readonly int _maxLength;

        public StringLengthRangeAttribute(int minLength, int maxLength)
            : base("{0} length must be between {1} and {2}")
        {
            this._minLength = minLength;
            this._maxLength = maxLength;
        }

        #region ValidationAttribute overrides

        public override string FormatErrorMessage(string name)
        {
            return string.Format(
                this.ErrorMessageString, name, this._minLength, this._maxLength);
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (this._minLength >= this._maxLength) {
                throw new ArgumentException();
            }

            string stringValue = value as string;
            if (stringValue == null) {
                return ValidationResult.Success;
            }

            if ((stringValue.Length < this._minLength) ||
                (stringValue.Length > this._maxLength)) {
                return new ValidationResult(
                    this.FormatErrorMessage(validationContext.DisplayName));
            }

            return ValidationResult.Success;
        }

        #endregion
 
        #region IClientValidatable Members

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
            ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule() {
                ValidationType = "strlenrange",
                ErrorMessage = this.FormatErrorMessage(metadata.DisplayName)
            };

            rule.ValidationParameters.Add("min", this._minLength);
            rule.ValidationParameters.Add("max", this._maxLength);

            yield return rule;
        }

        #endregion
    }
}

Данный атрибут получает в конструкторе два значения: минимальную и максимальную длину строки. В методе IsValid() происходит сравнение полученной длины строки с указанным диапазоном.

Здесь все идентично исходному примеру, различия начинаются в реализации IClientValidatable. Обратите внимание, в отличии от версии в указанной статье используем универсальный адаптер. Это может потребоваться, если для свойства необходимо передать несколько различных диапазонов.

Конструктор универсального правила проверки данных ModelClientValidationRule получает уникальное имя и строку с сообщением об ошибке. Чтобы передать значения переменных, ограничивающих диапазон, воспользуемся свойством ValidationParameters. Это коллекция элементов типа "ключ-значение", в которую можно добавить любые параметры используя метод Add().

Перейдем к клиентской части и создадим файл StringLenghtAttribute.js в папке Scripts\Validation. В методе проверки длины строки повторим алгоритм, использованный в классе атрибута.

Следующий шаг – реализация адаптера. Как вариант, можно было бы рассмотреть использование стандартного адаптера диапазона adapters.addMinMax(). Но он подразумевает использование трех правил проверки, из которых в данном случае всего будет нужно только одно.

Учитывая выше сказанное и цель примера, воспользуемся универсальным адаптером. Для его использования необходимо создать функцию, которая будет преобразовывать значения от атрибута в формат для jQuery Validation. Результат её работы сохраняется в options.rules["имя-правила"] и options.messages["имя-правила"]. Создадим в первой переменной массив и запишем в него значения минимума и максимума. Во второй сохраним текст сообщения об ошибке. Исходный код будет выглядеть так:

/// <reference path="../jquery-1.4.4-vsdoc.js" />
/// <reference path="../jquery.validate-vsdoc.js" />
/// <reference path="../jquery.validate.unobtrusive.js" />

jQuery.validator.addMethod("strlenrange", function (value, element, param) {
    if (value == null) { return true; }

    var stringLen = value.toString().length;
    if ((stringLen < parseInt(param["min"])) ||
         (stringLen > parseInt(param["max"]))) {
        return false;
    }

    return true;
});

jQuery.validator.unobtrusive.adapters.add("strlenrange", ["min", "max"], function (options) {
    options.rules["strlenrange"] = new Array();
    options.rules["strlenrange"]["min"] = options.params.min;
    options.rules["strlenrange"]["max"] = options.params.max;
    options.messages["strlenrange"] = options.message;
});

Обратите внимание, что в качестве второго параметра функции add() выступает массив в ключами значений, передаваемых из атрибута проверки данных. Это те самые ключи из коллекции ValidationParameters() на стороне клиента. Передав их значения в массив options.rules["strlenrange"], обеспечим доступ к ним из метода проверки. В нем они будут полями options.params.

Последней шаг – добавление ссылки на созданный JavaScript в Представление UserProfiles\Create:

@model MVCDemo.Models.NewUserProfileModel
@using MVCDemo.Resources.Views.UserProfiles;
@{
    ViewBag.Title = CreateRes.PageTitle;
}

<h2>@ViewBag.Title</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/Validation/EqualAttribute.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/Validation/StringLenghtAttribute.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <div class="editor-label">
            @Html.LabelFor(model => model.Login)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Login)
            @Html.ValidationMessageFor(model => model.Login)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Password)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Password)
            @Html.ValidationMessageFor(model => model.Password)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ConfirmPassword)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ConfirmPassword)
            @Html.ValidationMessageFor(model => model.ConfirmPassword)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.DisplayName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.DisplayName)
            @Html.ValidationMessageFor(model => model.DisplayName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Email)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Email)
            @Html.ValidationMessageFor(model => model.Email)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Homepage)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Homepage)
            @Html.ValidationMessageFor(model => model.Homepage)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Description)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Description)
            @Html.ValidationMessageFor(model => model.Description)
        </div>

        <div class="editor-label">
            @Html.EditorFor(model => model.AgreementAccepted)
            @Html.LabelFor(model => model.AgreementAccepted)
        </div>
        <div class="editor-field">
            @Html.ValidationMessageFor(model => model.AgreementAccepted)
        </div>

        <p><input type="submit" value="@CreateRes.CreateProfileButton" /></p>
    </fieldset>
}

<div>@Html.ActionLink(CreateRes.BackToProfilesList, "Index")</div>

Данный вариант будет работать аналогично созданному в исходной статье.

Необходимо еще раз отметить, что данные примеры демонстрируют лишь возможности ASP.NET MVC.

Pingbacks and trackbacks (2)+

Добавить комментарий