One of the strengths of WCF RIA Services is in its capabilities around the application of common validation logic on both the client tier and the service tier. I am using the following model for validation example -
In our particular example, the Product class has a MetadataType attribute applied to it which states that the generated nested class Product.ProductMetadata can also contribute metadata to the Product class itself which is being generated for me by the Entity Framework generation process.
Lets apply a validation attribute such as this Range attribute below to the ProductMetadata property called UnitsOnOrder and the implication is that I want to apply that Range attribute to the UnitsOnOrder property of Product;
Lets apply a validation attribute such as this Range attribute below to the ProductMetadata property called UnitsOnOrder and the implication is that I want to apply that Range attribute to the UnitsOnOrder property of Product;
There are a number of pre-canned validation attributes in System.ComponentModel.DataAnnotations such as;
• Range
• Required
• RegularExpression
• StringLength
all of which are derived from ValidationAttribute.
Now, with my Range attribute applied on the service-side, let’s imagine that I have some client code that tries to create a new product like this sketched client-side code below does;
• Range
• Required
• RegularExpression
• StringLength
all of which are derived from ValidationAttribute.
Now, with my Range attribute applied on the service-side, let’s imagine that I have some client code that tries to create a new product like this sketched client-side code below does;
Attempting to insert an invalid product like this ( invalid because of the 101 units on order ) is going to result in errors being returned from the call to SubmitChanges and running the code gives me;
In this case, the invalid data never made it to the service. So we have following posibilites -
1. It can be caught on the client when we call SubmitChanges()
2. It can be caught by the service when a SubmitChanges() call arrives on the service-side
3. It can be caught on the client long before we ever call SubmitChanges()
1. It can be caught on the client when we call SubmitChanges()
2. It can be caught by the service when a SubmitChanges() call arrives on the service-side
3. It can be caught on the client long before we ever call SubmitChanges()
Validation Around SubmitChanges()
My attempt to call SubmitChanges() never left the client side because a ChangeSet was constructed ( containing my single insert of a single invalid Product ) and then that ChangeSet was validated and the failed validation result is immediately returned to the client rather than wasting a trip to the service.
What if I apply some validation logic that’s a little more custom? As a starting example, I can replace my Range attribute with a CustomValidation attribute like this one;
What if I apply some validation logic that’s a little more custom? As a starting example, I can replace my Range attribute with a CustomValidation attribute like this one;
Now, I have a choice about how I build the type MyValidator on the service-side. I can either put it into a regular code file and, in that case, the validation will only be applied on the service-side. So, if I write MyValidator as;
and then re-run my original client side code against this then the validation will not happen on the client because the code generation process has not copied the MyValidator type to the client and nor has it generated the CustomValidation attribute on the Product’s UnitsOnOrder property.
Consequently, the call to SubmitChanges() will cross the network and rely on the service-side to do the validation.
When that call reaches the service-side, my DomainService (ProductService) will get instantiated and then it will go through its lifecycle of;
Initialize->Submit->AuthorizeChangeSet->ValidateChangeSet
and when my ChangeSet hits that ValidateChangeSet() method, the base class implementation will essentially use the System.ComponentModel.DataAnnotations.Validator class to validate the entities which in my case will cause a validation failure.
Once again the end result is that I get an error back to my call to SubmitChanges but this one actually came from the service-side unlike the previous example where there was no need to invoke the service.
If I want my new custom validation to occur down on the client, I can easily arrange for the MyValidator type to be shared with the client. I just rename its source-code file to include a .shared as in;
Consequently, the call to SubmitChanges() will cross the network and rely on the service-side to do the validation.
When that call reaches the service-side, my DomainService (ProductService) will get instantiated and then it will go through its lifecycle of;
Initialize->Submit->AuthorizeChangeSet->ValidateChangeSet
and when my ChangeSet hits that ValidateChangeSet() method, the base class implementation will essentially use the System.ComponentModel.DataAnnotations.Validator class to validate the entities which in my case will cause a validation failure.
Once again the end result is that I get an error back to my call to SubmitChanges but this one actually came from the service-side unlike the previous example where there was no need to invoke the service.
If I want my new custom validation to occur down on the client, I can easily arrange for the MyValidator type to be shared with the client. I just rename its source-code file to include a .shared as in;
and now the code generation process will work differently and make sure that on the client side two additional things happen;
1. The type MyValidator is copied to the client side and is built into the application there.
2. The same CustomValidation attribute will also be applied to the UnitsOnOrder of the Product entity
so now when I run my client-side code I’ll get validation once again on the client and the service-side will never be invoked.
There are other ways of doing custom validation. On the service-side, I could alter the definition of my Product metadata class so that it looked something like; and then I can change the definition of the MyValidator type;
1. The type MyValidator is copied to the client side and is built into the application there.
2. The same CustomValidation attribute will also be applied to the UnitsOnOrder of the Product entity
so now when I run my client-side code I’ll get validation once again on the client and the service-side will never be invoked.
There are other ways of doing custom validation. On the service-side, I could alter the definition of my Product metadata class so that it looked something like; and then I can change the definition of the MyValidator type;
Once again, if I place this into a file with a .shared.cs extension on it then it will run both client and service-side whereas if I place it in a file without a .shared.cs extension then it will only run client side.
If I only want the validation to run service-side then another option is to remove the CustomValidation attribute from my Product class and, instead, modify my DomainService itself. Here I ll modify its InsertProduct method to include a CustomValidation attribute as below;
If I only want the validation to run service-side then another option is to remove the CustomValidation attribute from my Product class and, instead, modify my DomainService itself. Here I ll modify its InsertProduct method to include a CustomValidation attribute as below;
Then I get that same custom validation of my Product instance but, in this case, that’s only on the service-side and it’s only for Insert operations because that’s the only place where I have applied the attribute. Clearly, I could also apply it to Update and Delete or have different validators plugged in on those operations if I wanted that flexibility.
Another option about applying custom validation is to build my own class derived from ValidationAttribute in order to have my own library of custom validation attributes. As an again, contrived, example I might build an attribute such as this one which makes a fairly weak attempt to ensure that a numeric value is an even number; and I could then apply that on my ProductMetadata class to something like UnitsOnOrder;
Another option about applying custom validation is to build my own class derived from ValidationAttribute in order to have my own library of custom validation attributes. As an again, contrived, example I might build an attribute such as this one which makes a fairly weak attempt to ensure that a numeric value is an even number; and I could then apply that on my ProductMetadata class to something like UnitsOnOrder;
Once again, if I put the code for my EvenNumberAttribute into a file with a .shared.cs extension then it will be run on both the client and the service side whereas if I don’t have a .shared.cs extension then it will only apply on the service side.
Validation Before SubmitChanges()
If I revert back to my original setup and fragment of client-side code, reproduced below, which was being validated by a model which had a Range attribute on the UnitsOnOrder property;
Validation Before SubmitChanges()
If I revert back to my original setup and fragment of client-side code, reproduced below, which was being validated by a model which had a Range attribute on the UnitsOnOrder property;
then it’s worth nothing that the Product that I create and populate is invalid from the point at which I set the UnitsOnOrder to a value of 101 and the entity itself “knows” that it is invalid from that point.
This is because the client-side generated Product class derives from Entity and is code-generated such that every property setter calls the base class ValidateProperty() method every time the property value changes.
What does ValidateProperty do? Ultimately it ends up in a call to Validator.ValidateProperty from System.ComponentModel.DataAnnotations and it’s that method which will perform the validation based on the validation attributes that it “sees”.
Consequently, as soon as the call to the UnitsOnOrder property setter has completed, the Entity will already be marked as invalid and I could have checked for validation results by doing something like;
This is because the client-side generated Product class derives from Entity and is code-generated such that every property setter calls the base class ValidateProperty() method every time the property value changes.
What does ValidateProperty do? Ultimately it ends up in a call to Validator.ValidateProperty from System.ComponentModel.DataAnnotations and it’s that method which will perform the validation based on the validation attributes that it “sees”.
Consequently, as soon as the call to the UnitsOnOrder property setter has completed, the Entity will already be marked as invalid and I could have checked for validation results by doing something like;
which displays a MessageBox showing that the Entity knew that it had errors as soon as the property had been set to an invalid value and long before I got anywhere near to calling SubmitChanges() on the DomainContext.
But, in Silverlight this would be a pretty lame way of gathering up the validation problems and reporting them to the UI because the Entity base class implements INotifyDataErrorInfo and so it is fully capable of returning validation errors to the UI ( both synchronously and asynchronously ).
If I create the most basic of UIs around this data; and if I then have code which sets up a DataContext with an invalid Product; then, because of INotifyDataErrorInfo and because I set the NotifyOnValidationError on my bindings then the validation problem will show up immediately in the UI;
But, in Silverlight this would be a pretty lame way of gathering up the validation problems and reporting them to the UI because the Entity base class implements INotifyDataErrorInfo and so it is fully capable of returning validation errors to the UI ( both synchronously and asynchronously ).
If I create the most basic of UIs around this data; and if I then have code which sets up a DataContext with an invalid Product; then, because of INotifyDataErrorInfo and because I set the NotifyOnValidationError on my bindings then the validation problem will show up immediately in the UI;
and I could also drop a validation summary onto the same form to display that information and, just by virtue of putting that ValidationSummary into the same parent container (in this case, a Grid) it picks up the validation problems as in;
Now, I could have some kind of “Submit Changes” button that enabled/disabled itself based on the HasValidationErrors property of my Entity (or I could do something more complex and have a SubmitChanges ICommand that did a similar thing) as in;
where negateConverter is just a converter that flips over a boolean value and so that’s nice because it means that my UI won’t let my SubmitChanges call execute if the underlying Entity has validation problems.
That’s going to work out “ok” if I am doing per property validation because the generated Entity-derived class on the client side calls Entity.ValidateProperty every time a property changes.
But what if I have custom validation on the Product type itself. For instance, what if I have this routine that wants to validate a whole Product instance rather than just a single numeric property value?
That’s going to work out “ok” if I am doing per property validation because the generated Entity-derived class on the client side calls Entity.ValidateProperty every time a property changes.
But what if I have custom validation on the Product type itself. For instance, what if I have this routine that wants to validate a whole Product instance rather than just a single numeric property value?
and I make sure this is available to the client-side by putting it into a .shared.cs file and I associate it in my service-side metadata code with the Product type as in;
then what happens client side when I have a UnitPrice, UnitsOnOrder combination that results in > 1000.0? Nothing.
Why? Because nothing in the framework is going to attempt to validate the whole Entity instance. There is nothing in the generated setters that will cause this validation to run. So, what I see in my UI is;
Why? Because nothing in the framework is going to attempt to validate the whole Entity instance. There is nothing in the generated setters that will cause this validation to run. So, what I see in my UI is;
Now if I actually make a call to DomainContext.SubmitChanges then it will validate the Entity and that will fail. That is, if I put some code behind my “Submit Changes” button like;
then I see that my validation is being called on the client-side and that it is failing. However, my UI still doesn’t display anything.
Why? Because the errors that I’m raising from my validation routine are not being associated with any particular property of the Entity. They are being associated with the “whole entity”. The interface INotifyDataErrorInfo associates the [null/string.empty] property name with errors that are meant to relate to the “whole object”.
The bindings I have set up in my UI are only set up to look for validation problems with properties (ProductName, UnitPrice, UnitsOnOrder).
If I want to get validation problems for the “whole object” displayed in the UI then (AFAIK) I need to have a binding that is interested in the “whole object” such as below on line 4;
Why? Because the errors that I’m raising from my validation routine are not being associated with any particular property of the Entity. They are being associated with the “whole entity”. The interface INotifyDataErrorInfo associates the [null/string.empty] property name with errors that are meant to relate to the “whole object”.
The bindings I have set up in my UI are only set up to look for validation problems with properties (ProductName, UnitPrice, UnitsOnOrder).
If I want to get validation problems for the “whole object” displayed in the UI then (AFAIK) I need to have a binding that is interested in the “whole object” such as below on line 4;
notice that the Grid binding its own DataContext back to its own DataContext doesn’t really achieve anything other than to switch on the NotifyOnValidationError and that causes my UI to do the right thing in the sense of;
Ok – so I can get my “whole entity” validation errors to show up on the screen but only after I call DomainContext.SubmitChanges or execute equivalent code – I think equivalent code might be something like;
which left me wondering whether there’s some way I can force this sort of code to run whenever my UnitPrice or UnitsOnOrder property value change in order to avoid having to call SubmitChanges to find out about those validation errors.
Some routes that I tried or at least thought about;
1. I can’t (AFAIK) interfere with the code generation process so I don’t think there’s anything that I can do to force the whole entity to be validated when those 2 properties change by that route.
2. I figured that I might try to hack together a custom validation attribute which didn’t actually do any validation but, instead, tried to get the whole entity validated and then I could drop that attribute onto the 2 related properties so that they would force the revalidation of the whole entity.
I tried to sketch out (2) above. The “trick” is that I can only get my code invoked as a result of a code-generated call which calls Entity.ValidateProperty and passes the prospective property value which has not yet been set on the underlying object instance. So perhaps I can sketch out an attribute like;
Some routes that I tried or at least thought about;
1. I can’t (AFAIK) interfere with the code generation process so I don’t think there’s anything that I can do to force the whole entity to be validated when those 2 properties change by that route.
2. I figured that I might try to hack together a custom validation attribute which didn’t actually do any validation but, instead, tried to get the whole entity validated and then I could drop that attribute onto the 2 related properties so that they would force the revalidation of the whole entity.
I tried to sketch out (2) above. The “trick” is that I can only get my code invoked as a result of a code-generated call which calls Entity.ValidateProperty and passes the prospective property value which has not yet been set on the underlying object instance. So perhaps I can sketch out an attribute like;
and note that this is really just trying to call TryValidateObject which will call any “whole entity validation” that I have associated with my entity and it also tries (hack!) to pass down a hint in the ValidationContext.Items collection of what the proposed new value for the property is. I could drop that attribute onto my UnitsOnOrder and UnitPrice properties;
and then I drop into a not-too-pleasant ValidateProduct routine on my ProductValidator class;
and you’ll notice that this is trying in a very brittle way to do the right thing in that it is trying to serve two purposes';
1. Whole entity validation when called from a place like SubmitChanges when it is ok to inspect the actual value of the two properties UnitPrice/UnitsOnOrder on the instance.
2. Whole entity validation when called from a generated property setter when there is a changed property value still “in flight” and we have to try and figure out which property it is and what the value is going to be.
1. Whole entity validation when called from a place like SubmitChanges when it is ok to inspect the actual value of the two properties UnitPrice/UnitsOnOrder on the instance.
2. Whole entity validation when called from a generated property setter when there is a changed property value still “in flight” and we have to try and figure out which property it is and what the value is going to be.
On your cross-validation problem, the below partial class on the client would enable the properties to force each other to revalidate:
ReplyDeletepublic partial Product
{
partial void OnUnitPriceChanged()
{
this.ValidateProperty("UnitsOnOrder", UnitsOnOrder);
}
partial void OnUnitsOnOrderChanged()
{
this.ValidateProperty("UnitPrice", UnitPrice);
}
}
With that change in place you would not need the entity level validation at all. You can add the partial class server side if you want, just put it in a partial class and surround the code with #if SILVERLIGHT #endif.