Varying rules according to scenario

Coordinator
Apr 23, 2009 at 8:00 AM

(Posted on behalf of Tim Hardy)

How do DataAnnotations or xVal handle the concept of different requirements based on the scenario taking place (e.g. Create versus Update)? I ask because my first attempt to use straight DataAnnotations resulted in me getting a ModelState.IsValid = false with a message of “ID is Required”. The problem is, I was about to a Create the entity in question, and it’s obvious that the Primary Key is not required when you are doing a Create. It is required when doing an Update. How can I mark my properties to handle these different scenarios using DataAnnotations?

Also, I didn’t even mark my ID property as being required, so I’m curious as to how DataAnnotations figured out that it was required. I’m using LinqToSql, so I figure it’s interpreting one of the LinqToSql attributes, but I’d like to know exactly which one. If you know or could shoot me a link, I’d greatly appreciate it.

I’m about to dive into xVal because it looks great.

Coordinator
Apr 23, 2009 at 8:16 AM
Edited Apr 23, 2009 at 8:17 AM
Tim, good questions.

> I didn’t even mark my ID property as being required, so I’m curious as to how DataAnnotations figured out that it was required
When you're using the default DataAnnotationsRulesProvider, it automatically regards all numeric value types (int, long, etc) as being required because they are value types so in .NET they must always have *some* value- maybe zero, but they can't be null.

> getting a ModelState.IsValid = false with a message of “ID is Required”
I think you're talking about server-side validation here. Remember that xVal's key job is enabling client-side validation, where this problem doesn't occur. On the client, you won't create a textbox for "ID" (because you wouldn't want the user to edit it directly) - at least not during the creation phase - so the client-side validator would ignore the ID property, not attempting to validate it because there's no input control to validate.
On the server, the behaviour will vary according to which validation runner you're using.

xVal handle the concept of different requirements based on the scenario taking place (e.g. Create versus Update)
Different validation frameworks offer different features to support this requirement. xVal acts as a bridge between these frameworks (primarily translating a server-side framework's rules to a client-side framework to enable client-side validation) so it is determined by the features exposed by your server-side facility. If you're using Castle Validation on the server, you can use its RunWhen API to restrict selected rules so they apply in a subset of all scenarios. If you're using DataAnnotations, you'll need to implement this sort of thing manually in your validation runner.

At present, xVal doesn't recognise any notion of RunWhen, simply because not all server-side frameworks support it. However, this question has come up a couple of times now so it's something I'm keen to add support for in a forthcoming release.
Apr 23, 2009 at 11:46 PM
Thanks for your responses. 

Here are my current roadblocks with xVal (some of which may be specific to DataAnnotations themselves):
* Still having the issue with DataAnnotations telling me that my primary key value is required on a Create. What's the best way to handle this without a built-in RunWhen feature?

* Attributes used in a "buddy class" aren't seen.  This works with the Microsoft.Web.Mvc.DataAnnotations.DataAnnotationsModelBinder, but if I use that binder, it will validate upon binding, as you are aware.  I need this functionality however in order to use partial classes.  This is odd to to me because the MetaDataType attribute is in System.ComponentModel.DataAnnotations, not Microsoft.Web.Mvc.DataAnnotations.  I'd expect it to work with xVal and default DataAnnotations, but it's not working for me currently.

* If I move the attributes into my LinqToSql file directly, the validation works, but the error messages are not showing up correctly.  Instead of seeing "The Name field is required", I see "The field is required".  On top of that, the error shows up in my ValidationSummary but not next to the field itself.  This works fine if I use the futures model binder (both the property names showing up and the individual validation messages being displayed), so I'm not sure what I'm doing wrong with xVal.
Here's my validation message syntax - Html.ValidationMessage("Name", "*")
Here's how I'm adding the ModelState errors - ex.AddModelStateErrors(ModelState, "");
Apr 24, 2009 at 11:23 AM
http://xval.codeplex.com/Thread/View.aspx?ThreadId=54300

I have the same problem. I was able to get rid of the primary key validation by commenting out code in xval.

In DataAnnotationsRuleProvider

        protected override IEnumerable<Rule> GetRulesFromProperty(PropertyDescriptor propertyDescriptor)
        {
            return base.GetRulesFromProperty(propertyDescriptor);
                   //.Union(GetNumericValueTypeRulesFromProperty(propertyDescriptor));
        }

GetNumericValueTypeRulesFromProperty is adding rules for number data types.

My server side validation is working, but the client side Html.ClientSideValidation is not generating rules from my buddy class.
Apr 26, 2009 at 12:46 AM

Another problem I'm having, even with the Booking example, is that the DefaultModelBinder validates against the DataAnnotations, even when you're using xVal.  Basically, the model is getting validated before the entity is passed into the controller action, then xVal validates again when the entity is saved.  ModelState already has errors on it when the entity is passed into the controller, and ModelState.IsValid is already false.  Am I missing something?  I'm having trouble getting this to work in the simplest of scenarios.  Any help will be appreciated.
Jun 4, 2009 at 6:34 PM

I was going to post a new question regarding this but then I found this post. I ran into a similar issue where I needed to partially validate my model (updating my model requires 2 separate steps/views).

Anyways... I came up with a fairly simple solution that seems to work well for me, but I wanted to get more opinions about it here.

This works with DataAnnotations only (I think)...This is my validation runner:

    public static class ValidationRunner
    {
        /// <summary>
        /// Gets all the errors for the instance.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <returns></returns>
        public static IEnumerable<ErrorInfo> GetErrors(object instance)
        {
            return GetErrors(instance, null, null);
        }

        /// <summary>
        /// Gets the errors with an include or exclude list.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="include">The include string. Properties seperated by a comma.</param>
        /// <param name="exclude">The exclude string. Properties seperated by a comma.</param>
        /// <returns></returns>
        public static IEnumerable<ErrorInfo> GetErrors(object instance, string include, string exclude)
        {
            string[] i = (string.IsNullOrEmpty(include)) ? null : include.Replace(" ", "").ToLower().Split(',');
            string[] e = (string.IsNullOrEmpty(exclude)) ? null : exclude.Replace(" ", "").ToLower().Split(',');

            return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
                   from attribute in prop.Attributes.OfType<ValidationAttribute>()
                   let whitelist = (i != null) ? i.Contains(prop.Name.ToLower()) : true
                   let blacklist = (e != null) ? e.Contains(prop.Name.ToLower()) : false
                   where (whitelist == true && blacklist == false)
                        && !attribute.IsValid(prop.GetValue(instance))
                   select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
        }
    }

So as you can see I create a white-list and black-list param. I then pass in the fields I want to receive errors for:

            var errors = ValidationRunner.GetErrors( user, "Email,Password", null );
            if ( errors.Any() ) throw new RulesException( errors );

In that example I am creating a new user and I am only adding an email address and password - therefore my white-list contains only those fields and I only get errors for those fields.

I would appreciate any feedback, thanks

Jun 4, 2009 at 6:50 PM

Tim, this may help you in regards to:

"On top of that, the error shows up in my ValidationSummary but not next to the field itself."

http://xval.codeplex.com/Thread/View.aspx?ThreadId=58393

Jun 5, 2009 at 11:34 AM

Hi there,

I solved the “ID is Required” primary key issue for Create scenarios by simply telling the DefaultModelBinder to ignore it.

At least, in my code that error was coming from the MVC framework ModelState, rather than xVal itself.

 

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Insert([Bind(Exclude = "Customer.Id", Prefix = "Customer")]Customer customer)

        {  ...  }

 

Hope this helps solve your problems - it certainly worked for me.

-- Scott

Scott Lowe,

Manchester UK