Best dependency-injection questions in June 2011

Real examples of ASP.NET MVC code refactored for Dependency Injection using Ninject

9 votes

Good morning!

Yesterday I watched Tekpub's amazing video "Dependency Injection and Inversion of Control" and it blew my mind. I feel like this is how I should be writing code. I have watched it before and even experimented with Ninject a little bit, but feel like I need some more examples of code being refactored into this paradigm.

The brief MUD example he goes into is great, but does anyone know of a site or project where they start with some ugly, but working, code and then refactor it into clean Dependency Injected code?

While I can find examples of code that use Dependency Injection pretty easily, I am having a hard time finding before/after code and I feel like this would greatly help me start programming "at the next level". I need a few more examples to really wrap my head around it.

Suppose you wanted to implement a spelling correction service. Input a word/phrase and out pops a corrected version of the word/phrase. What if you were depending on a large, online provider that went down for a day. Ideally, you would implement a backup in case the request to said provider failed. In that backup, you might call your secondary service and hope they're online. Maybe in a last ditch effort in the event they're both down, you implement an offline provider to correct spelling based on a local dictionary that exists on the file system.

How do you manage multiple providers like this?

Before

public class SpellCheck
{
    public string FixText(string text)
    {
        string CorrectedText = string.Empty;
        // create HTTP request to Online Provider #1
        // return CorrectedText;
    }
}

After

public class SpellCheck
{
    private readonly IOnlineSpellService _onlineSpell;
    private readonly IOfflineSpellService _offlineSpell;

    public SpellCheck(IOnlineSpellService onlineSpell, 
        IOfflineSpellService offlineSpell)
    {
        this._onlineSpell = onlineSpell;
        this._offlineSpell = offlineSpell;
    }

    // same method as before, just a slightly different implementation
    public string FixText(string text)
    {
        string fixedText = _onlineSpell.FixText(text);
        if(_onlineSpell.Failed)
            fixedText = _offlineSpell.FixText(text);

        return fixedText;
    }
}

Of course, you'd need to implement the interface ISpellOnlineService (and similar interface for ISpellOfflineService

public interface ISpellOnlineService
{
    public bool Failed { get; set; }
    public string FixText(string text);
}

Next, implement your spelling providers (both online and offline, or even multiple instances of both to gain more flexibility)

public class OnlineProviderNo1 : IOnlineSpellService
{
    public string FixText(string text)
    {
        // implement online logic
        this.Failed = false;
    }
}

Finally, register your services by overriding the CreateKernel method and subclassing your MvcApplication from NinjectHttpApplication. And apparently with Ninject you replace Application_Start with an override called OnApplicationStarted like so:

**This done in global.asax

public class MvcApplication : NinjectHttpApplication
{
    // ...

    protected override void OnApplicationStarted()
    {
        base.OnApplicationStarted();

        AreaRegistration.RegisterAllAreas();
        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }

    protected override IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        kernel.Bind<IOnlineSpellService>().To<OnlineProviderNo1>();
        kernel.Bind<IOfflineSpellService>().To<OfflineProviderNoX>();

        return kernel;
    }
}

Yes, this is a real world example, as I've implemented it (I actually used Castle Windsor, but it's the identical concept with Ninject--just adapted above) with 3 online providers and 2 offline providers. In CreateKernel you may choose to include logic to examine a database, config file, etc to determine which provider to register. For example, maybe one service has announced 24 hours of downtime (hopefully, you're lucky enough to have that announced!) so during that time you want to switch to another online provider for which you've implemented an instance of IOnlineSpellService

Why does everyone say dependency injection in ASP.NET webforms is hard when PageHandlerFactory and IHttpHandlerFactory exist?

6 votes

So I have a legacy webforms site and am working on making it easier to maintain. Chucking it away and rewriting it isn't an option.

IoC is obviously one of the first things it got, but this leaves me with the service-locator pattern and a bad taste, and the wondering of whether it could be done better.

Various people I've talked to online and off tell me that I could do property-injection with an HttpModule that scans a Page class for properties decorated with an Inject attribute or similar, but that sounds like a Reflection hit (cached, but still) on every request. Not appealing.

So I was looking at other options, and came across System.Web.IHttpHandlerFactory, which has apparently been in the framework since v2. One can remove the default *.aspx handler and replace it with one that uses a custom implementation, in httpHandlers web.config section.

So, the people I've talked to aren't dumb; I thought I'd ask here. Are there any gotchas with replacing the webforms PageHandlerFactory with an IoC-based implementation...?

It looks like it has both a CreateHandler and ReleaseHandler method, so life-style related memory leaks from the container keeping a reference to created components shouldn't be a problem...

Because of the way ASP.NET is designed, Page classes need to have a default constructor. When you want to use constructor injection, there is a way around this. You can make the default constructor protected and add a single public constructor that takes the dependencies as follows:

public partial class _Default : System.Web.UI.Page
{
    private IUserService service;

    protected _Default()
    {
    }

    public _Default(IUserService service)
    {
        this.service = service;
    }
}

This allows you to create a custom PageHandlerFactory and inject the dependencies in the constructor.

So this works, but there is a catch though. The _Default class you define is not the actual class ASP.NET uses. ASP.NET creates a new class that inherits from _Default. This new class builds a control hierarchy based on the markup in the .aspx file. This class looks a bit like this:

public class ASPGeneratedDefault : _Default
{
    public ASPGeneratedDefault() : base()
    {
    }

     protected override vor OnPreInit(object sender, EventArgs e)
     {
          // Building up control hierarchy.
     }
}

As you can see, the custom constructor hasn't been overridden in the ASPGeneratedDefault by ASP.NET. Because of this there is no way of letting a DI framework create this type for us. The way around this is to let ASP.NET create this type for us and invoke the non-default constructor of the _Default base class on that existing instance. Because this instance already exists, we must do this with reflection and this will fail when run in partial trust.

Besides that, this works for page classes, but not for user controls on the page. The code generator of ASP.NET news up those controls with their default constructor during the control hierarchy build up process. When you want this to work for them, you need your custom PageHandlerFactory to hook to the PreInit event of those controls, because during the time the page class is constructor, the related controls and user controls are not yet created. However, to register the PreInit event on them, you need to find those controls within the page class, and again we need to reflect over the page class. Because controls are stored in non-public instance fields, again this won't work in partial trust.

Whether or not it is a problem your application can not run in partial trust is up to you, but since the security model of .NET 4 has been simplified considerably, it is very easy to run web apps in partial trust and it is something I strive to do.

So in conclusion, it is possible to do so (see for instance this example), but because of the limitations of the ASP.NET Web Forms framework, you need to run in full trust to get it to work.