A Brief Introduction Into Web Form Validation
As you know, the purpose of an HTML form is to deliver user input from the client to the server. One of the purposes of web applications is to make sure that the incoming data is valid, safe, and ready for further processing. This is called "input validation". The process of input validation can be divided into three steps:
- Format validation. For example, the server needs to make sure that fields don't contain any unsafe values, that the required fields have data, that values of different types can be safely converted into those types, etc. Normally, format validation is done by validation controls.
- Conversion of received string values into typed values. For example, if the form accepts loan applications, the incoming data needs to be converted into some kind of a loan application object where the string "annual gross income" would be converted into
GrossIncome property of type System.Decimal.
- Optional validation of business rules against strongly typed data. For example, the server must make sure that annual income is larger than the total debt and that the applicant has been employed for the last 5 years.
The first two steps are well documented and understood. They are almost always required and can be automated by using common or custom tools and controls, at least to some degree. But the third step is not that simple. A lot of developers I know are absolutely sure that they never use business rules in their web forms. And most of them are wrong. The reason is simple: we often mix format and rule validation in one statement without realizing it. Of course, some small forms don't use business rules at all. But it's rather an exception from the rule. Consider the following code:
DateTime test;
if(txtLastEmployed.Text.Length == 0 ||
!DateTime.TryParse(txtLastEmployed.Text, out test) ||
test < DateTime.Now.AddYears(-5))
{
DisplayWarningMessage("The date of last employment is either empty,
or has invalid format, or is in the past of limit.");
}
In this example, the first condition is format validation, the second is type conversion, and the third is evaluation of a business rule. It's easy to see that the first two conditions will never change, unless the LastEmployed property gets removed from our imaginary data object. But the third condition is not guaranteed to stay the same forever. The way it's written, if tomorrow the business owner changes the 5 years term to any other number, we will have to compile, test, and deploy the entire application all over again just because of this tiny change.
Of course, we can always store condition values in configuration files. For example, the following snippet would demonstrate such an implementation:
try
{
}
catch(Exception ex)
{
if (ConfigurationManager.AppSettings["Production"] == "true")
{
LogException(ex);
DisplayAnExcuse("Sorry, we screwed up!");
}
else throw ex;
}
This code enforces a business rule "If the environment is production, then log the exception and apologize. Else throw it up the stack so the developer can work with it". The rule is very small, it contains only one condition, and it's easy to change its value in the configuration file without the need to recompile the entire web app. (Note that this example is for demonstration only. Unless some special exception is expected, you wouldn't want to handle general exceptions in the page code. Exception handling belongs in Global.asax.)
But what if a web form has dozens of input controls and must enforce complicated business rules that can (and certainly will) change in the future? Something like a large shopping cart or a business tax form? In this case, we would eliminate lots of issues, bugs, test cases, and maintenance headaches if we employ a business rules engine.
Let's briefly talk about business rules and rule engines before we get to the main point.
Business Rules
A business rule is a collection of conditions that can be grouped in rule sets by their execution priorities. You don't need to learn anything special to understand what they are and how they work. Simply attend any corporate meeting and listen to business analysts while they explain requirements for a piece of software. "We need to make sure that all approved applicants are older than 18 and have a credit score of at least 640". This is a business rule, right there. Let's rewrite it in a machine-like format:
If Applicant.Age is greater or equal to 18
and GetCredit(Aplicant.SSN) is greater or equal to 640
then Approve
else Reject
The Applicant class is typically called the "source" object. All its public value typed properties (Age, SSN) are called "rule fields" or "facts". Some rules engines also support in-rule methods (GetCredit) that they can invoke during the rule evaluation.
Rules can be of two types: evaluation type and execution type. The evaluation type rules are designed to answer only one question: "Does the source object conform to the rule or not?" The return of such a rule is always a Boolean value. This type of rules cannot have any action that must be invoked or "else if" and "else" flow sections. Here is an example of an evaluation type rule:
Check if Car.Year > (Today.Year - 4)
and (Car.Engine.Type = Petrol or Car.Engine.Turbo = True)
Serious engines also support execution type rules. These rules allow division of logic into flow sections, exactly like "if - else" statements in any computer language. Only the "if" section is required in execution type rules. The "else if" and "else" flow sections are optional. Each flow section must contain one or more actions that the engine executes if the entire section evaluates to true. In .NET-based engines, rule actions are public methods that return System.Void. They can be parameterless or take any number of value typed params. Here is an example of an execution type rule:
If Car.Engine.Cylinders = 16
or (Car.Brand = "Ferrari" and Car.Price < 10,000)
then BuyRightAway()
else if Car.Price > 30,000 then MakeOffer(25,000)
else ShopSomewhereElse()
Business Rules Engine
They say that the number of requirements for a business rules engine is pretty much equal to the total number of mid-level IT managers that exist out there. Yet, we can safely extract four major features that any engine must implement:
- Rules can be created, edited, tested, deployed, and evaluated against source objects without recompilation of the main code.
- Business people must be able to author rules with no (or very little) involvement of IT department.
- The rule evaluation must be fast.
- Although not really useful in validation of web forms, the rule evaluation must be thread safe.
Building It
So, why the use of a rules engine would be great for web form validation? Because, once set up, it'll give business people the ability to manage business logic without bothering you, the developer. Of course, you could argue that the cost of the engine itself (sometimes hundreds of thousands of bucks) and even the cost of implementation and maintenance of such system is hardly justifiable when you simply need to validate a web form, even if it's a really complicated form. Such systems belong in banks and government agencies where they execute hundreds of huge rule sets against millions and millions of source objects.
You see, that's the thing: I don't believe this is true anymore. Now business rules make total sense in web form validation if we use the new type of rule engines that has begun to emerge recently.
So, let's build a web form that will use such engine for data validation. It'll take just 5 short steps. There are two things that we need to take care of first, though. We need to define the requirements for our small example and we need to know details about the engine we are going to use. Then we will be ready to go.
For the sake of simplifying this article and making it more readable, I'm going to create a single web page that would contain both our web form and engine's UI for rule authoring. You would be able to create the rule, fill out the form and evaluate your input against that rule, all on the same page. The final result would look like this screenshot:
(Obviously, the business rule on this screenshot has nothing to do with reality. I just needed some conditions there for demonstration purposes.)
In real life, though, you would definitely want to separate these two things. The engine's rule authoring UI would sit somewhere on a corporate intranet. Business folks would use it to create or edit validation rules for your web form(s) and save them to a database or a file system. The form itself would be hosted by other public or secured website. Its server would take care of loading the proper rule from the storage and evaluating it against the posted input every time a user submits the form.
For our example, we are going to use the new ASP.NET server control called Web Rule. It has a free version, supports .NET 3.5 and up, comes in a small .NET assembly that you simply reference from your project and doesn't require installation on the server or any special account privileges, so it can be used anywhere. Download a copy of Web Rule control from here.
Web Rule has a unique client UI that allows rule authors to create validation rules by simply selecting elements from context-sensitive menus. This works very similar to the way IntelliSense works in Visual Studio. It makes rule authoring a very intuitive and easy process for business people, requiring almost no learning time.
Web Rule also employs parentheses to prioritize the order of execution of rule equations. This is quite different from the traditional approach which simply gives each equation a "priority". The use of parentheses greatly simplifies life for a rule author as long as that author understands basic algebra. For example, a typical execution type rule with four equations would look something like this in a traditional rules engine:
When
And
Name = "John" #priority 10
Age < 60 #priority 100
Or
Street = "123 Main Street" #priority 1
City = "London" #priority 10
Then Do()
Else DoNot()
Not really a business user-friendly statement. But with parentheses, you can make the same rule readable and understandable for pretty much anybody:
if (Name = "John" and Age < 60)
or (Street = "123 Main Street" and City = "London")
then Do
else DoNot
This is only one of many reasons why I think that these new engines are great for the web. Download and run the article's project (above) to experience it for yourself. Very cool stuff.
Now, we are ready to build our form. Let's say that we need a web form for a medical facility to process new patients. I know absolutely nothing about medical business/procedures/processes/policies/whatever. And I've never seen any medical validation rule or policy in my entire life. But that's the whole point of the article - let the professionals worry about their business logic and how they want to handle it. We just need to develop a tool that enables them to do that.
Even though, the execution type rules are much more interesting to deal with, in web forms you mostly just need to know if the user input is OK or not. Of course, the execution type makes more sense if you'd like to execute certain actions depending on the result of the execution. For instance, you might want to display different warning messages to the different types of users or log different events or session data. But for the most part, the evaluation type is sufficient enough.
Step 1. Create a new web project. I'm going to use VS 2010 Web Application template. VS 2008 and websites can be used, too. Make sure that the project contains Default.aspx page.
Step 2. Add a reference to the downloaded Web Rule assembly CodeEffects.Rule.dll.
Step 3. Add a new class to the project. Call it Patient. This is going to be our source object. With Web Rule, you can use a plain .NET class as a source without adding any rule meta data to it. The control will find all public value typed properties of that class and use them as rule fields. By default, it assumes that the plain source object is for evaluation type rules, so it will require no action methods. But Web Rule has tons of attribute classes that you can use to specify how the engine should behave. I'm going to add some of those attributes to the Patient's members to illustrate that. It's easy to see what they are used for. You can always refer to Web Rule's documentation for details.
The most useful aspect of attributing the source, I think, is the ability to set our own DisplayName values for any field, action or in-rule method. The default names such as Patient.Work.Street look OK only to a programmer. Normal people would very much prefer the "Work Street" name :) This feature also plays a vital role in multilingual environments.
using System;
using CodeEffects.Rule.Attributes;
using CodeEffects.Rule.Common;
namespace CodeProject.FormValidation
{
[Source(RuleType.Evaluation, TrueLabel = "Checked", FalseLabel = "Not Checked")]
[Flow(FlowType.If, "Verify that")]
[SystemMethod(SystemMethod.IsEmail, DisplayName = "Is Email",
ReturnValueInputType = ValueInputType.User)]
public class Patient
{
public Guid ID { get; set; }
[Field(DisplayName = "First Name", Max = 30,
ValueInputType = ValueInputType.User)]
public string FirstName { get; set; }
[Field(DisplayName = "Last Name", Max = 30,
ValueInputType = ValueInputType.User)]
public string LastName { get; set; }
[Field(DisplayName = "Email Address", Max = 150,
ValueInputType = ValueInputType.User)]
public string Email { get; set; }
[Field(DisplayName = "Date of Birth", DateTimeFormat = "MMMM dd, yyyy",
ValueInputType = ValueInputType.User)]
public DateTime? DOB { get; set; }
[Field(ValueInputType = ValueInputType.User)]
public Gender Gender { get; set; }
[Field(Min = 0, Max = 150)]
public int? Pulse { get; set; }
[Field(Min = 0, Max = 200)]
public int? SystolicPressure { get; set; }
[Field(Min = 0, Max = 200)]
public int? DiastolicPressure { get; set; }
[Field(Min = 50, Max = 110)]
public decimal? Temperature { get; set; }
[Field(DisplayName = "Headaches Box",
ValueInputType = ValueInputType.User)]
public bool Headaches { get; set; }
[Field(DisplayName = "Allergies Box",
ValueInputType = ValueInputType.User)]
public bool Allergies { get; set; }
[Field(DisplayName = "Tobacco Box",
ValueInputType = ValueInputType.User)]
public bool Tobacco { get; set; }
[Field(DisplayName = "Alcohol Box",
ValueInputType = ValueInputType.User)]
public bool Alcohol { get; set; }
public Address Home { get; set; }
public Address Work { get; set; }
public Patient()
{
this.ID = Guid.Empty;
this.Gender = Gender.Unknown;
this.Headaches = this.Allergies =
this.Tobacco = this.Alcohol = false;
}
}
public class Address
{
[Parent("Home", "Home Street")]
[Parent("Work", "Work Street")]
[Field(Max = 50)]
public string Street { get; set; }
[Parent("Home", "Home City")]
[Parent("Work", "Work City")]
[Field(Max = 30)]
public string City { get; set; }
[Parent("Home", "Home State")]
[Parent("Work", "Work State")]
public State State { get; set; }
[Parent("Home", "Home Zip")]
[Parent("Work", "Work Zip")]
[Field(Max = 5)]
public string Zip { get; set; }
public Address() { this.State = State.Unknown; }
}
public enum Gender
{
Male,
Female,
[ExcludeFromEvaluation]
Unknown
}
public enum State
{
Arizona,
California,
Florida,
Georgia,
[ExcludeFromEvaluation]
Unknown
}
}
You can exclude any qualified member of the source object or its dependent types from the engine's UI by decorating those members with ExcludeFromEvaluationAttribute class. For example, we might not want rule authors to be able to include State.Unknown in their rules. So, it's excluded and authors won't even see that option in rule area.
Step 4. Open the Default.aspx page. Register Web Rule server control on one side of the page and add a web form to its other side. I'm not a layout masochist, so I'm going to use the TABLE tag to split the page in two parts. Yeah, I know... DIV, UL, CSS Level 3... I just don't want to waste 5-10 hours of my life just to make sure that my 20 controls look the same in each and every client :) So, the whole page looks like this (most of form's controls and styling were omitted for clarity, the attached project contains the entire markup):
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="CodeProject.FormValidation.Default" %>
<%@ Register assembly="CodeEffects.Rule" namespace="CodeEffects.Rule" tagprefix="rule" %>
<html>
<head>
<title>Test page</title>
</head>
<body>
<form id="form1" runat="server">
<table cellpadding="20" cellspacing="0" width="100%">
<tr>
<td>
<rule:AspControl ID="ruleTestControl" runat="server"
SourceAssembly="CodeProject.FormValidation"
SourceType="CodeProject.FormValidation.Patient" />
</td>
<td>
<asp:Label ID="lblInfo" runat="server" ForeColor="Red">
Create a rule, enter a new patient, click Evaluate
</asp:Label><br /><br />
<span>Patient:</span><br /><br />
<span>First Name:</span> <asp:TextBox ID="txtFirstName"
runat="server" /><br />
<span>Last Name:</span> <asp:TextBox ID="txtLastName"
runat="server" /><br />
<!---->
<asp:Button ID="btnEvaluate" runat="server" Text="Evaluate" />
<br /><br /><br />
<span>This app uses free version of Web Rule.<br />
Evaluation will be delayed for 1 second.</span>
</td>
</tr>
</table>
</form>
</body>
</html>
We didn't add the code that executes our rules yet, but this markup is all you need to build the project and start using the UI to play with the rules. It's that simple. Notice the values of the SourceAssembly and SourceType properties of Web Rule control. This is how we tell the engine which class to use as its source object. Web Rule has many more settings but only these two are required to run it.
Step 5. Let's add the code that would create an instance of the source type, fill it up with the data from the form and evaluate it against the rule that is currently on the page. To make things shorter, our code is not going to perform a format validation of the data and the page doesn't use validation controls (step 1 in the first paragraph above). I'm also going to exclude the flesh of the long GetPatient() method that converts form data into the source object (but it's present in the downloadable project).
using System;
using System.Web.UI;
using CodeEffects.Rule;
using CodeEffects.Rule.Common;
namespace CodeProject.FormValidation
{
public partial class Default : Page
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.btnEvaluate.Click += new EventHandler(this.Evaluate);
}
private void Evaluate(object sender, EventArgs e)
{
if (this.ruleTestControl.IsEmpty ||
!this.ruleTestControl.IsValid)
return;
Patient patient = this.GetPatient();
string rule = this.ruleTestControl.GetRuleXml();
Result result = Evaluator.Evaluate(rule, patient);
if (result.Success)
{
this.lblInfo.Text =
"Success. The last field that
evaluated to true: " +
result.SucceededFieldName;
}
else
{
FailedField field = result.FailedFields
[result.FailedFields.Count - 1];
this.lblInfo.Text =
string.Format(
"Failure. Field: {0}, Reason: {1}",
field.FieldName,
field.Reason);
}
}
private Patient GetPatient()
{
Patient patient = new Patient();
patient.ID = Guid.NewGuid();
return patient;
}
}
}
Believe it or not, but we got our web form and we can evaluate it against our own business rules. I think you already realized that it takes longer to read this article than to actually build the code that it describes. Of course, the real system would save the rule XML in a database as SqlServer XML data type or in file system as XML file. Web Rule supports loading existing rules and editing them which gives you ability to build a full-blown rule management system with very little effort. You can learn more about Web Rule by reading its documentation. I would like to point out just one more interesting thing about our code.
Notice the first line of the Evaluate method. It checks if the rule area is empty or if the rule is not valid and exits the method if either one is true. Any rule has very little value if it misses elements or the order of elements are not valid. Machines don't create rules - people do. Therefore, it is absolutely vital to make sure that each rule is valid and ready to be used.
Rule validation is a huge topic in all major rules engines. Often it comes as an independent component or a process that you must purchase, install and learn to use separately. Web Rule does it all for you automatically. You don't even need to write a single line of code to validate your rules. Just run the page, create an invalid rule and click the Evaluate button. Web Rule will highlight each invalid rule element. Hover your mouse over each one to see the description of the issue. You can even overwrite those descriptions with your own Help file if you don't like defaults or if your rule authors don't speak English.