Integration of validation based on System.ComponentModel.DataAnnotations

Feb 10, 2010 at 4:26 PM

Hello Michael!

 

It's nice to see this project in a good shape thanks to your support!

I have a question or "asking for advise" is a better wording. I was thinking to implement declarative validation based on mechanism provided in System.ComponentModel.DataAnnotations assembly. The idea is that in domain model I'll have validation attributes declared on domain classes' properties and validation check should happen when the value is updated. The problem here is that I'd want to escape strange code like this (Silverlight):

[Required]
public string Name
{
    get { _independentName.OnGet(); return _name; }
    set
    {
        Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Name" });
        _independentName.OnSet();
        _name = value;
    }
}
private readonly Independent _independentName = new Independent();
private string _name;

Specifically I'd like not to explicitly check validation in property's setter. I'd prefer having external "validation binding" mechanism instead as in some cases validation should not be implicitly kicked in and there's need for more control over it in such scenarios. Having that on mind I wondered if you experienced such possibilities or might have an idea how can wrapping mechanism be re-used or extended for such multi-purpose use. Please, let me know if you have some guiding notes on that topic.

Coordinator
Feb 10, 2010 at 6:54 PM

The Update Controls wrapper goes around the view model. These annotations are on the data model. So I can't see any way of reusing the same wrapper to pull validation out of the setter.

But looking at it from another angle, I can see that both UpdateControls.Independent and System.ComponentModel.DataAnnotations.ValidationContext have similar needs. They both need hooks into property getters and setters. It sounds like what you are really after is an aspect-oriented solution to both problems.

I've considered aspect-oriented tool support in the past. I've rejected it for a number of reasons:

  1. It would add a dependency to Update Controls that all consumers would have to take on, whether they used aspects or not.
  2. Aspects can't distinguish between enumerating a collection (OnGet) and modifying it (OnSet).
  3. Update Controls looks like magic; aspects look like magic. The two together would be voodoo.

Even though I don't support aspects in the general sense, you might consider them for your specific scenario. My advice would be to look for a solution that can inject both the cross cutting concerns of Independent.OnSet and Validator.ValidateProperty.

 

Feb 11, 2010 at 6:03 AM

Ok, I see your point. But I thought of mainly exposing data objects "as is" via the ViewModel as they contain quite a lot of properties and wrapping them all would be an issue. I've explored the wrapping mechanism a bit today and it seems that it doesn't just wrap ViewModel. I've inserted this line in ObjectPropertyAtom.OnUserInput before ClassProperty.SetObjectValue call:

Validator.ValidateProperty(value, new ValidationContext(ObjectInstance.WrappedObject, null, null) { MemberName = ClassProperty.Name });

then I did these modifications in demo:

- exposed Payment public property for _payment field

- set customer name binding to "{Binding Payment.Customer.Name, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"

- added [Required] attribute on Name property in Customer class

It was enough to make validation work. I know it's rather rough "integration" and that's why I'm thinking now how to make it more "natural"..

Coordinator
Feb 11, 2010 at 7:04 PM

You are correct. I oversimplified by saying that the wrapper only wraps the view model. It also wraps any object that the view model exposes via a property. So if it exposes the data model directly, then that gets wrapped too.

The more I think about it, the more I like your change. It doesn't add extra dependencies. If you don't want validation, just don't add the binding flags and you don't get it.

Furthermore, if you wanted to add the [Required] attribute to the view model, you wouldn't even have to expose the data model. This opens up a whole new set of options. But again, only if you choose to take advantage of them.

The only problem I see is that someone could put the [Required] attribute on the data model, not expose the data model directly through the view model, and be confused why the validation doesn't occur. It wouldn't be obvious what steps you have to take.

 

Here's an alternative that might solve that problem. If we define a new kind of Independent, we can have it call ValidateProperty in OnSet.

public class ValidatedIndependent
{
    private Independent _independent = new Independent();
    private object _obj;

    public ValidatedIndependent(object obj)
    {
        _obj = obj;
    }

    public void OnSet(object value)
    {
        string memberName = ""; // Walk the call stack to get the member name?
        Validator.ValidateProperty(value, new ValidationContext(_obj, null, null) { MemberName = memberName });
        _independent.OnSet();
    }

    public void OnGet()
    {
        _independent.OnGet();
    }
}

I don't know if that's any better. What do you think?

Feb 11, 2010 at 8:00 PM

I don't know if what I have is a good/correct way to go (obviously you guys know more), but here is how I am doing it right now.

Michael, you can look at my validation wiring on DataModel in the project I sent you.
I am doing similar wiring for view model now, and probably disable validation on data model later on.

I use IDataErrorInfo. 

I used the same T4 templates Michael provided for DataModel generation for LINQ to SQL, but I also generate ViewModel classes with it.

In the code generator I declare partial rule delegates for every property in the class and then add to a collection of all properties' rules. Like so:

public class PropertyRuleBase
    {
        public delegate string PropertyRule(ShmmpDataModelBase obj);

        private List<PropertyRule> _propertyRules = new List<PropertyRule>();

        public string PropertyName { get; set; }

        public List<PropertyRule> PropertyRules { get { return _propertyRules; } }
    }

The property rule delegate is a type which has a collection of all the rules as delegates which need to be validated on a given property.
When property name is called via IDataErrorInfo the base class method uses the collection of property rule delegates to call individual rules on a child object. Like so:

        public string this[string propertyName]
        {
            get
            {
                return ValidateProperty(propertyName, this); 
            }
        }


        protected virtual string ValidateProperty(string name, ShmmpDataModelBase obj)
        {
            StringBuilder result = new StringBuilder();

            foreach (PropertyRuleBase.PropertyRule ruleDelegate in _propertiesRules.SingleOrDefault((p) => p.PropertyName == name).PropertyRules)
            {
                string ruleResult = ruleDelegate(obj);
                string lineBreak = (result.Length != 0 ? "\n" : "");

                if (!String.IsNullOrEmpty(ruleResult)) result.Append(lineBreak + ruleResult);
            }

            return (String.IsNullOrEmpty(result.ToString()) ? null : result.ToString());
        }

        protected virtual bool ValidateAll()
        {
            bool result = false; 

            foreach (PropertyRuleBase propertyRules in _propertiesRules)
            {
                if (!String.IsNullOrEmpty(this.ValidateProperty(propertyRules.PropertyName, this)))
                {
                    result = true;
                    break;
                }
            }

            return result; 
        }


It accepts the whole object instead of a single value, that way I can have complex rules which reqiure references to other properties or even other objects linked to the one which is passed.
It is also very easy to write tests when Validation rules are in the model and not declarative in XAML.

The rules are defined in a partial class, for each entity's view model. All is left is to implement a rule and add it to a collection of property rules for this entity view model. Like so:

        #region Rules' Definitions

        public static string ClientNameLengthValidation(ShmmpDataModelBase obj)
        {

            if (String.IsNullOrEmpty(((Client)obj).ClientName)) return ClientNamePropertyRules.PropertyName + " can not be empty!";
            else return null;
        }

        #endregion

        #region Rules' Registration

        protected override void AddPropertyRules()
        {
            ClientNamePropertyRules.PropertyRules.Add(ClientNameLengthValidation);
            // other rules added in similar fashion.
            // each property can have 0+ rules defined for it. 

            // rules can even accept data context for a more complex validation, using queries against database. Like check if client is already present, and/or made payments.. whatever the logic. 

        }

        #endregion

 
This way I can avoid a case when two or more different objects are being edited at the same time and before one is finished the other one is saved (validated and changes are submitted), changes for other object will be submitted even though object was not in a Valid state. With extra layer of view model for each entity I can have a separate save command in a lock and submit changes only when object is valid. There would be no changes from other object to submit since data model changes occur only on Save command in a viewmodel.

 Currently I am still refining my code generator template for View Model.

Feb 11, 2010 at 8:33 PM

Michael, my idea was to pull validation of domain model while retaining validation logic definitions in it so that it could be used when validation check is necessary (data being edited by user, data arriving on server side before saving to DB). It isn't really a good idea to keep validation triggers directly in properties' setters, at least in my case.

Ivan, well, everything you've described can be done with declarative definitions (attributes) on domain model using System.ComponentModel.DataAnnotations means. E.g. there you have CustomValidationAttribute providing you with ability to validate data through your own sophisticated validation rules while being able to access the whole object via validationContext parameter. As for ViewModel generation - it is not what I'm after..

Feb 11, 2010 at 10:37 PM
Edited Feb 11, 2010 at 10:38 PM

@nayato,

I looked into DataAnnotations, but I could not figure out 2 things:

- how can I generate CustomValidationAttributes for rules which I don't have. I think it would still require putting some generic rule which would then somehow evaluate other rules which could be added later, or I put all the validation logic in one method.

- since I am using LINQ to SQL, how can I submitt changes to db on only 1 object in case of concurrent edits.
The attach-detach feature is not available in LINQ to SQL, while it is in PLINQO. And I don't know how else to overcome the fact that model can get into invalid state. :(

Also I wanted to use IDataErrorInfo. It looks like it can report errors on individual properties and on the whole object.  Seemed to me like a very useful interface.
But I am not a WPF expert by any means and this is my first MVVM and LINQ to SQL project, this is just how I was thinking.

 

Coordinator
Feb 12, 2010 at 5:39 PM

Nayato,

I see what you mean. The ValidatedIndependent class doesn't meet your original objective at all. You need to trigger validation on user input, but not on other processing.

Would you ever need to skip validation on user input? If there is a possibility, then triggering validation in the wrapper is out. Or at least it needs a switch. I can't think of a reason for skipping validation on user input.

I'll give the idea another day or two to roll around. Then I'll incorporate your change. It's better than any solution I can think of for this problem.

 

Coordinator
Feb 16, 2010 at 7:00 PM

I just checked in your change. Please verify that it solves your problem.

Thanks for the patch!

Feb 16, 2010 at 7:26 PM

Sorry, I haven't been around as I was get through another challenge: I made friends of UpdateControls and DevExpress'es AgDataGrid which was a lot of fun as the grid appears to be lacking real binding functionality.

The fix looks just right. Thanks! I appreciate your efforts you put into this project. I've now got on project longing for more than year of inadequate coding. I'm about to do things right and UpdateControls is a part of my plan.