Another developer who I used to work with had mentioned to me data validation shouldn't even be in the Presenter Class of a traditional MVP/MVC implementation. He also mentioned his approach at the time (which if I recall correctly was something like exception guards?) as well as Jimmy Nilsson's approach towards data validation as described in his book, Applying Domain-Driven Design and Patterns: With Examples in C# and .NET. In the meantime, I had been recently researching how to do MVP with the ASP.NET custom validators since we are using these on our project at work and was trying to find a "better" way to handle rudimentary validation.
After some "blood, sweat, and tears" I think I was able to successfully apply Fowler's Notification Pattern to solve this "issue". The notification pattern tries to manage the capturing of error messages as it relates to data validation that are specific to domain objects and are generally outputted to the end-user. (An example is if an email address is required on a submit form. If the user skips over that then on 'submit' a message is displayed such as "An email address is required...blah blah) It all started while re-reading one of Jeremy Miller's post on validation as part of his CAB series . His post lead to both Fowler's Notification Pattern and two posts from Jean-Paul S. Boodhoo's blog (Part I and Part II). Those served as my blueprints for my implementation.
Essentially, I went through each of their slightly differing approaches to see what I could use. The core of what I ended up with borrows heavily from Fowler with most of my changes just renaming things to suit my liking. Fowler always writes with such clarity and without the cruft and his code examples are so easy to follow that his version was the main driver for what I wanted to do. Miller's and JP took the pattern to another level but it was too much for what I wanted. My goal was to keep it simple of course and let it evolve on its own (BUFD bad!) I initially developed it on a separate test project. Once that worked I then implemented it in our project at work somewhat seamlessly.
I first created the initial base classes that are the foundation of this pattern and that can be re-used on any project. Below are their interfaces:
/// Specific business rule error that provides a specific message about the broken business rules.
public interface IBusinessRuleError
{
/// Gets or sets the name of the property that causes the error.
string PropertyName { get; set; }
/// Gets or sets the specific error message.
string Message { get; set; }
}
/// Set of Business Rules used by Domain Objects that captures and stores errors.
public interface IBusinessRules
{
/// Gets or sets the business rule errors.
IListErrors { get; set; }
/// Gets a value indicating whether this instance has any business rule errors.
bool HasErrors { get;}
/// Determines whether the specified set of business rules contains error.
bool ContainsError(IBusinessRuleError ruleError);
}
Basically 'BusinessRules' manages a collection of individual 'BusinessRule'. The business rule contains the error message and it also contains the name of the specific property for the error that will be used later to when mapping it back to a specific UI control.
Now I added BusinessRules to the abstract DomainObject class and expose it as a property. Initially it was hard-coded into my domain object but at work I decided to pull it into its own class that could then be instantiated internally and, if need be, injected in as a dependency into the domain object base class (as you know ideal for mock testing it!) Here is what I call the "validator"
This interface has the RunValidation method whose purpose is to cycle through it each business rule that the derived class is responsible to implement for itself. In addition, the interface also has some basic, re-usable validation tests of these methods such as IsNullOrBlank, FailIf, etc. (courtesy of Fowler) (NOTE: What struck me very quickly was the similarities between these generic methods and with the Asserts of NUnit. It dawned on me when I started to implement a new one that checked the difference for dates such IsBetween(string startDate, string endDate). Mmm...looks a lot like NUnit's Is Constraint model. In fact, 'FailIf' looks like a special case of Assert.That. I'm wondering whether some framework exists out for me to use instead of trying to create and maintain my own.)
In turn, the validator's members are delegated and exposed as members of the domain class itself:
Once that was done then it was time to actually use it for a specific domain object. So I have a domain object named 'Question' that makes up a 'Quiz':
So in the actual Question class, I override and implement the 'RunValidation' method with "rules/errors" specific to 'Question':
So this is where it all happens. Basically, this is where all the business rules that require validation for 'Question' is kept and maintained. Not in the UI, not in the presenter, not in the database or not anywhere else. Right where it should be. What's great is how nice is it to itemize and view all of your business rules in one place. The best part is unit testing this (which you really can't do well at all if it's in the presenter). Here are one of the tests:
How cool is that? I especially like the clarity of this code line:
question.Rules.ContainsError(descriptionError)
Here are a few more tests:
By implementing this at work the app's Domain model is now slightly less anemic. However, the auto-gen of partial classes presented an issue that I was not too happy with. The MyGeneration template is currently set up to read from the database the constraints of the columns and then it's hard-coded directly into the property setters (which includes throwing exceptions). This forces the trapping of the validation error to occur OUTSIDE of the domain object which goes against this implementation of the pattern. So unless I modify the template to remove this from the setters (or at least move into some private method) I had to circumvent updating via the setters and use some methods as so:
question.Description = "My Description"; question.MaxPointValue = 101;
becomes using overloads
question.UpdateDescriptionUsingValidation("1234567891011") question.UpdateMaxPointValueUsingValidation (101)
and/or
question.UpdateUsingValidation("1234567891011", 101)
Not really what I wanted but it works for now until I can resolve that auto-gen issue (another reason why auto-gen can sometimes be an anti-pattern)
So let's see the entity 'Question' actually used in a Controller/Presenter context:
Now how does that compare with one of the original LONG save methods? The intent, readability, and therefore maintainability is light years better. (NOTE: As a side note, I had to use the NHibernate's ISession.Evict() to prevent the entity from being persisted to the db.)
OK, finally the UI/View/Code-Behind
The controls '_ctlDescriptionValidator' and '_ctlMaxPointValidator' are ASP.NET custom validators that are now really dumbed down. I also used the asp.net 'ValidationSummary' control on the web page without needing to do hardly any wiring up. Here is some of the related HTML:
All in all it does not matter if I use the asp.net validators, my own custom message controls, or whatever. The data validation is not tightly coupled with the UI by using the deadly combo of MVP and the notification pattern!!!
I'm certain that aspects of my implementation can be improved and/or extended in some fashion. There are some things I debated as to which is the best approach but I can go into more detail later (for example, I mulled over a couple of other ways on how to pass the messages to the View but settled on the one above. Another was possiblly using reflection to set the property names in the error messages...but like I said I wanted to keep it simple for now. )
Now I added BusinessRules to the abstract DomainObject class and expose it as a property. Initially it was hard-coded into my domain object but at work I decided to pull it into its own class that could then be instantiated internally and, if need be, injected in as a dependency into the domain object base class (as you know ideal for mock testing it!) Here is what I call the "validator"
public interface IDomainObjectValidator
{
/// Runs the validation of each business rule.
/// Each derived class can override this method to define its own
/// set of validation rules.
void RunValidation();
/// Gets the business rules.
IBusinessRules Rules { get;}
/// Gets a value indicating whether this instance is valid based on whether any business rules failed.
bool IsValid
/// Determines whether [is null or blank] [the specified item to test].
bool IsNullOrBlank(string itemToTest);
/// Fails if condition to test is true.
void FailIf(bool conditionToTest, IBusinessRuleError error);
/// Fails if is null or blank the condition to test is true.
void FailIfNullOrBlank(string itemToTest, IBusinessRuleError error);
This interface has the RunValidation method whose purpose is to cycle through it each business rule that the derived class is responsible to implement for itself. In addition, the interface also has some basic, re-usable validation tests of these methods such as IsNullOrBlank, FailIf, etc. (courtesy of Fowler) (NOTE: What struck me very quickly was the similarities between these generic methods and with the Asserts of NUnit. It dawned on me when I started to implement a new one that checked the difference for dates such IsBetween(string startDate, string endDate). Mmm...looks a lot like NUnit's Is Constraint model. In fact, 'FailIf' looks like a special case of Assert.That. I'm wondering whether some framework exists out for me to use instead of trying to create and maintain my own.)
In turn, the validator's members are delegated and exposed as members of the domain class itself:
// domain object abstract class
private readonly IDomainObjectValidator _validator;
public DomainObject()
{
_validator = new DomainObjectValidator();
}
public DomainObject(IDomainObjectValidator validator)
{
_validator = validator;
}
public bool IsValid
{
get { return _validator.IsValid; }
}
public IBusinessRules Rules
{
get { return _validator.Rules; }
}
public virtual void RunValidation()
{
_validator.RunValidation();
}
public bool IsNullOrBlank(string itemToTest)
{
return _validator.IsNullOrBlank(itemToTest);
}
public void FailIf(bool conditionToTest, IBusinessRuleError error)
{
_validator.FailIf(conditionToTest, error);
}
public void FailIfNullOrBlank(string itemToTest, IBusinessRuleError error)
{
_validator.FailIfNullOrBlank(itemToTest, error);
}
Once that was done then it was time to actually use it for a specific domain object. So I have a domain object named 'Question' that makes up a 'Quiz':
public interface IQuestion
{
/// The text of the question itself.
/// For example, "How old are you?"
string Description { get; set; }
/// Point value of the question if quiz taker gets it correct.
int MaxPointValue { get; set; }
/// Sequence # of the question within a quiz.
int SequenceNumber { get; set; }
// Bunch of other members...
}
So in the actual Question class, I override and implement the 'RunValidation' method with "rules/errors" specific to 'Question':
// Question class
public override void RunValidation()
{
// validation # 1
FailIfNullOrBlank(_description, new BusinessRuleError("Description", "Question description must contain a value."));
// validation # 2
if (_description != null)
{
FailIf(_description.Length > 10,
new BusinessRuleError("Description", "Question description can not be longer than 10 characters."));
}
// validation # 3
FailIf(_maxPointValue > 100, new BusinessRuleError("MaxPointValue", "Maximum Point Value can not exceed 100."));
// ....
// validation # 100...
}
So this is where it all happens. Basically, this is where all the business rules that require validation for 'Question' is kept and maintained. Not in the UI, not in the presenter, not in the database or not anywhere else. Right where it should be. What's great is how nice is it to itemize and view all of your business rules in one place. The best part is unit testing this (which you really can't do well at all if it's in the presenter). Here are one of the tests:
// Question test fixture
[Test][Category("Data Validation")]
public void DoesContainBrokenRuleWhenDescriptionIsNull()
{
Question question = new Question();
question.Description = null;
question.RunValidation();
IBusinessRuleError descriptionError = new BusinessRuleError("Description", "The description for this 'Question'
must contain a value.");
Assert.That(question.Rules.ContainsError(descriptionError), "Does not contain Description error.");
Assert.That(question.IsValid, Is.False, "Question is valid.");
}
How cool is that? I especially like the clarity of this code line:
question.Rules.ContainsError(
Here are a few more tests:
[Test][Category("Data Validation")]
public void DoesContainBrokenRuleWhenDescriptionLengthGreaterThan10()
{
Question question = new Question();
question.Description = "1234567891011";
question.RunValidation();
BusinessRuleError descriptionError = new BusinessRuleError("Description", "The description for this 'Question' can not be longer than 10 characters.");
Assert.That(question.Rules.ContainsError(descriptionError), "Does not contain Description error.");
Assert.That(question.IsValid, Is.False, "Question is valid.");
}
[Test][Category("Data Validation")]
public void DoesContainBrokenRuleWhenMaxPointValueExceeds100()
{
Question question = new Question();
question.MaxPointValue = 101;
question.RunValidation();
BusinessRuleError maxPointValueError = new BusinessRuleError("MaxPointValue", "Maximum Point Value for this 'Question' can not exceed 100.");
Assert.That(question.Rules.ContainsError(maxPointValueError), "Does not contain MaxPointValue error.");
Assert.That(question.IsValid, Is.False, "Question is valid.");
}
By implementing this at work the app's Domain model is now slightly less anemic. However, the auto-gen of partial classes presented an issue that I was not too happy with. The MyGeneration template is currently set up to read from the database the constraints of the columns and then it's hard-coded directly into the property setters (which includes throwing exceptions). This forces the trapping of the validation error to occur OUTSIDE of the domain object which goes against this implementation of the pattern. So unless I modify the template to remove this from the setters (or at least move into some private method) I had to circumvent updating via the setters and use some methods as so:
question.Description = "My Description"; question.MaxPointValue = 101;
becomes using overloads
question.
and/or
question.
Not really what I wanted but it works for now until I can resolve that auto-gen issue (another reason why auto-gen can sometimes be an anti-pattern)
So let's see the entity 'Question' actually used in a Controller/Presenter context:
// Presenter class
public void SaveChanges()
{
IQuestion question = new Question();
question.Description = _view.Description;
question.MaxPointValue = _view.MaxValuePoint;
question.SequenceNumber = _view.SequenceNumber;
question.RunValidation();
if (question.IsValid)
{
_dao.SaveOrUpdate(question);
_view.DisplaySuccess("The question has now been saved.");
}
else
{
_view.DisplayErrors(question.Rules.Errors);
}
}
Now how does that compare with one of the original LONG save methods? The intent, readability, and therefore maintainability is light years better. (NOTE: As a side note, I had to use the NHibernate's ISession.Evict() to prevent the entity from being persisted to the db.)
OK, finally the UI/View/Code-Behind
// View class
public void DisplayErrors(IListerrors)
{
foreach (IBusinessRuleError error in errors)
{
if (error.PropertyName.Equals("Description"))
{
_ctlDescriptionValidator.ErrorMessage = error.Message ;
_ctlDescriptionValidator.IsValid = false;
}
if (error.PropertyName.Equals("MaxPointValue"))
{
_ctlMaxPointValidator.ErrorMessage = error.Message;
_ctlMaxPointValidator.IsValid = false;
}
}
}
The controls '_ctlDescriptionValidator' and '_ctlMaxPointValidator' are ASP.NET custom validators that are now really dumbed down. I also used the asp.net 'ValidationSummary' control on the web page without needing to do hardly any wiring up. Here is some of the related HTML:
<form id="form1" runat="server">
<asp:ValidationSummary ID="_ctlValidationSummary" runat="server" />
<asp:Label ID="_lblSuccessMessage" runat="server"></asp:Label><div>
Description
<asp:TextBox ID="_txtDescription" runat="server" >
</asp:TextBox>
<asp:CustomValidator ID="_ctlDescriptionValidator" runat="server" ControlToValidate="_txtDescription"
ErrorMessage="" OnServerValidate="_ctlDescriptionValidator_ServerValidate">*</asp:CustomValidator><br />
Max Point Value
<asp:TextBox ID="_txtMaxPointValue" runat="server" >
</asp:TextBox>
<asp:CustomValidator ID="_ctlMaxPointValidator" runat="server" ControlToValidate="_txtMaxPointValue"
ErrorMessage="" >*</asp:CustomValidator>
All in all it does not matter if I use the asp.net validators, my own custom message controls, or whatever. The data validation is not tightly coupled with the UI by using the deadly combo of MVP and the notification pattern!!!
I'm certain that aspects of my implementation can be improved and/or extended in some fashion. There are some things I debated as to which is the best approach but I can go into more detail later (for example, I mulled over a couple of other ways on how to pass the messages to the View but settled on the one above. Another was possiblly using reflection to set the property names in the error messages...but like I said I wanted to keep it simple for now. )
No comments:
Post a Comment