Linq to Sql and ASP.NET MVC - DataContext Per Request

This is the first in a series of posts about using Linq to Sql with ASP.NET MVC.

Code for this series is available here.

When using an ORM tool within a web application, it's often common to scope a unit of work to the lifetime of an HTTP Request. If you're using Linq to Sql and ASP.NET MVC, you can achieve this by using an Inversion of Control container in conjunction with an ActionFilter.

For this example, I'm going to be using the StructureMap IoC container alongside a fictional "Blog" database.

Firstly, you'll need to configure StructureMap by calling ObjectFactory.Configure inside your Global.asax passing in a custom Registry instance:

protected void Application_Start() {
	RegisterRoutes(RouteTable.Routes);
 
	ObjectFactory.Configure(cfg => {
		cfg.AddRegistry(new MyRegistry());
	});
}

The code for MyRegistry looks like this:

public class MyRegistry : Registry {
	public MyRegistry() {
		For<BlogDataContext>()
			.HttpContextScoped()
			.Use(c => new BlogDataContext());
 
		Scan(scan => {
			scan.AddAllTypesOf<Controller>();
		});
	}
}

Here I'm telling StructureMap to create one instance of my BlogDataContext per HTTP Request as well as registering each Controller instance with the container.

Next, we need to tell MVC to use StructureMap to instantiate our controllers. This can be done by creating a custom ControllerFactory:

public class StructureMapControllerFactory : DefaultControllerFactory {
	protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) {
		return (IController) ObjectFactory.GetInstance(controllerType);
	}
}

We can then replace the DefaultControllerFactory with the StructureMapControllerFactory in our Application_Start:

protected void Application_Start() {
	RegisterRoutes(RouteTable.Routes);
 
	ObjectFactory.Configure(cfg => {
		cfg.AddRegistry(new MyRegistry());
	});
 
	ControllerBuilder.Current.SetControllerFactory(
		new StructureMapControllerFactory());
}

Now, if we create a PostController (for creating and editing Posts in our fictional blog), we can now take a BlogDataContext in the constructor (for better testability, you'd probably want to hide the DataContext behind an interface):

public class PostController : Controller {
	private readonly BlogDataContext context;
 
	public PostController(BlogDataContext context) {
		this.context = context;
	}
 
	public ActionResult Edit(int id) {
		var post = context.Posts.Single(x => x.Id == id);
		return View(post);
	}
}

Now, visiting http://mysite/Post/Edit/1 would return a view to display a post with the Id of 1 (assuming the appropriate Post exists in the database).

Automatically Submitting Changes

We can take this a stage further by adding an ActionFilter to our application that will automatically call SubmitChanges on our DataContext at the end of the HTTP Request:

public class AutoCommitAttribute : ActionFilterAttribute {
	public override void OnActionExecuted(ActionExecutedContext filterContext) {
		if (filterContext.Controller.ViewData.ModelState.IsValid) {
			var currentDataContext = ObjectFactory.GetInstance<BlogDataContext>();
 
			using (var transaction = new TransactionScope()) {
				currentDataContext.SubmitChanges();
				transaction.Complete();
			}
		}
	}
}

Here we call into StructureMap to retrieve our current DataContext and commit any changes back to the database inside a transaction. Note that this will only happen if the ModelState is valid (ie there are no validation errors).

Another thing to keep in mind that using an IoC container as a Service Locator (as we're doing in this filter) is generally considered bad practice. There are ways around this but these are outside the scope of this post.

Now, whenever we make a change to one of our entities the change will automatically be committed to the database provided the action is decorated with the AutoCommit attribute:

[AcceptVerbs(HttpVerbs.Post), AutoCommit]
public ActionResult Create(Post post) {
	context.Posts.InsertOnSubmit(post);
	return RedirectToAction("Index");
}
Written on January 31, 2010