using IDataErrorInfo in asp.net mvc

后端 未结 3 1581
无人及你
无人及你 2020-12-16 07:09

I\'ve got a simple address entry app that I\'m trying to use the IDataErrorInfo interface as explained on the asp.net site.

It works great for items that can be val

相关标签:
3条回答
  • 2020-12-16 07:51

    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.

    0 讨论(0)
  • 2020-12-16 07:52

    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.

    0 讨论(0)
  • 2020-12-16 07:54

    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.

    0 讨论(0)
提交回复
热议问题