Real-World Inversion of Control and Dependency Injection with WebForms (This Is Not a Drill)

Posted on November 20, 2009   |   Download sample code

2 comments

An exercise in refactoring a real-world WebForm to a more maintainable and testable one.

Suppose we have a Contact Us page. Old school, with all the logic in code-behind. I borrowed it from an old project, but I’ve written plenty of those myself over the years, and I’m sure so have you.

public partial class Contact1 : Page
{
 private void SubmitFeedback (object sender, EventArgs e)
 {
  if (!Page.IsValid)s
   return;

  string subject;
  string to, from;
  MailMessage message;
  StringBuilder body;

  subject = Subject.Text;

  body = new StringBuilder ();
  body.Append ("Hello!" + "<br /><br />");
  body.Append ("Thank you for stopping by at Acme.com.");
  body.Append ("We will get back to you shortly." + "<br /><br />");
  body.Append ("Sincerely," + "<br /><br />");
  body.Append ("The Acme Team" + "<br /><br />");
  body.Append ("NOTE: Please do not reply to this message.<br />");

  from = ConfigurationManager.AppSettings["SupportEmail"];
  message = new MailMessage ();

  message.From = new MailAddress (from);
  message.To.Add (new MailAddress (Email.Text));
  message.Subject = subject;
  message.Body = body.ToString ();
  message.IsBodyHtml = true;

  SmtpClient client = new SmtpClient ();
  client.Send (message);

  // ------------------------------------

  to = ConfigurationManager.AppSettings["SupportEmail"];
  subject = Subject.Text;

  body = new StringBuilder ();
  body.Append ("First Name: " + FirstName.Text.Trim () + "<br />");
  body.Append ("Last Name: " + LastName.Text.Trim () + "<br />");
  body.Append ("Email: " + Email.Text.Trim () + "<br />");
  body.Append ("Comment: " + Comment.Text.Trim () + "<br />");
  body.Append ("<br />");
     
  message = new MailMessage ();

  message.From = new MailAddress (Email.Text);
  message.To.Add (new MailAddress (to));
  message.Subject = subject;
  message.Body = body.ToString ();
  message.IsBodyHtml = true;

  client = new SmtpClient ();
  client.Send (message);

  Response.Redirect ("~/ThankYou.aspx");
 }

 protected override void OnInit (EventArgs e)
 {
  base.OnInit (e);
  SubmitContact.Click += SubmitFeedback;
 }
}

This is the kind of code we’ve come to loathe and this is why WebForms have a bad reputation.

We have too many concerns here:

  1. The code knows it lives on a web page. It knows the page has controls, knows their types; can reach out and grab the values of any of them.
  2. The code knows how to format an email. I’m not even going to touch this. It’s a painful area—HTML or not, personalizable or not, etc.
  3. The code knows the business logic of creating two emails: one to the submitter, and one to the company’s support department.
  4. The code knows how to construct and email and how to send it.
  5. The code knows where to find configuration settings and how to read them.

This code is begging for refactoring.

Here are my goals for this exercise:

  1. Increase cohesion: classes should be highly focused on few, related responsibilities.
  2. Reduce coupling: I don’t want classes to know about each other’s implementation. Ideally, I’d like them talking through interfaces.
  3. Put as much code under test as possible, which the previous two points help happen.

Refactoring the code

It genuinely surprises me how many people cannot define refactoring during interviews. The idea is simple, really:

“Refactoring” source code means improving it without changing its overall results. The process could be informally referred to as “cleaning up” or “taking out the garbage.” Refactoring neither fixes bugs nor adds new functionality, though it might precede either activity. Rather, it improves the understandability of the code, changes its internal structure and design, and removes dead code.

Here’s what the same page may look like after some reorg:

public partial class Contact2 : Page
{
 private void SubmitFeedback (object sender, EventArgs e)
 {
  if (!Page.IsValid)
   return;

  SendConsumerEmail ();
  SendSupportEmail ();

  Response.Redirect ("~/ThankYou.aspx");
 }

 private void SendConsumerEmail ()
 {
  string subject = Subject.Text;

  body = new StringBuilder ();
  body.Append ("Hello!" + "<br /><br />");
  body.Append ("Thank you for stopping by at Acme.com.");
  body.Append ("We will get back to you shortly." + "<br /><br />");
  body.Append ("Sincerely," + "<br /><br />");
  body.Append ("The Acme Team" + "<br /><br />");
  body.Append ("NOTE: Please do not reply to this message.<br />");

  string from = ConfigurationManager.AppSettings["SupportEmail"];
  
  new EmailManager ()
   .SendEmail (Email.Text, from, subject, body.ToString ());
 }

 private void SendSupportEmail ()
 {
  string to = ConfigurationManager.AppSettings["SupportEmail"];
  string subject = Subject.Text;

  StringBuilder body = new StringBuilder ();
  body.Append ("First Name: " + FirstName.Text.Trim () + "<br />");
  body.Append ("Last Name: " + LastName.Text.Trim () + "<br />");
  body.Append ("Email: " + Email.Text.Trim () + "<br />");
  body.Append ("Comment: " + Comment.Text.Trim () + "<br />");
  body.Append ("<br />");

  new EmailManager ()
    .SendEmail (to, Email.Text, subject, body.ToString ());
 }

 protected override void OnInit (EventArgs e)
 {
  base.OnInit (e);
  SubmitContact.Click += SubmitFeedback;
 }
}

Most of the same “concerns” still live in the page, with the exception of EmailManager whose purpose must be obvious. No magic yet.

Identifying external dependencies

What if I want to move all dependencies off the page and have them provided from the outside? This way the page will be left to its devices to manage postback, interact with controls, etc.

I have described this thought process in an earlier article of mine, Inversion of Control and Dependency Injection with WebForms (In 2 Acts). Please read it, if you haven’t yet. Also, if the concepts of Inversion of Control (IoC) and Dependency Injection (DI) are new to you, you do need to read the article.

What I have in mind is this:

public partial class Contact3 : Page
{
 public IContactProcessor ContactProcessor { get; set; }

 private void SubmitFeedback (object sender, EventArgs e)
 {
  if (!Page.IsValid)
   return;

  ContactInfo contactInfo = new ContactInfo
  {
   Subject = Subject.Text,
   FirstName = FirstName.Text,
   LastName = LastName.Text,
   ConsumerEmail = Email.Text.Trim (),
   Comment = Comment.Text
  };

  ContactProcessor.Process (contactInfo);
  Response.Redirect ("~/ThankYou.aspx");
 }

 protected override void OnInit (EventArgs e)
 {
  base.OnInit (e);
  SubmitContact.Click += SubmitFeedback;
 }
}

What’s going on here?

The page collects personal info (PI) and passes it on to some Contact Processor. The page is not concerned with what the processor does and how.

In this context, ContactInfo is a simple class which shuttles PI. In refactoring terms, it’s a Parameter Object.

The Contact Processor may send emails, or call web services, or store PI in a database. The point here is the page doesn’t worry about it, because it’s not its business. We achieve this by getting the page to talk to the Contact Processor through a contract—an interface.

public interface IContactProcessor
{
 void Process (ContactInfo contactInfo);
}

You may have noticed that the page doesn’t create an instance of a Contact Processor. It has no idea what concrete implementation it’s going to work with. An instance of IContactProcessor will be handed to it by an IoC container, the mechanics of which I’ll discuss later.

ContactProcessor, in its turn, doesn’t concern itself with irrelevant concerns of creating, formatting, and dispatching emails.

public class ContactProcessor : IContactProcessor
{
 private readonly IEmailManager _emailManager;
 private readonly IEmailFactory _emailFactory;

 public ContactProcessor (
    IEmailFactory emailFactory, 
    IEmailManager emailManager)
 {
  _emailFactory = emailFactory;
  _emailManager = emailManager;
 }

 public void Process (ContactInfo contactInfo)
 {
  SendConsumerEmail (contactInfo);
  SendSupportEmail (contactInfo);
 }

 private void SendConsumerEmail (ContactInfo contactInfo)
 {
  MailMessage email = 
   _emailFactory.CreateContactConfirmationEmail (contactInfo);

  _emailManager.SendEmail (email);
 }

 private void SendSupportEmail (ContactInfo contactInfo)
 {
  MailMessage email = 
   _emailFactory.CreateContactEmailToSupport (contactInfo);

  _emailManager.SendEmail (email);
 }
}

Again, dependencies external to ContactProcessor are injected by an IoC container. You can inspect their implementation in the code download.

You can add layers of abstraction ad nauseum, so you have to develop a feel for when to stop. I could refactor email formatting into its own class, but I chose not to.

At some point, you’ll have to read app settings from web.config. For example, the mailer needs to know the reply-to and support emails. How do you break the dependency on ConfigurationManager to make the code testable?

What I normally do is define an interface, IConfigurationProvider, and declare only the settings I’m interested in.

public interface IConfigurationProvider
{
 string ReplyToEmail { get; }
 string SupportEmail { get; }
}

This allows me to create a configuration provider mock in my unit tests without any dependency on web.config, whereas the real ConfigurationProvider goes about its business as usual.

public class ConfigrationProvider : IConfigurationProvider
{
 public string ReplyToEmail
 {
  get { return ConfigurationManager.AppSettings["ReplyToEmail"]; }
 }

 public string SupportEmail
 {
  get { return ConfigurationManager.AppSettings["SupportEmail"]; }
 }
}

Configuring an IoC container

Remember the trouble I went to in my previous article creating a custom PageHandlerFactory, resolving dependencies by hand and so forth?

This is where Spring.NET—an open-source IoC container—shines: it does it all for you! I has its own PageHandlerFactory, it works with WebForms, user controls (.ascx), HttpModules, HttpHandlers, WCF, etc. You can read more about it in Chapter 22, Spring.NET Web Framework of the official docs. In truth, Spring.NET has so much more to it, but here and now we’ll focus on its web facilities.

Spring’s configuration is XML-based (surprise!) which actually comes in handy when you maintain config files for various environments (dev, QA, stage, prod). Don’t try to memorize the settings—they are all in the docs.

To enable Spring.NET I’ve added the following entries in my web.config:

<configuration>
 <sectionGroup name="spring">
  <section name="context" 
    type="Spring.Context.Support.WebContextHandler, Spring.Web"/>
  <section name="objects" 
    type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
 </sectionGroup>
</configSections>

<system.web>
 <httpHandlers>
  <add verb="*" 
    path="*.aspx" 
    type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>
 </httpHandlers>

 <httpModules>
  <add name="Spring" 
    type="Spring.Context.Support.WebSupportModule, Spring.Web"/>
 </httpModules>
</system.web>

<system.webServer>
 <validation validateIntegratedModeConfiguration="false"/>
 <modules>
  <add name="Spring" 
    type="Spring.Context.Support.WebSupportModule, Spring.Web"/>
 </modules>

 <handlers>
  <add name="SpringPageHandler" verb="*" 
    path="*.aspx" 
    type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>

  <add name="SpringContextMonitor" verb="*" 
    path="ContextMonitor.ashx" 
    type="Spring.Web.Support.ContextMonitor, Spring.Web"/>
 </handlers>
</system.webServer>

 <spring>
  <context>
   <resource uri="~/Config/Spring.config"/>
  </context>
 </spring>
</configuration>

You will find these settings in sections 22.3.1. Configuration and 22.3.1.1. Configuration for IIS7 of the docs.

I chose to register all dependencies in a separate file, Spring.config.

<objects xmlns="http://www.springframework.net"
    default-autowire="byName">

 <object id="emailFactory" autowire="constructor"
    type="ContactUs.Infrastructure.Services.EmailFactory,»
                                         ContactUs.Infrastructure" />
  <!-- the rest omitted -->

 <object type="~/Contact3.aspx" />
</objects>

Note that web pages have a somewhat odd notation. The type attribute is the path to an .aspx file and there’s no id attribute.

Constructor or setting injection?

You may have noticed that ContactProcessor is injected onto the page via a property setter, whereas ContactProcessor itself takes everything in its constructor.

One school of thought dictates that dependencies injected via properties should be treated as optional. I disagree. First, I don’t quite understand why you’d have optional code. Second, we’re dealing with ASP.NET, which has a life of its own, so I don’t call the shots. Hence, my only recourse is to use properties.

On the other hand, I’m the sole owner of ContactProcessor so I can pass dependencies to its constructor. The signature of such a constructor helps me see if dependencies are coherent, i.e. related to each other. If not, perhaps my class has one too many concerns and it’s time to refactor it.

To me, which approach to use is a matter of expediency, not dogma. At any rate, Spring.NET can automatically wire both.

Putting dependencies under test

It’s not feasible or reasonable to test WebForms, so I won’t even go there. By now, my WebForms should have a bare minimum of logic, with each external dependency standing on its own and placed under test.

As a matter of personal preference, I chose NUnit, an open-source unit-testing framework, and Moq, a light and simple mocking library.

I encourage you to peruse the test project on your own.

Wrap-up

I hope this article helps you rethink how you build WebForms. The name of the game here is to identify dependencies, move them out of the WebForm and let the page do what it does best. This way you kill several birds with one stone: your code becomes less coupled and more cohesive, and you can put more of it under test than before.

Whereas my previous article was highly experimental, the ideas set forth here applicable to the here-and-now. Historically, ASP.NET has been a very closed framework, so we need some help peeling the layers which is where Spring.NET helps immensely.

2 comments

Magento
on February 02, 2010

Oh, this is an excellent article! I guess I will read all your posts here.


Bilal
on March 16, 2010

I attended your talk at the NYC Code Camp and this article along with your demo at the code camp cleared up quiet a few questions I had about the IOC/DI principles.

Thank you.