using IDataErrorInfo in asp.net mvc

≯℡__Kan透↙ 提交于 2019-11-29 02:34:55

For more complex business rule validation, rather than type validation it is maybe better to implement design patterns such as a service layer. You can check the ModelState and add errors based on your logic.

You can view Rob Conroys example of patterns here

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

This article on Data Annotations ay also be useful.

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

Hope this helps.

Here's the best solution I've found for more complex validation beyond the simple data annotations model.

I'm sure I'm not alone in trying to implement IDataErrorInfo and seeing that it has only created for me two methods to implement. I'm thinking wait a minute - do i have to go in there and write my own custom routines for everything now from scratch? And also - what if I have model level things to validate. It seems like you're on your own when you decide to use it unless you want to do something like this or this from within your IDataErrorInfo implementation.

I happened to have the exact same problem as the questioner. I wanted to validate US Zip but only if country was selected as US. Obviously a model-level data annotation wouldn't be any good because that wouldn't cause zipcode to be highlighted in red as an error. [good example of a class level data annotation can be found in the MVC 2 sample project in the PropertiesMustMatchAttribute class].

The solution is quite simple :

First you need to register a modelbinder in global.asax. You can do this as an class level [attribute] if you want but I find registering in global.asax to be more flexible.

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

Then create the modelbinder class, and write your complex validation. You have full access to all properties on the object. This will run after any data annotations have run so you can always clear model state if you want to reverse the default behavior of any validation attributes.

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
    }
}

The beauty of this is that all your existing validation attributes will still work - [Required], [EmailValidator], [MyCustomValidator] - whatever you have.

You can just add in any extra code into the model binder and set field, or model level ModelState errors as you wish.

Please note that for me an Address is a child of the main model - in this case CheckoutModel which looks like this :

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

    // etc.
}

That's why I have to do bindingContext.ModelName+ ".ZipOrPostal" so that the model error will be set for 'BillingAddress.ZipOrPostal' and 'ShippingAddress.ZipOrPostal'.

PS. Any comments from 'unit testing types' appreciated. I'm not sure about the impact of this for unit testing.

Regarding the comment on Error string, IDataErrorInfo and the Html.ValidationMessage, you can display object level vs. field level error messages using:

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

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

In your controller decorate the post method handler parameter for the object with [Bind(Prefix = "address")]. In the HTML, name the input fields as such...

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

I don't generally use the Html helpers. Note the naming convention between id and name.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!