используя IDataErrorInfo в asp.net mvc - PullRequest
4 голосов
/ 02 октября 2009

У меня есть простое приложение для ввода адресов, которое я пытаюсь использовать интерфейс IDataErrorInfo, как объяснено на сайте asp.net .

Отлично работает с элементами, которые могут быть проверены независимо, но не очень хорошо, когда некоторые элементы зависят от других. Например, проверка почтового индекса зависит от страны:

    private string _PostalCode;
    public string PostalCode
    {
        get
        {
            return _PostalCode;
        }
        set
        {
            switch (_Country)
            {
                case Countries.USA:
                    if (!Regex.IsMatch(value, @"^[0-9]{5}$"))
                        _errors.Add("PostalCode", "Invalid Zip Code");
                    break;
                case Countries.Canada:
                    if (!Regex.IsMatch(value, @"^([a-z][0-9][a-z]) ?([0-9][a-z][0-9])$", RegexOptions.IgnoreCase))
                        _errors.Add("PostalCode", "Invalid postal Code");
                    break;
                default:
                    throw new ArgumentException("Unknown Country");
            }
            _PostalCode = value;
        }
    }

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

Я мог бы использовать строку Error из IDataErrorInfo, но она не отображается в Html.ValidationMessage рядом с полем.

Ответы [ 3 ]

5 голосов
/ 03 октября 2009

Для более сложной проверки бизнес-правил, а не проверки типов, возможно, лучше реализовать шаблоны проектирования, такие как сервисный уровень. Вы можете проверить ModelState и добавить ошибки на основе вашей логики.

Пример паттернов Роба Конроя можно посмотреть здесь

http://www.asp.net/learn/mvc/tutorial-29-cs.aspx

Эта статья о аннотациях данных также может быть полезна.

http://www.asp.net/learn/mvc/tutorial-39-cs.aspx

Надеюсь, это поможет.

1 голос
/ 17 января 2010

Вот лучшее решение, которое я нашел для более сложной проверки, помимо простой модели аннотаций данных.

Я уверен, что я не одинок в попытках реализовать IDataErrorInfo и увидеть, что он создал для меня только два метода для реализации. Я думаю, подожди минутку - я должен пойти туда и написать свои собственные стандартные процедуры для всего теперь с нуля? А также - что, если у меня есть вещи на уровне модели для проверки. Кажется, что вы сами по себе, когда решаете использовать его, если только вы не хотите сделать что-то , например или this изнутри вашей реализации IDataErrorInfo.

У меня возникла та же проблема, что и у спрашивающего. Я хотел проверить американский почтовый индекс, но только если страна была выбрана в качестве США. Очевидно, что аннотация данных на уровне модели не будет хорошей, потому что это не приведет к тому, что почтовый индекс будет выделен красным цветом как ошибка. [хороший пример аннотации данных уровня класса можно найти в примере проекта MVC 2 в классе PropertiesMustMatchAttribute].

Решение довольно простое:

Сначала вам нужно зарегистрировать связыватель моделей в global.asax. Вы можете сделать это как уровень класса [атрибут], если хотите, но я считаю, что регистрация в global.asax более гибкая.

private void RegisterModelBinders()
{
     ModelBinders.Binders[typeof(UI.Address)] = new AddressModelBinder();
}

Затем создайте класс связующего компонента и напишите свою сложную проверку. У вас есть полный доступ ко всем свойствам объекта. Это запустит после любых аннотаций данных, поэтому вы всегда можете очистить состояние модели, если хотите изменить поведение по умолчанию любых атрибутов проверки.

public class AddressModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        base.OnModelUpdated(controllerContext, bindingContext);

        // get the address to validate
        var address = (Address)bindingContext.Model;

        // validate US zipcode
        if (address.CountryCode == "US")
        {
            if (new Regex(@"^\d{5}([\-]\d{4})?$", RegexOptions.Compiled).
                Match(address.ZipOrPostal ?? "").Success == false)
            {
                // not a valid zipcode so highlight the zipcode field
                var ms = bindingContext.ModelState;                    
                ms.AddModelError(bindingContext.ModelName + ".ZipOrPostal", 
                "The value " + address.ZipOrPostal + " is not a valid zipcode");
            }
        }
        else {
            // we don't care about the rest of the world right now
            // so just rely on a [Required] attribute on ZipOrPostal
        }

        // all other modelbinding attributes such as [Required] 
        // will be processed as normal
    }
}

Прелесть этого в том, что все ваши существующие атрибуты проверки будут по-прежнему работать - [Обязательно], [EmailValidator], [MyCustomValidator] - независимо от того, что у вас есть.

Вы можете просто добавить любой дополнительный код в связыватель модели и задать поле или ошибки ModelState на уровне модели, как вам нужно.

Обратите внимание, что для меня Address является дочерним элементом основной модели - в данном случае CheckoutModel, который выглядит следующим образом:

public class CheckoutModel
{
    // uses AddressModelBinder
    public Address BillingAddress { get; set; }
    public Address ShippingAddress { get; set; }

    // etc.
}

Вот почему я должен сделать bindingContext.ModelName+ ".ZipOrPostal", чтобы ошибка модели была установлена ​​для 'BillingAddress.ZipOrPostal' и 'ShippingAddress.ZipOrPostal'.

PS. Любые комментарии от «типов модульного тестирования» приветствуются. Я не уверен насчет влияния этого на юнит-тестирование.

0 голосов
/ 03 октября 2009

Что касается комментария к строке ошибки, IDataErrorInfo и Html.ValidationMessage, вы можете отобразить сообщения об ошибках уровня объекта и уровня поля, используя:

Html.ValidationMessage("address", "Error")

Html.ValidationMessage("address.PostalCode", "Error")

В вашем контроллере украсьте параметр обработчика метода post для объекта с помощью [Bind (Prefix = "address")]. В HTML укажите поля ввода как таковые ...

<input id="address_PostalCode" name="address.PostalCode" ... />

Я обычно не пользуюсь помощниками Html. Обратите внимание на соглашение об именах между id и name.

...