Modern ASP.NET Web Forms Development – Dependency Injection

Jeffrey Fritz

Puzzle PieceWe’ve all read various ‘best practices’ posts about this framework and that framework from expert developers in the community.  They’ll cover topics regarding how to make your application more maintainable and how to drive down the risk of maintenance in your applications.  A common design recommendation is to structure your applications so that you compose the objects you are working on.  The MVC framework is a good example that demonstrates this technique.  You compose controller objects in MVC using constructor dependency injection so that the other facilities that our controllers need like loggers and database access are managed in other classes.  What do we do if we’re working on an ASP.NET Web Forms application?  The Page object doesn’t allow you to use constructor injection, so how do we work around this limitation?

In this series of posts, we’re going to look at how to modernize the development of your ASP.NET Web Forms application using existing features in innovative ways.  Along the way, we will also learn how to reduce risk while maintaining our long standing applications.

Dependency Injection Primer

For those unfamiliar with the concept, dependency injection is a class design strategy that relocates logic from one class to another. The second class is then injected into the first through either a property or a constructor argument.  As an example, we can move all database management logic to a repository class or all log management tasks to a logger class.  Our application code can be structured so that it focuses more on the application logic and only pulling in these concerns when needed.

The following code demonstrates how you can inject a repository object that is defined by an interface, ICustomerRepository, into a Controller class for use in an MVC action method:

public interface ICustomerRepository {

  IEnumerable<Customer> GetAll();

  void Save(Customer c);

  void Delete(int customerId);

}

public class CustomerController : Controller {

  public ICustomerRepository Repository { get; }

  public CustomerController(ICustomerRepository repo) {

    Repository = repo;

  }

  public IActionResult Index() {

    return View(Repository.GetAll());

  }

}

An inversion of control container can be configured to automatically create the CustomerController and pass in an appropriate class that implements the ICustomerRepository interface.

Inversion of Control Containers

An Inversion of Control (IOC) container is an object in your code that manages the construction and object lifetime of objects that can be automatically injected into your classes for use.  The code you are writing in the client classes, like the CustomerController above, calls into the classes that do most of the work for you.  This inversion of control is facilitated by a container object.  There are many containers available on NuGet.org for you to choose and add to your projects including:

For this article, we’re going to work with Autofac and add its capabilities to a Web Forms application.

Web Forms and Dependencies

ASP.NET Web Forms has a long history of managing code in a collection of code-behind files named something like “EditCustomer.aspx.cs” that contain a partial class.  This is the editable part of the class, as there is also a “EditCustomer.designer.aspx.cs” class that contains some generated code for the framework to use in managing the user-interface.  If we would like to add dependencies to a page, say a reference to a CustomerRepository, we would typically create the CustomerRepository in the constructor of the EditCustomer class and then use it appropriately later in the class.

This leads to some code management problems as we are then creating all of the dependent objects that we need to build and service our pages in the constructor of every page.

If those objects change or if we wanted to use a different implementation of our interface, we’re going to have a lot of code to update and test to ensure that it still functions the same way.  We can start to simplify this problem by introducing an abstract BasePage concept to move some of these common concerns out into a reusable class that other pages can inherit from as well:

We still have a page level concern for the CustomerRepository, but perhaps that’s just a one-off problem for this page.  Or maybe it isn’t…

Using a Container with Web Forms

Out of the box, you can’t add parameters to any of your code-behind file’s constructors.  Not only that, but you have no way to plug in a container to set property values either.  There is a way around this, and it involves using an HttpModule.  An HttpModule is a class that can intercept events in the ASP.NET pipeline and interact with the input, output, and other processes managing the pipeline.  In this case, we’re going to write a simple HttpModule to inspect a Page object and inject required properties or constructor arguments as needed.

In this sample, we’re going to use Autofac to power our module.  After installing the Autofac NuGet package, you can create a new class that implements the IHttpModule interface.  The IHttpModule defines two methods that you must implement: Init and Dispose.  We can use the Init method to listen for the PreRequestHandlerExecute event, the event that is triggered after a Page is constructed and before it begins any Page-level events processing.  Let’s also add a static constructor to configure the Autofac container:

With some additional reflection work inside of our event handler method, we can inject arguments for the constructor method and execute that constructor.  This is a little strange, as we’re used to executing constructor methods when calling “new FooClass()”.  In this case, the class is already instantiated and we are triggering the constructor method after the fact.

Structuring a Page

To set up a page to be injected, we can add public properties of types that Autofac knows how to handle or we can create a public constructor method that accepts arguments.  If we use the constructor injection technique, we need to ensure that a protected argument-less constructor method is available for the framework to use when it creates the Page object.

Final Configuration

The last piece of configuration needed to ensure that our AutofacModule executes in the ASP.NET event pipeline is to add an entry appropriately to web.config:

Summary

We’ve taught an old dog a new trick.  This is sample code only, and may not be optimized for the best performance for your application in production.  Some of the containers, like Autofac, have their own modules available for you to use if you want to employ this technique with your application.  Is this something that you find valuable?  Should we make investments in this type of “modernization” of the web forms templates and architecture?  Let us know in the comments below. Thanks to MVP and Regional Director Miguel Castro for helping with the content of this article.

1 comment

Discussion is closed. Login to edit/delete existing comments.

Feedback usabilla icon