Validating with Lambda Expressions

I’ve recently been working on a small library for doing validation with lambda expressions. It allows you to run a set of validators against an object, then collects the validation errors in a list. Here are some examples:

var person = new Person();
 
var validator = Validator.For(person).Rules(
	rule => rule.For(p => p.Forename).NotNull(), //Forename cannot be null
	rule => rule.For(p => p.Id).NotEqual(0) //The Id must not be equal to 0
);
 
//run the validators
bool success = validator.Validate();
 
//Collect the errors
IList<string> errors = validator.Errors;

It is also possible to chain validators together:

 //Forename cannot be null, cannot be equal to 'Foo' and must be between 1-10 characters.
Validator.For(person).Rules(
	rule => rule.For(p => p.Forename).NotNull().NotEqual("Foo").Length(1, 10)
);

If a validator fails, then it will put an error message into the errors collection. For example, the above NotEqual validator would produce an error like ‘Forename’ should not be equal to ‘Foo’. You can change the name of the field by calling the WithName method:

Validator.For(person).Rules(
	rule => rule.For(p => p.Forename).NotEqual("Foo").WithName("First Name")
);

The error would now be generated as ‘First Name’ should not be equal to ‘Foo’

You can also replace the entire error message by using WithMessage

Validator.For(person).Rules(
	rule.For(p => p.Forename).NotEqual("Foo").WithMessage("Please ensure that you have entered a sensible first name.")
);

The source code is available in my svn repository.

New Server; Moved Blog

I recently moved my blog to a virtual server purchased from 1and1. Here are some things I learned in the process:

  • Wordpress is really easy to install
  • Apache’s mod_proxy is very cool – I can use it to access both IIS and Apache through port 80
  • CssEdit is the best thing since sliced bread
  • Mark is much better at CSS than I am!
  • Setting up SMTP servers is a pain

I’ve also changed the blog’s primary URL to www.jeremyskinner.co.uk. The old blog URLs (www.jeremyskinner.me.uk and blog.jeremyskinner.me.uk) should still work.

ASP.NET Routing, DataBinding and WebForms

Update 13/09/08 I’ve posted a new version of the sample which is compatible with .NET 3.5 SP1. It can be found here.

One of the great things about ASP.NET MVC is the automatic databinding support for routing. For example, if you define a route like this:

routes.Add(new Route("{controller}/{action}/{id}", new MvcRouteHandler()) {
	Defaults = new RouteValueDictionary(new { action = "Index", id = (string)null })
});

…and you have a controller like this:

public class HomeController : Controller {
	public ActionResult Show(int id) {
		// do stuff
		return View();
	}
}

…then visiting a url like mysite.com/Home/Show/5 will automatically popuate the id parameter of the Show action with the value 5.

I thought it would be fun to see if I could get something similar working using WebForms. It actually turned out to be quite straightforward.

First, you’ll need to set up ASP.NET Routing to work with WebForms. I’m using Phil Haack’s WebFormRouting library which can be found here.

I’ve added an extension method to RouteCollection that allows me to declare routes like this:

RouteTable.Routes.MapWithAutoBinding("Customer/{CustomerId}").To("~/Customer.aspx");

In the code behind file for my customer.aspx page I can now declare a property like this:

public partial class Customer : Page {
	protected void Page_Load(object sender, EventArgs e) {
		customerNameLabel.Text = CustomerId;
	}
 
	[DataBind]
	public string CustomerId { get; set; }
}

So when I visit MySite.com/Customer/Jeremy the “CustomerId” property on the Customer.aspx page will automatically be set to “Jeremy”.

How does it work?

Calling RouteTable.MapWithAutoBinding will create a route using the AutoBoundWebFormRouteHandler which inherits from Phil Haack’s WebFormRouteHandler. After GetHttpHandler is called, reflection is used to find any properties on the page that have a DataBindAttribute.

The RequestContext is then passed to the PerformBinding method on the DataBindAttribute, which will find the appropriate value from the RouteData and invoke the property’s setter.

Taking it a stage further: using Linq to Sql

Just for fun, I thought I’d see if it was possible to automatically fetch an entity from the database based on the value in the RouteData in a similar way to Monorail’s ARDataBind attribute. I thought I’d try and use Linq to SQL as I haven’t used this in a project before.

The first stage was to inherit from the DataBindAttribute class and override the PerformBinding method. Now, instead of just passing the value from the RouteData to the property, we have to run a linq query using the value from the route data as the primary key. This is very easy to do thanks to the Dynamic Linq provider that Microsoft ship in the Visual Studio 2008 samples folder.

So, now the property on the Customer.aspx page can look like this:

[LinqBind("CustomerId")]
public Customer Cust { get; set; }

The “CustomerId” being passed into the LinqBind attribute tells the binder the name of the RouteData value it should use as the primary key for the Customer entity. Currently, this only works with single primary keys, although it should be possible to get it work with composite keys too.

Visiting mysite.com/customer/ALFKI will now run a linq query to load a customer from the Customers table using the customer id of “ALFKI” and then set the “Cust” property to be a reference to this entity.

It is also necessary to tell the LinqBindAttribute which DataContext should be used to do the fetching. This can be set in the Global.asax’s Application_Start method using a delegate:

LinqBindAttribute.DataContextFactory = () => new MyDataContext();

I’ve uploaded the code here if anyone wants to play with it.

Disclaimer: The zip file contains Phil Haack’s WebFormRouting.dll and Microsoft’s Dynamic Linq provider from the Visual Studio 2008 samples.

Twitter

The end is nigh! I have joined twitter!

LLBLGen Pro 2.6 Available

Version 2.6 of my favourite Object Relational Mapper, LLBLGen Pro, is now available.

The main addition to V2.6 is support for a full Linq provider, so writing queries in LLBLGen just became a lot easier. V2.6 also includes a new Prefetching API which I wrote during the 2.6 beta period, which Frans then included in the product.

For example, to eagerly load a Customer -> Orders relationship prior to v2.6, you’d have to do something like this:

EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>(new CustomerEntityFactory());
 
PrefetchPath2 prefetcher = new PrefetchPath2(EntityType.Customer);
prefetcher.Add(CustomerEntity.PrefetchPathOrders);
 
using(DataAccessAdapter adapter = new DataAccessAdapter()) {
	adapter.FetchEntityCollection(customers, null, prefetcher);
}

Now, with the lambda prefetching API you can do this:

using(var adapter = new DataAccessAdapter()) {
	var linq = new LinqMetaData(adapter);
 
	var customers = (from c in linq.Customers select c)
				.WithPath(path => path.Prefetch(c => c.Orders));
 
}

Likewise, if you wanted to filter the prefetched orders (eg, only return orders costing more than £10) and prefetch each order’s OrderDetail, you’d need to do something like this:

EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>(new CustomerEntityFactory());
 
PrefetchPath2 prefetcher = new PrefetchPath2(EntityType.Customer);
prefetcher.Add(CustomerEntity.PrefetchPathOrders, 0, new PredicateExpression(OrderFields.TotalCost > 10))
	.SubPath.Add(OrderEntity.PrefetchPathOrderDetail);
 
using(DataAccessAdapter adapter = new DataAccessAdapter()) {
	adapter.FetchEntityCollection(customers, null, prefetcher);
}

…while now you can do this:

using(var adapter = new DataAccessAdapter()) {
	var linq = new LinqMetaData(adapter);
 
	var customers = (from c in linq.Customers select c)
				.WithPath(path =>
					path.Prefetch<OrderEntity>(c => c.Orders)
						.FilterOn(o => o.TotalCost > 10)
						.SubPath(orderPath => orderPath.Prefetch(o => o.OrderDetail))
				);
 
}

Lambdas rock :)