Webforms and Dependency Injection
Asked Answered
B

6

20

I am in the process of introducing a Dependency Injection framework into an existing WebForms application (using Castle Windsor).

I have pretty deep experience with DI, and tend to very strongly favor constructor injection over setter injection. If you are familiar with Webforms, you know that the ASP.Net framework handles the construction of page and control objects, making true constructor injection impossible.

My current solution is to register the container in the Application_Start event of the Global.asax, and keep the container as a public static variable in Global as well. I then simply resolve each service that I need directly in the page or control when I need them. So at the top of each page, I end up with code like this:

private readonly IMyService _exposureManager = Global.IoC.Resolve<IMyService>();
private readonly IMyOtherService _tenCustomersExposureManager = Global.IoC.Resolve<IMyOtherService>();

Obviously, I don't like having all these references to the container scattered about my application or having my page/control dependencies be non-explicit, but I have not been able to find a better way.

Is there a more elegant solution for using DI with Webforms?

Brakpan answered 20/1, 2012 at 20:24 Comment(2)
I found this article codemag.com/Article/1210031 which has excellent sample code as to how to implement dependency injection on WPF, MVC and WebForms (using PageHandlerFactory, in the case of WebForms). It seems to say that because of code-behind class limitations, you will have to use setter injection. It also, interestingly, shows how Microsoft Managed Extensibility Framework (MEF) can help you to solve this and similar DI problems in a very useful and slightly non-standard way.Groundspeed
Starting .NET 4.7.2 there is a Dependency Injection in WebForms. See my answer bellow.Equal
E
19

I agree with @DarinDimitrov that MVP is an interesting option. However, when working with a legacy application, rewriting an existing page to the MVP pattern is a hell of a job. In that case it might be better to start with the Service Locator pattern (but only in your UI classes) as you are already doing. However, do change one thing. Do not expose the chosen DI container to the application, as I expect you are doing with the Global.IoC property.

Instead, create a static Resolve<T> method on the Global class. This hides the container completely and allows you to swap implementations without having to change anything in your web pages. When you do this, there is no advantage in using the Common Service Locator as @Wiktor proposes. The Common Service Locator is just another abstraction for something that doesn't have to be abstracted (since you've already abstracted away the container using the Global.Resolve<T>).

Unfortunately with Web forms, there is not really any good way to do this. For Simple Injector, I wrote an integration guide for Web Forms that basically describes the use of the Global.Resolve<T> method, but also shows a way to tests if Page classes can be created. The guide can be used for other DI containers as well.

BTW, please keep in mind that with Castle Windsor, everything you request must be released explicitly (the Register Resolve Release pattern). This is a bit nasty (IMO) and differs from how other containers work and can be a source of memory leaks when you do not do this correctly.

Last note. It is possible to do constructor injection with Web Forms. Well... sort of, since this will call the overloaded constructor using reflection after the Form has been created using the default constructor, so this causes Temporal Coupling.

Eri answered 20/1, 2012 at 23:38 Comment(4)
Thanks for your answer. That last article is interesting, and would indeed be of great interest if not for the full-trust problem.Brakpan
@PhilSandler: Seems that Microsoft has abandoned partial trust completely from ASP.NET 4.0 and beyond.Eri
If the Resolve<T> method is static, then it has no access to the instance member container. I might be misunderstanding you though. Do you recommend making the container static as well?Soutane
If you can upgrade to .net framework 4.7.2 there is support for dependency injection. Example of integrating SimpleInjector into webforms.Foeman
C
4

Is there a more elegant solution for using DI with Webforms?

Yeap, the MVP pattern allows you to have a clean separation of concerns in a WebForms application. And once you have separation of concerns and weak coupling, DI is easy.

And in ASP.NET MVC that's built-in.

Complot answered 20/1, 2012 at 20:26 Comment(0)
E
4

Know that this is pretty old, but now, there is DI in WebForms starting in .NET 4.7.2. Regarding to this article: ASP.NET Blog: Use Dependency Injection In WebForms Application

Just install Microsoft.AspNet.WebFormsDependencyInjection.Unity package and registr your classes in Global.asax:

protected void Application_Start(object sender, EventArgs e)
{
    var container = this.AddUnity();

    container.RegisterType<IPopularMovie, MovieManager>();
    container.RegisterType<IMovieRepository, XmlMovieRepository>();
}

Hope it help.

Equal answered 3/10, 2018 at 5:55 Comment(0)
S
2

ASP.NET MVC has IDependencyResolver and a static manager class that lets you get and set the resolver. I didn't like the idea of referencing System.Web.Mvc in a web forms project, so I went with IServiceLocator, which does about the same thing:

public static class Bootstrapper
{
    private static readonly IUnityContainer _container = new UnityContainer();

    public static void Initialize()
    {
        ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(_container));

        _container.RegisterType<IDriverService, DriverService>();
    }

    public static void TearDown()
    {
        _container.Dispose();
    }
}

public class Global : HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        Bootstrapper.Initialize();
    }

    protected void Application_End(object sender, EventArgs e)
    {
        Bootstrapper.TearDown();
    }
}

Then in your Page class ...

IDriverService service = ServiceLocator.Current.GetInstance<IDriverService>();

Or wire up DI via constructor injection. I haven't gone down that road with web forms yet, so someone else will need to fill in for me :) (I've been living mostly in MVC land for about a year now).

My example uses Unity, but you should be able to adapt it to any other DI implementation fairly easily.

Shamblin answered 20/1, 2012 at 20:35 Comment(2)
Is it really useful to do a TearDown on application end? The app domain is being unloaded anyway. It's only useful if you registered singleton services that have teardown logic in their Dispose method, but would still be fragile, since there is no guarantee that all those Dispose methods actually run when an AppDomain is unloaded.Eri
Good question. I was simply trying to follow the disposable pattern. I wasn't sure where else to put it other than application end.Shamblin
S
1

As @DarinDimitrov says the MVP pattern is the way to go in order to use DI/IOC with Webforms.

Either you can roll your own implementation or use an existing framework. I've heard good about Webforms MVP, but I haven't actually used it.

According to the docs, it has built in support for DI via Castle Windsor, Autofac and Unity. It also has convention based auto discovery for Presenters.

Statism answered 20/1, 2012 at 20:54 Comment(0)
G
0

Actually, what you have just built is your own implementation of the Service Locator. But, almost for sure, an implementation already exists for a IoC framework of your choice.

http://commonservicelocator.codeplex.com/

Gotama answered 20/1, 2012 at 20:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.