Custom model validation of dependent properties using Data Annotations

后端 未结 5 1038
迷失自我
迷失自我 2020-11-27 03:30

Since now I\'ve used the excellent FluentValidation library to validate my model classes. In web applications I use it in conjunction with the jquery.validate plugin to per

相关标签:
5条回答
  • 2020-11-27 04:08

    Because the methods of the DataAnnotations of .NET 3.5 don't allow you to supply the actual object validated or a validation context, you will have to do a bit of trickery to accomplish this. I must admit I'm not familiar with ASP.NET MVC, so I can't say how to do this exactly in conjunction with MCV, but you can try using a thread-static value to pass the argument itself. Here is an example with something that might work.

    First create some sort of 'object scope' that allows you to pass objects around without having to pass them through the call stack:

    public sealed class ContextScope : IDisposable 
    {
        [ThreadStatic]
        private static object currentContext;
    
        public ContextScope(object context)
        {
            currentContext = context;
        }
    
        public static object CurrentContext
        {
            get { return context; }
        }
    
        public void Dispose()
        {
            currentContext = null;
        }
    }
    

    Next, create your validator to use the ContextScope:

    public class CustomValidationAttribute : ValidationAttribute
    {
        public override bool IsValid(object value)
        {
             Event e = (Event)ObjectContext.CurrentContext;
    
             // validate event here.
        }
    }
    

    And last but not least, ensure that the object is past around through the ContextScope:

    Event eventToValidate = [....];
    using (var scope new ContextScope(eventToValidate))
    {
        DataAnnotations.Validator.Validate(eventToValidate);
    }
    

    Is this useful?

    0 讨论(0)
  • 2020-11-27 04:09

    It took a little while since your question was asked, but if you still like metadata (at least sometimes), below there is yet another alternative solution, which allows you provide various logical expressions to the attributes:

    [Required]
    public DateTime? StartDate { get; set; }    
    [Required]
    [AssertThat("StartDate != null && EndDate > StartDate")]
    public DateTime? EndDate { get; set; }
    

    It works for server as well as for client side. More details can be found here.

    0 讨论(0)
  • 2020-11-27 04:22

    MVC2 comes with a sample "PropertiesMustMatchAttribute" that shows how to get DataAnnotations to work for you and it should work in both .NET 3.5 and .NET 4.0. That sample code looks like this:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
    public sealed class PropertiesMustMatchAttribute : ValidationAttribute
    {
        private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
    
        private readonly object _typeId = new object();
    
        public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
            : base(_defaultErrorMessage)
        {
            OriginalProperty = originalProperty;
            ConfirmProperty = confirmProperty;
        }
    
        public string ConfirmProperty
        {
            get;
            private set;
        }
    
        public string OriginalProperty
        {
            get;
            private set;
        }
    
        public override object TypeId
        {
            get
            {
                return _typeId;
            }
        }
    
        public override string FormatErrorMessage(string name)
        {
            return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
                OriginalProperty, ConfirmProperty);
        }
    
        public override bool IsValid(object value)
        {
            PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
            object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
            object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
            return Object.Equals(originalValue, confirmValue);
        }
    }
    

    When you use that attribute, rather than put it on a property of your model class, you put it on the class itself:

    [PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public class ChangePasswordModel
    {
        public string NewPassword { get; set; }
        public string ConfirmPassword { get; set; }
    }
    

    When "IsValid" gets called on your custom attribute, the whole model instance is passed to it so you can get the dependent property values that way. You could easily follow this pattern to create a date comparison attribute, or even a more general comparison attribute.

    Brad Wilson has a good example on his blog showing how to add the client-side portion of the validation as well, though I'm not sure if that example will work in both .NET 3.5 and .NET 4.0.

    0 讨论(0)
  • 2020-11-27 04:23

    Instead of the PropertiesMustMatch the CompareAttribute that can be used in MVC3. According to this link http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1:

    public class RegisterModel
    {
        // skipped
    
        [Required]
        [ValidatePasswordLength]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }                       
    
        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
        public string ConfirmPassword { get; set; }
    }
    

    CompareAttribute is a new, very useful validator that is not actually part of System.ComponentModel.DataAnnotations, but has been added to the System.Web.Mvc DLL by the team. Whilst not particularly well named (the only comparison it makes is to check for equality, so perhaps EqualTo would be more obvious), it is easy to see from the usage that this validator checks that the value of one property equals the value of another property. You can see from the code, that the attribute takes in a string property which is the name of the other property that you are comparing. The classic usage of this type of validator is what we are using it for here: password confirmation.

    0 讨论(0)
  • 2020-11-27 04:24

    I had this very problem and recently open sourced my solution: http://foolproof.codeplex.com/

    Foolproof's solution to the example above would be:

    public class Event
    {
        [Required]
        public DateTime? StartDate { get; set; }
    
        [Required]
        [GreaterThan("StartDate")]
        public DateTime? EndDate { get; set; }
    }
    
    0 讨论(0)
提交回复
热议问题