Validation In The NuSoft Framework
posted on 04/23/08 at 12:50:36 am by Joel Ross
One of the new features of the NuSoft Framework that we added for version 3 is validation. We went back and forth on how we wanted to implement it - at one point we even had a third party validation framework integrated into our project. We eventually decided against that, backing out those changes. We didn't want to distribute a third party DLL with our source. At the time, I was against the decision, but have since come around and think that what we've built has the functionality that we need. Note that I'm not saying necessarily that I think we should be against working with third party libraries - just that we won't include them with our source. We might provide the hooks to use them easily, but we won't include it by default.
Anyway, back to validation. We've include two types of validations out of the box - required fields and maximum length (for string fields). We also provide the hooks so you can include your own custom validations. It's much easier to understand with a code sample. Here's a check to see if a customer is valid.
1: if(this.Customer.IsValid)
2: {
3: this.Customer.Save();
4: }
5: else
6: {
7: foreach (ValidationError error in this.Customer.GetValidationErrors())
8: {
9: ErrorMessages.Text += error.ErrorMessage + "<br />";
10: }
11: }
We've added a property to all entities - IsValid - to determine if an entity is valid. If it is, then you can go ahead and save the customer. If not, then you can get at the errors and display them. Note that calling Save() without first checking IsValid when the entity is not valid will throw a validation exception.
Anyway, that's the very basics of validation. You can also validate just a particular property:
1: List<ValidationError> errors =
2: this.Customer.GetValidationErrorsByProperty(Customer.CustomerProperties.ContactName);
As you can see, we've added constants for each property as a subclass to each entity. It's not perfect, but it does allow for a little bit of security to avoid "magic strings". The above call will validate just the "ContactName" property. By default, it will check to see if the field is nullable and if so, ensure that the current value isn't null. If the field is a string, it will also verify that the length does not exceed what the database can handle.
These methods are in EntityBase, so if you want to add custom validations, you can override the methods in the entity class - for example, in the Customer class, if I wanted to check that "ContactName" did not start with "Z", I can override the above method and add my validations:
1: public override List<ValidationError> GetValidationErrorsByProperty(string propertyName)
2: {
3: List<ValidationError> errors = new List<ValidationError>();
4: if(propertyName == CustomerProperties.ContactName)
5: {
6: if(ContactName.StartsWith("Z"))
7: {
8: errors.Add(new ValidationError("Contact Name can't start with 'Z'", this, propertyName));
9: }
10: }
11: errors.AddRange(base.GetValidationErrorsByProperty(propertyName));
12: return errors;
13: }
You can also do validation by groups. We have a class called ValidationGroup that holds the built-in validation groups: Length and Required. You can add custom ones to this class if you want as well.
The last main area I wanted to touch on with respect to validation is the events that we fire. We've added four events.
- EntityValidating
- PropertyValidating
- PropertyValidated
- EntityValidated
I listed them out in that order on purpose. When you call GetValidationErrors() (or check IsValid), the EntityValidating event will fire. Then, for each property validated, the PropertyValidating and PropertyValidated events will fire. Finally, the EntityValidated event fires. As is customary in the framework. the "ing" events have cancel abilities, so you can cancel validation. Obviously, canceling during a Save won't make the database accept nulls where it shouldn't, so you'd still get a Sql exception instead of a validation exception. The event args for these events contains the entity being validated and the current SqlHelper (if available), just like the standard EntityEventArgs. In addition to those, it also adds the property being validated, and the validation group being validated - if those are available.
So that's validation in a nutshell. I think we've made it pretty flexible, but I'm also realistic about it's limitations in it's current form. The one glaring issue to me is the fact that you can't validate a new value before you actually apply it to the entity. One thing I'd like to see is a way to test a value - maybe an overload on GetValidationErrorsByProperty that takes the new value - I haven't thought through the best way to do it yet, so I'm just speculating. But that seems like a decent way to do it without having to set the property on the entity to do the check.
This was actually one of the advantages of having a third party library - it gave us this ability. Not only that, but it would produce client side custom validators where it could - validating length, required fields, etc. Of course, it's been a very conscious decision to keep the framework UI agnostic, so producing ASP.NET specific validation controls isn't really something we want to do. We have the pieces to expose some of this, but right now, it would be a manual process and not necessarily intuitive. Maybe I'll follow this up with an example of you can do that.
For those who have used other validation frameworks, what features are we missing? How would you have done things differently? We're (in my opinion) still early in our implementation - it works and is extensible, but I know there's room for improvement. I just need someone smarter than me to help point me in the right direction!
Categories: Development, C#, RCM Technologies