Combining ECO OCL constraint validation with ASP .net page validation
Whenever I create a new ECO application I always create my business classes as a stand-alone .net assembly. This makes it easy to reuse the same business rules in various interfaces (WinForms, WebServices, WebApplications, Data import, etc).
One useful feature of ECO classes is the constraints property. Constraints allow you to enter a message + OCL expression against a class definition in your model. It is then possible for your application to evaluate each of these constraints in order to determine whether or not the current object is valid or not.
Name required:name.length > 0
End date must be after start date:endDate > startDate
Using OCL as a form of validation has been very useful in my WinForm apps (and Win32 apps in Bold for Delphi too). Not only am I able to evaluate each of these expressions, but ECO will also allow me to "subscribe" to their return value. Taking the "End date must be after start date" constraint as an example, subscribing to the expression "endDate > startDate" will result in ECO triggering an event whenever either of those class members are modified. As a result my WinForm GUI is able to show a list of broken constraints to the user, and additionally enable / disable the Save button depending on whether or not the object has any errors or not.
Obtaining constraints for a class
Obtaining a list of constraint expressions for an ECO modeled class is quite simple. The list retrieved will be a union of constraints from the current class plus the constraints of all ancestor classes.
[C#]
public void GetConstraintsForObject(IObject instance, ref ArrayList constraints)
{
if (! instance.Deleted)
{
for(Int32 index = instance.UmlType.Constraints.Count - 1; index >= 0; index--)
...
[Pascal]
procedure GetConstraintsForObject(Instace: IObject; var Constraints: ArrayList)
var
Index: Integer;
begin
if not Instance.Deleted then
for Index := 0 to Instance.UmlType.Constraints.Count - 1 do
...
end;
Each constraint (which is expected to return True/False) may be evaluated like so
[C#]
IOclService ocl = (IOclService) instance.ServiceProvider.GetEcoService(typeof(IOclService));
return (bool) ocl.EvaluateAndSubscribe(instance, expression, null, null).AsObject;
[Pascal]
var
Ocl: IOclService;
begin
Ocl := Instance.ServiceProvider.GetEcoService(typeof(IOclService)) as IOclService;
Result := ocl.EvaluateAndSubscribe(Instance, Expression, nil, nil).AsObject as Boolean;
end;
where Expression is obtained from Constraint.Body.Body
ASP .net page validation
ASP .net page validation is designed so that validators are registered with the current page when it is created. Typically WebControls that implement System.Web.UI.IValidator will be dropped onto your WebForm, these objects will register themselves with the page like so
Page.Validators.Add(......);
The problem with this approach in my opinion is that it is the job of the web-app developer to add validation components to the WebForm. This means that the developer must not only ensure that they have included a suitable validation for the object, but must also ensure that the WebForm is updated whenever the business rules change. This is quite a contrast to the WinForm development I discussed earlier where the WinForm automatically validates based on modelled constraints. In fact, some constraints for business classes may depend upon values in members of associated objects, which could be very difficult to implement an ASP .net validator for.
Adding error messages at runtime
The solution I employed allowed me to check all constraints on the current object, and to add an IValidator for each broken constraint. The constraint message would then appear in the ASP .net ValidationSummary control. The first step was to create a WebControl that supported IValidator.
public class MultiValidator : WebControl, IValidator
{
}
I then added an ArrayList to hold the error strings, and a method to add an error.
private ArrayList Errors = new ArrayList();
public void AddError(string message)
{
Errors.Add(message);
}
When ASP .net validates a page it enumerates all IValidators within its own Validators property, and called IValidator.Validate(). To determine if the page is valid or not it then checks IValidator.IsValid.
To add custom error messages at runtime I decided to create a static validator class which always returns "false" from IsValidator.IsValid. For each error message in my MultiValidator I could then simply create an instance of one of these validators.
[ToolboxItem(false)]
internal class StaticValidator : IValidator
{
private string errorMessage;
#region IValidator
void IValidator.Validate()
{
}
bool IValidator.IsValid
{
get { return false; }
set { }
}
#endregion
public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}
}
Now that the StaticValidator was written, all I needed to do was to add the required IValidator implementations to my MultiValidator class.
#region IValidator
void IValidator.Validate()
{
isValid = (Errors.Count == 0);
foreach(string error in Errors)
{
StaticValidator validator = new StaticValidator();
validator.ErrorMessage = error;
Page.Validators.Add(validator);
Validators.Add(validator);
}//foreach errors
}//Validate
bool IValidator.IsValid
{
get { return isValid; }
set { isValid = value; }
}
#endregion
Using the validator
The first thing to do is to set CausesValidation on the Save button to False. If this property is True, the WebForm will go through the process of obtaining a list of IValidator instances before our source code gets a chance to execute.
Next I populate my ECO object's members with the values entered into the WebForm by the user. For each broken constraint I add an error message to my MultiValidator control.
MultiValidator1.AddError(Constraint.Body.Body);
Now it is time for the WebForm to register each IValidator instance. Once the errors have all been added to the MultiValidator control I call Page.Validate()
Once Page.Validate() has been called I can now check Page.IsValid as normal. The user will see a list of broken constraints from the model, and the object will not be saved if the page is not valid.
Control source code
MultiValidator.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DroopyEyes.Web.Controls
{
public class MultiValidator : WebControl, IValidator
{
#region Members
private bool isValid = true;
private string errorMessage = "";
private ArrayList Errors = new ArrayList();
private ArrayList Validators = new ArrayList();
#endregion
#region IValidator
void IValidator.Validate()
{
isValid = (Errors.Count == 0);
foreach(string error in Errors)
{
StaticValidator validator = new StaticValidator();
validator.ErrorMessage = error;
Page.Validators.Add(validator);
Validators.Add(validator);
}//foreach errors
}//Validate
bool IValidator.IsValid
{
get { return isValid; }
set { isValid = value; }
}//IsValid
#endregion
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.Validators.Add(this);
}
protected override void OnUnload(EventArgs e)
{
if (Page != null)
{
Page.Validators.Remove(this);
foreach(IValidator validator in Validators)
Page.Validators.Remove(validator);
}//Page != null
base.OnUnload(e);
}
public void AddError(string message)
{
Errors.Add(message);
}//AddError
#region Properties
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}//ErrorMessage
#endregion
}
}
StaticValidator.cs
using System;
using System.ComponentModel;
using System.Web.UI;
namespace DroopyEyes.Web.Controls
{
[ToolboxItem(false)]
internal class StaticValidator : IValidator
{
private string errorMessage;
#region IValidator
void IValidator.Validate()
{
}//Validate
bool IValidator.IsValid
{
get { return false; }
set { }
}//IsValid
#endregion
public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}//ErrorMessage
}
}
Summary
Using this simple IValidator technique it is possible to provide object validation based on modelled constraints, rather than depending on the web-app designer to validate objects correctly. Added benefits of this technique include
- Complex validations may be used due to the power of OCL expressions
- Object validation rules are specified where they belong, in the model
- The same validation rules are used no matter what type of application is using the business classes
- Updating the validation rules in your business classes assembly is enough to update the validation in all of your applications
About the author
Peter Morris has been developing model-driven applications since his introduction to Bold
for Delphi (ECO's predecessor) back in 2001. He is the managing director of Air Software Ltd, a company that provides bespoke
software solutions and consultancy services to various industries.
Connect with Us