ASP.net MVC - Custom attribute error message with nullable properties

前端 未结 3 1676
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-01-23 05:42

I am having a property in my View Model which can accept integer and nullable values:

    [Display(Name = \"Code Postal\")]
    public int? CodePostal { get; set         


        
3条回答
  •  误落风尘
    2021-01-23 06:38

    It's little disappointing to see about the amount of work we have to do when all we want is a custom error message for the implicit validations done by the default model binder. The reason is the DefaultModelBinder hides some important methods as private especially the GetValueInvalidResource and GetValueRequiredResource. I hope they will take care of this in future.

    I was trying to give a generic solution for the problem avoiding to create model binders for every type.

    Honestly I haven't tested the below implementation in all the cases(ex. when binding collections) but did in basic levels.

    So here is the approach.

    We have two custom attributes that helps to pass custom error message for our custom model binder. We could have a base class but that's fine.

    public class PropertyValueInvalidAttribute: Attribute
    {
        public string ErrorMessage { get; set; }
    
        public PropertyValueInvalid(string errorMessage)
        {
            ErrorMessage = errorMessage;
        }
    }
    
    public class PropertyValueRequiredAttribute: Attribute
    {
        public string ErrorMessage { get; set; }
    
        public PropertyValueRequired(string errorMessage)
        {
            ErrorMessage = errorMessage;
        }
    }
    

    Here is the model binder this is generic and independent of types and takes care of customizing error messages for both required and invalid validations.

    public class ExtendedModelBinder : DefaultModelBinder
    {
        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
    
            if (propertyDescriptor.Attributes.OfType().Any())
            {
                var attr = propertyDescriptor.Attributes.OfType().First();
    
                foreach (ModelError error in bindingContext.ModelState[propertyDescriptor.Name]
                .Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null)
                .ToList())
                {
                    for (Exception exception = error.Exception; exception != null; exception = exception.InnerException)
                    {
                        if (exception is FormatException)
                        {
                            bindingContext.ModelState[propertyDescriptor.Name].Errors.Remove(error);
                            bindingContext.ModelState[propertyDescriptor.Name].Errors.Add(attr.ErrorMessage);
                            break;
                        }
                    }
                }
            }
        }
    
        protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            if (propertyDescriptor.Attributes.OfType().Any())
            {
                var attr = propertyDescriptor.Attributes.OfType().First();
    
                var isTypeAllowsNullValue = (!propertyDescriptor.PropertyType.IsValueType || Nullable.GetUnderlyingType(propertyDescriptor.PropertyType) != null);
    
                if (value == null && !isTypeAllowsNullValue)
                {
                    bindingContext.ModelState[propertyDescriptor.Name].Errors.Clear();
                    bindingContext.ModelState.AddModelError(propertyDescriptor.Name, attr.ErrorMessage);
                }
            }
    
            base.OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, value);
        }
    }
    

    We are overriding the OnPropertyValidated method just to override the implicit required error message thrown by the default model binder, and we are overriding the SetProperty just to use our own message when the type is not valid.

    Set our custom binder as the default in Global.asax.cs

    ModelBinders.Binders.DefaultBinder = new ExtendedModelBinder();
    

    And you can decorate your properties like this..

    [PropertyValueRequired("this field is required")]
    [PropertyValueInvalid("type is invalid")]
    [Display(Name = "Code Postal")]
    public int? CodePostal { get; set; }
    

提交回复
热议问题