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.