Inheriting From RegularExpressionAttribute And Client Side Validation

I’ve been working with MVC2 and DataAnnotations recently and ran into a problem that I was unable to Google (or Bing) an answer too. So with some creative thinking I came up with a solution.

The Problem

We encountered some strange behaviour when we changed from using a RegularExpressionAttribute to a custom EmailAttribute that inherited from the former, as discussed by Scott Guthrie in his post on Model Validation (see step 4).

So you get the full picture, lets look at some code. Start off by creating a new MVC2 web application. To this add a class called Person that resembles the following:

    public class Person
    {
		[Required]
		public string Firstname { get; set; }
    
		[Required]
		public string Surname { get; set; }
    
		[Required]
		public string PreferredName { get; set; }
    
		[Required]
		[RegularExpression("^[a-zA-Z0-9_\+-]+(\.[a-zA-Z0-9_\+-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.([a-zA-Z]{2,4})$", ErrorMessage = "Not a valid email")]
		public string Email { get; set; }
    }

Next we add a PersonController and to that a Create method.

    public ActionResult Create() { return View(); }

After some other wiring up we run it up and see something like

Clicking the Create button will result in

If we now enter some text into the Email field the RegularExpressionAttribute we added will kick in and we getting the appropriate error message.

Now, to follow the DRY principle we should move the regular expression to a more descriptive object. So we introduce the EmailAttribute.

     [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
     public class EmailAttribute : RegularExpressionAttribute
     {
     public EmailAttribute() : base("^[a-zA-Z0-9_\+-]+(\.[a-zA-Z0-9_\+-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.([a-zA-Z]{2,4})$") { }
     }

And we update the Email property on our Person class.

     [Required]
     [Email(ErrorMessage="Please enter a valid email address.")]
     public string Email { get; set; }

After a rebuild and a reload of the page. Clicking Create will still result in the required message. However after entering text into the Email field we see that no validation message is displayed. Why not?

Okay, so after some searching we find that Phil Haacked has a solution. The problem is that the client side script is no longer being generated. In order to get this working again we need to add a custom validator for the new attribute. So let’s add that

    public class EmailValidator : DataAnnotationsModelValidator<EmailAttribute>
     {
     private readonly string _message;
     private readonly string _pattern;
    
    public EmailValidator(ModelMetadata metadata, ControllerContext context, EmailAttribute attribute) : base(metadata, context, attribute)
     {
     _pattern = attribute.Pattern;
     _message = attribute.ErrorMessage;
     }
    
    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
     {
     var rule = new ModelClientValidationRule
     {
     ErrorMessage = _message,
     ValidationType = "email"
     };
    
    rule.ValidationParameters.Add("pattern", _pattern);
    
    return new[] { rule };
     }
     }

We then need to register with the validator.

     protected void Application_Start()
     {
     AreaRegistration.RegisterAllAreas();
     RegisterRoutes(RouteTable.Routes);
     DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAttribute), typeof(EmailValidator));
     }

Now after a rebuild, clicking Create once more will result in the expected required field message. Now if we enter text into the Email field we still don’t see our email validation message appear. Argh!

What Next?

After digging around, removing my custom validator and reverting to the standard RegularExpressionAttribute I found that the javascript being generated was key to the solution. Here’s a snippet of what is being generated when using the RegularExpressionAttribute - I’ve taken the liberty of formatting the code to make it more readable.

     if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
     window.mvcClientValidationMetadata.push({
     "Fields":[
     {"FieldName":"Firstname","ReplaceValidationMessageContents":true,"ValidationMessageId":"Firstname_validationMessage","ValidationRules":[{"ErrorMessage":"The Firstname field is required.","ValidationParameters":{},"ValidationType":"required"}]},
     {"FieldName":"Surname","ReplaceValidationMessageContents":true,"ValidationMessageId":"Surname_validationMessage","ValidationRules":[{"ErrorMessage":"The Surname field is required.","ValidationParameters":{},"ValidationType":"required"}]},
     {
     "FieldName":"Email",
     "ReplaceValidationMessageContents":true,
     "ValidationMessageId":"Email_validationMessage",
     "ValidationRules":[
     {
     "ErrorMessage":"Not a valid email",
     "ValidationParameters":{
     "pattern":"^[a-zA-Z0-9_\+-]+(\.[a-zA-Z0-9_\+-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.([a-zA-Z]{2,4})$"
     },
     "ValidationType":"regularExpression"
     },
     {
     "ErrorMessage":"The Email field is required.",
     "ValidationParameters":{},
     "ValidationType":"required"
     }
     ]
     }
     ],
     "FormId":"form0",
     "ReplaceValidationSummary":false,
     "ValidationSummaryId":"validationSummary"
     });

And here’s what is generated with the EmailAttribute.

     if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
     window.mvcClientValidationMetadata.push({
     "Fields":[
     {"FieldName":"Firstname","ReplaceValidationMessageContents":true,"ValidationMessageId":"Firstname_validationMessage","ValidationRules":[{"ErrorMessage":"The Firstname field is required.","ValidationParameters":{},"ValidationType":"required"}]},
     {"FieldName":"Surname","ReplaceValidationMessageContents":true,"ValidationMessageId":"Surname_validationMessage","ValidationRules":[{"ErrorMessage":"The Surname field is required.","ValidationParameters":{},"ValidationType":"required"}]},
     {
     "FieldName":"Email",
     "ReplaceValidationMessageContents":true,
     "ValidationMessageId":"Email_validationMessage",
     "ValidationRules":[
     {
     "ErrorMessage":"Not a valid email",
     "ValidationParameters":{
     "pattern":"^[a-zA-Z0-9_\+-]+(\.[a-zA-Z0-9_\+-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.([a-zA-Z]{2,4})$"
     },
     "ValidationType":"email"
     },
     {
     "ErrorMessage":"The Email field is required.",
     "ValidationParameters":{},
     "ValidationType":"required"
     }
     ]
     }
     ],
     "FormId":"form0",
     "ReplaceValidationSummary":false,
     "ValidationSummaryId":"validationSummary"
     });

See the difference? I’ll give you a hint, look at the ValidationType property of the first ValidationRule. For the EmailAttribute it says “email”, whereas for a RegularExpressionAttribute is is “regularExpression”. What happens if we change the value in the EmailValidator?

     if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
     window.mvcClientValidationMetadata.push({
     "Fields":[
     {"FieldName":"Firstname","ReplaceValidationMessageContents":true,"ValidationMessageId":"Firstname_validationMessage","ValidationRules":[{"ErrorMessage":"The Firstname field is required.","ValidationParameters":{},"ValidationType":"required"}]},
     {"FieldName":"Surname","ReplaceValidationMessageContents":true,"ValidationMessageId":"Surname_validationMessage","ValidationRules":[{"ErrorMessage":"The Surname field is required.","ValidationParameters":{},"ValidationType":"required"}]},
     {
     "FieldName":"Email",
     "ReplaceValidationMessageContents":true,
     "ValidationMessageId":"Email_validationMessage",
     "ValidationRules":[
     {
     "ErrorMessage":"Not a valid email",
     "ValidationParameters":{
     "pattern":"^[a-zA-Z0-9_\+-]+(\.[a-zA-Z0-9_\+-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.([a-zA-Z]{2,4})$"
     },
     "ValidationType":"regularExpression"
     },
     {
     "ErrorMessage":"The Email field is required.",
     "ValidationParameters":{},
     "ValidationType":"required"
     }
     ]
     }
     ],
     "FormId":"form0",
     "ReplaceValidationSummary":false,
     "ValidationSummaryId":"validationSummary"
     });

So after setting the EmailAttribute’s ValidationType to “regularExpression” the correct validation JavaScript is once again generated and client side validation now works! Hopefully this saves you some time, once again if anyone knows another way please comment.

Edit

Here’s the final version of EmailValidtor

    public class EmailValidator : DataAnnotationsModelValidator<EmailAttribute>
     {
     private readonly string _message;
     private readonly string _pattern;
    
    public EmailValidator(ModelMetadata metadata, ControllerContext context, EmailAttribute attribute) : base(metadata, context, attribute)
     {
     _pattern = attribute.Pattern;
     _message = attribute.ErrorMessage;
     }
    
    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
     {
     var rule = new ModelClientValidationRule
     {
     ErrorMessage = _message,
     ValidationType = "regularExpression"
     };
    
    rule.ValidationParameters.Add("pattern", _pattern);
    
    return new[] { rule };
     }
     }

Working with Entity Framework 4 and MS SQL Spatial Types

I’ve been involved in a project at work recently that required the ability to maintain records that contained spatial information. The ORM of choice for the project was Entity Framework 4. During the build I ran into several problems. In this post I’ll detail what barriers I found, the options available to work around them and the solution I chose.

What we intend to build here is an editor, so first things first, let’s create a table to hold our data.

CREATE TABLE [dbo].[Dinners](
	[DinnerId] [int] NOT NULL,
	[Title] [nvarchar](50) NOT NULL,
	[Location] [geography] NOT NULL
)

The next step is to create a VS2010 project, to which we’ll add a class to model our Dinners table.

using System;

namespace JB.DinnersWithSpatial.Core
{
	public class Dinner
	{
		public int DinnerId { get; set; }
		public string Title { get; set; }
		public byte[] Location { get; set; }
	}
}

Next we’ll add an ADO.NET Entity Data Model called DinnersModel.edmx. This will kick start a wizard that will ask you about database connectivity and which entities from the database you want to include in your model. Be sure to grab the Dinners table. After you click finish the model should be visible, but wait where is the Location property? Checking the warnings shows the following:

warning 6005: The data type 'geography' is not supported; the column
'Location' in table 'JBExamples.dbo.Dinners' was excluded.

Well that’s just not cricket! So EF4 doesn’t support spatial types, time for a workaround. After searching the deepest darkest corners of the internet we came across a simple solution using a view. So now we need to create a view to represent our Dinners table.

CREATE VIEW [dbo].[vw_DinnersAsBinary]
AS
SELECT [DinnerId], [Title], CONVERT(VARBINARY(MAX), [Location], 0) AS [Location]
FROM [dbo].[Dinners]

With that done we need to edit the DinnersModel.edmx. The entity for Dinners should be replaced with the View we just created. Once the entity representing the View is added we need to open its properties and rename it to Dinner (the Entity Set Name should be Dinners). After saving you may notice that there is a new message in the errors list:

The table/view 'dbo.vw_DinnersAsBinary' does not have a primary key defined.
The key has been inferred and the definition was created as a read-only
table/view.

EF4 is telling us that since Views don’t support primary keys it has helped us by creating one, however the one it defines is not correct. The only property that should have Entity Key setting equal to true is DinnerId, so lets update the others. With that done we can move onto the repository plumbing.

Setting up for POCOs

In order to get our clean class to be used we need to turn off the code generation that is being done. To do this view the properties of DinnersModel.edmx and remove the value for Custom Tool. Once deleted the .cs file should disappear.

Now when we try to insert a new record we get another error.

Update or insert of view or function 'dbo.vw_DinnersAsBinary' failed because
it contains a derived or constant field.

This rabbit hole is getting deeper and deeper and we’ve just hit a fork in the tunnel. We have two options here. Either create CRUD stored procedures or use Instead Of triggers.

Approach 1 - Stored Procedures

This approach means creating the CRUD procedures, opening your mapping file and adding those procedures to it. Once that is done you need to go into your repository implementation (or context) and modify the code to call the procedures instead.

I’m not a huge fan of this approach because there is the need to map extra objects from the DB and extra code to write.

Approach 2 - Instead Of Triggers

The second approach is to use Instead Of triggers. These types of trigger allow you to intercept an action. So taking our insert statement above we could employ an Instead Of trigger to intercept the action from the view and perform the insert correctly.

First we need to modify the edmx so that EF4 thinks the view is a table. To do this we need to open the edmx with the XML editor. Somewhere near the top of the file will be the definition of vw_DinnersAsBinary, you’ll know if because it will contain a DefiningQuery element. Here’s what mine looked like.

<EntitySet Name="vw_DinnersAsBinary" EntityType="DinnersModel.Store.vw_DinnersAsBinary" store:Type="Views" store:Schema="dbo" store:Name="vw_DinnersAsBinary">
	<DefiningQuery>SELECT
		[vw_DinnersAsBinary].[DinnerId] AS [DinnerId],
		[vw_DinnersAsBinary].[Title] AS [Title],
		[vw_DinnersAsBinary].[Location] AS [Location]
		FROM [dbo].[vw_DinnersAsBinary] AS [vw_DinnersAsBinary]
	</DefiningQuery>
</EntitySet>

After making the changes it now looks like this

<EntitySet Name="vw_DinnersAsBinary" EntityType="DinnersModel.Store.vw_DinnersAsBinary" store:Type="Tables" Schema="dbo" />

Now we add our INSERT trigger. Note the use of the STPointFromWKB method is used to convert the incoming binary stream into a SQL spatial object.

CREATE TRIGGER [dbo].[trg_vw_DinnersAsBinary_Insert]
ON [dbo].[vw_DinnersAsBinary]
INSTEAD OF INSERT
AS
BEGIN
	INSERT INTO [dbo].[Dinners](DinnerId, Title, Location)
	SELECT DinnerId, Title, geography::STPointFromWKB((SELECT Location FROM inserted), 4326)
	FROM inserted
END

I like this approach because there is no need to map extra objects from the DB and no extra code to write. Supporting other SRIDs might prove an issue, however I don’t think solving that is too hard.

Workarounds suck

The down side to all of this is that without native support for spatial types in EF4 you cannot use the spatial querying that MS SQL provides and when MS does support spatial types in EF you’ve got code changes to make (if you decide to).

If anyone knows any other ways of doing this please post, I’d love to hear about them.

An NUnit wrapper for WebAssert

Early in April 2010 Damian Edwards released his WebAssert library onto CodePlex. The library allows for writing assertion unit tests so that HTML and CSS can be validated either by submitting to the W3C validator or to a custom validation service. The release is made up of 3 libraries; the WebAssert core, a unit test project for the core project and a wrapper around the core for use in projects that use the MS unit testing framework.

At work we currently use NUnit as our unit testing framework, so the MS test wrapper would not work for us. On reviewing what the WebAssert library did I thought about writing a wrapper for NUnit and thankfully Damian made it easy. Using the source for the MS unit testing framework wrapper as a guide I built a wrapper library for NUnit. I had to make a couple of changes, first remove the methods that refer to GetAspNetServerUrl as they work off a TestContext object that as far as I’m aware doesn’t exist in NUnit, the other change needed was the type of exception thrown by the Fail method. The new exception is a NUnit AssertionException.

private static void Fail(W3CValidityCheckResult result)
{
	var errors = result.ErrorsCount == 1 ? "error" : "errors";
	var warnings = result.WarningsCount == 1 ? "warning" : "warnings";
	
	throw new AssertionException(
		String.Format("The passed HTML is invalid: {0} {1}, {2} {3}",
		result.ErrorsCount, errors, result.WarningsCount, warnings)
	);
}

After a build I was then able to reference the wrapper library in a testing library for another project and write some WebAssert unit tests like these:

using System;
using NUnit.Framework;
using WebAssertTestLibrary.NUnit;

namespace WebAssertDemoNUnitTests
{
	[TestFixture()]
	public class DemoTests
	{
		[Test()]
		public void ValidHtmlAtUrl()
		{
			WebAssert.ReturnsValidHtml("http://localhost:49417/");
		}

		[Test()]
		public void ValidHTML()
		{
			WebAssert.IsValidHtml("<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head><title>Test HTML</title></head><body><p>lorem</p></body></html>");
		}
	}
}

When run with NUnit GUI all tests validated:

WebAssert Demo NUnit Tests

One limitation I found was getting enough information about what didn’t validate. Using Fiddler I found that there are no headers in the response describing what the errors or warnings are. So maybe there is a case for adding an HTML parser to the core library so that the validation response can be pulled apart and added to the exception thrown by the Fail method.

EDIT: Damian’s comment below shows that future project updates will allow for the use of the SOAP API for the W3C validation service meaning that more information about errors in validation will appear in exceptions from test failures. Nice.