Пара примеров, не вошедшие в цикл "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)+

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