Print

Integrating Enterprise Library Validation Application Block with LINQ to SQL and Entity Framework Part 2: Using context within custom validators.

This article describes how to write custom validators for the Enterprise Library Validation Application Block, in such a way that it runs within the context of a specific LINQ to SQL DataContext or Entity Framework ObjectContext class, which allows analysis of non-persisted (in-memory) changes in that context.

The Enterprise Library Validation Application Block, or VAB for short, is great for those simple validations like checking for null strings or checking for a number to fall within a specified range. However, I bet you do, like me, far more complex validations in your enterprise applications. It's not uncommon for me in having to query the database during validation. Let's make it clear, in the context of O/RM frameworks, those types of validations tend to get pretty complicated very quickly. This is because you will have to deal with both in-memory state and database state and perhaps even deal with the possibility of multiple users changing data at the same time, while preventing invalid state in the database. Nevertheless, when the enterprise architecture prescribes the use of an O/RM and VAB, having the ability to use your O/RM framework within your custom validators might simply be necessarily.


So why exactly is this a problem with Validation Application Block? The VAB wasn’t build with O/RM technologies in mind. Therefore it lacks the possibility to supply a context to the validation process. Some O/RM frameworks allow the context to be retrieved from the entity itself, but the LINQ to SQL and Entity Framework API’s do not allow this (which is good in my opinion, because I believe an entity should be unaware of the context in which it’s defined). This however leads to a situation where more complex O/RM driven validations can not be performed with VAB out of the box.

While it’s possible to create a new context within a custom validator and use it to query the database, this prevents you from accessing any non-persisted changes that exist in the original context that influence the validation process.

Before explaining how to supply a context to a custom validator, here’s an example of one. A custom (configuration based) validator requires a couple of things, such as defining a special constructor and a class level attribute, but it mainly comes down to overriding the protected DoValidate method.

using System;
using System.Collections.Specialized;

using CuttingEdge.Conditions; // http://conditions.codeplex.com

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Configuration;

[ConfigurationElementType(typeof(CustomValidatorData))]
public sealed class CustomerValidator : Validator
{
public CustomerValidator(NameValueCollection attributes)
: base(string.Empty, string.Empty)
{
}

protected override string DefaultMessageTemplate
{
get { throw new NotImplementedException(); }
}

protected override void DoValidate(object objectToValidate,
object currentTarget, string key, ValidationResults results)
{
Condition.Requires(currentTarget, "currentTarget")
.IsOfType(typeof(Customer));

Customer customerToValidate = (Customer)currentTarget;

// TODO: Validate the customer here

// When the customer is invalid, we log that.
this.LogValidationResult(results,
"The customer is invalid", currentTarget, key);
}
}

UPDATE 2010-01-07: I forgot to mention how the VAB configuration would look like. So, to be complete, here is the VAB configuration used to get this working (I stripped namespaces for readability):

  <validation>
<type name="Customer" defaultRuleset="Default" assemblyName="Domain">
<ruleset name="Default">
<validator type="CustomerValidator, Validators"
name="Customer Validator" />
</ruleset>
</type>
</validation>

Please note that when using the Enterprise Library Configuration tool, you should add the validator to the "Self" node of that particular type.

The custom validation logic for the customer goes in the DoValidate method, but the method lacks a context parameter. The contract for this method is given by the base class and this means that there is no way to operate within the context in which the validated customer is created.

While it is possible to create the context once during the lifetime of your application, store it in a public static field and let the DoValidate method use that instance, I strongly discourage you from doing this. The LINQ to SQL DataContext and Entity Framework ObjectContext classes are not thread-safe and follow the unit of work pattern. Using them this way in a multi-threaded architecture such as ASP.NET will do horrible things (multiple users saving each others changes for instance). But even in a single user Windows Forms application I recommend not doing this.

Needed is a mechanism that supplies validators with the context that is in scope, much like the TransactionScope does with transactions. Yes, we need .NET’s thread-local storage. Let us inspire ourselves by the TransactionScope class and create a ContextScope class that allows access to the thread’s current context:

using System;

using CuttingEdge.Conditions;

public sealed class ContextScope : IDisposable
{
private const string ScopeAlreadyActiveExceptionMessage =
"A ContextScope has already been declared within " +
"the current scope. Only one scope can be active.";

private const string NoScopeActiveExceptionMessage =
"A ContextScope hasn't been declared within the " +
"current scope. This property can only be called " +
"within the context of a ContextScope.";

[ThreadStatic]
private static object currentContext;

public ContextScope(object context)
{
Condition.Requires(context, "context").IsNotNull();

if (currentContext != null)
{
throw new InvalidOperationException(
ScopeAlreadyActiveExceptionMessage);
}

currentContext = context;
}

public static object CurrentContext
{
get
{
object context = currentContext;

if (context == null)
{
throw new InvalidOperationException(
NoScopeActiveExceptionMessage);
}

return context;
}
}

public void Dispose()
{
currentContext = null;
}
}

As you can see, the ContextScope class is very simple. A created instance functions as a wrapper around the thread static field. The static CurrentContext property can now be used to retrieve the context. The rest of the code is error checking to ensure a scope isn’t defined twice and to ensure the CurrentContext property doesn’t return null when no context is registered.

Below is the EntityValidator class from my previous post, adjusted in such a way that it uses the ContextScope.

using System.Collections.Generic;
using System.Linq;

using CuttingEdge.Conditions;

using Microsoft.Practices.EnterpriseLibrary.Validation;

static class EntityValidator
{
public static void Validate(object context,
IEnumerable<object> entities)
{
Condition.Requires(context, "context").IsNotNull();
Condition.Requires(entities, "entities").IsNotNull();

// Define the scope for custom validators to use.
using (new ContextScope(context))
{
// Get a list of ValidationResults, one for each
// invalid object.
ValidationResults[] invalidResults = (
from entity in entities
let type = entity.GetType()
let validator =
ValidationFactory.CreateValidator(type)
let results = validator.Validate(entity)
where !results.IsValid
select results).ToArray();

// Throw an exception when there are invalid results.
if (invalidResults.Length > 0)
{
throw new ValidationException(invalidResults);
}
} // end using
}
}

As you can see, little has changed to the Validate method. A context argument was added to the definition and within the body of the method the original code was wrapped with a using statement that contains the creation of a ContextScope instance. Because the definition of Validate changed, the calling code also has to change, as shown below (note that I give value types a different color, so that they're easy to spot):

// Changes to LINQ to SQL’s NorthwindDataContext class.
public override void SubmitChanges(ConflictMode failureMode)
{
// Just add the ‘this’ keyword as first argument.
EntityValidator.Validate(this, this.GetChangedEntities());

base.SubmitChanges(failureMode);
}

// Changes to Entity Framework’s NorthwindEntities class.
partial void OnContextCreated()
{
// Again, just add the ‘this’ keyword as first argument.
this.SavingChanges += (sender, e) =>
EntityValidator.Validate(this, this.GetChangedEntities());
}

And finally, within the custom validator, we can retrieve the context using the ContextScope.CurrentContext property as follows:

protected override void DoValidate(object objectToValidate,
object currentTarget, string key, ValidationResults results)
{
Condition.Requires(currentTarget, "currentTarget")
.IsOfType(typeof(Customer));

Customer customerToValidate = (Customer)currentTarget;

// Retrieve the Entity Framework ObjectContext
// from the context scope.
NorthwindEntities context =
(NorthwindEntities)ContextScope.CurrentContext;

// TODO: Validate the customer here

// When the customer is invalid, we log that.
this.LogValidationResult(results,
"The customer is invalid", currentTarget, key);
}

While VAB doesn’t support context based custom validators out of the box, as I’ve shown here, implementing support for this isn’t that hard. But as I stated in the beginning of this post, from here on it can get pretty complicated very quickly.

In part three, I’ll show an example of how complicated this can get and in part four I'll show how to use the metadata, generated by LINQ to SQL, to automate validations.

Happy validating!

- .NET General, C#, Enterprise Library, Entity Framework, LINQ, LINQ to SQL, O/RM, Validation Application Block - two comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

two comments:

Steven, I have tried implementing your solution as above, but for some reason my custom validator never gets called. The code executes as expected up until the CreateValidator is called. I see that my entity is in the context, and so I would have expected the ValidationFactory to return the custom validator. Instead, it just returns happily with nof failures, but it doesn't hit my validator (I've got breakpoints in my custom validator code and they don't get hit).

Perhaps I am missing a step? I added my type being validated to the Validation Block in the App.Config file on my project, added a property, and added the custom validator to that. Is that all I need to link it all together?

Thanks,

Chris
Chris - 06 01 10 - 19:51

Steven, I just re-read your article and I notice that you added a note about how the custom validator must be added to the "Self" node for that type, and that is exactly the thing I was missing! It all works wonderfully now.

Thanks very much for your excellent articles!
Chris - 10 01 10 - 11:34


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.