Combining ECO OCL constraint validation with ASP .net page validation

By: Peter Morris

Abstract: This article will describe how to combine the power of class based OCL constraints in ECO with ASP .net page validation.

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.

Runtime validation screenshot

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

  1. Complex validations may be used due to the power of OCL expressions
  2. Object validation rules are specified where they belong, in the model
  3. The same validation rules are used no matter what type of application is using the business classes
  4. 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.


Server Response from: ETNASC02