3

I'm trying to load web user controls using IoC. I'm using unity, I setup following the examples in the book. So far so good, but when I inject the interface that looks the individual controls itself I've a problem. I'm trying to use LoadControl(type, arguments) but the web user control is not loaded.

I look around the web and I cannot find anything to help me dynamic load web user controls using IoC.

Any of you have other strategy to load it? Do you need more info about my attempt?

Regards

4
  • Please make sure to post minimal example that shows what you talking about. Note that "the book" can be interpreted differently by people (mine would be GoF, but it has nothing to do with ASP.Net :)). Side note: consider switching to ASP.Net MVC instead as IoC/DI friendlier environment. Commented Jun 13, 2014 at 16:37
  • I do not believe this can be done directly with a standard IoC setup because of the requirement to use LoadControl, not simply resolving a type (and you must use the form with a Path for User Controls so the ASCX can be loaded). However, it's relatively easy to use a dispatch from Type->Path such that it can be as easy as LoadControlByType<T> (this in turn could be handled by IoC if injecting in the Page context, but I recommend not going that far). Commented Jun 13, 2014 at 16:39
  • @AlexeiLevenkov WebForms, with a few uhh, changes, is pretty IoC/DI friendly [outside of controls in the markup anyway]. Not saying that I'd start a new WebForms project, unless mandated by other requirements ;-) Commented Jun 13, 2014 at 16:43
  • Hi, The book is unity.codeplex.com/documentation. Change to MVC is not possible (requirement). Commented Jun 26, 2014 at 15:27

1 Answer 1

2

Letting your DI container wire up your Page, HttpHandler and UserControls is absolutely possible with Web Forms, but there isn't anything built-in, so you'll have to do it yourself. There are two ways of doing this. Either you create a custom PageHandlerFactory or you create a custom HttpModule. Since the only way to hook in a PageHandlerFactory is through the web.config, my preference is using a HttpModule. When using an HttpModule, you can register it using the System.Web.PreApplicationStartMethodAttribute (System.Web assembly) and the Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility (Microsoft.Web.Infrastructure assembly). That would look like this:

[assembly: System.Web.PreApplicationStartMethod(typeof(ModuleInitializer), "Init")]

public static class ModuleInitializer
{
    public static void Init()
    {
        DynamicModuleUtility.RegisterModule(
            typeof(WebFormsDependencyInjectionHttpModule));
    }
}

The trick with your custom HttpModule is to hook onto the application's PreRequestHandlerExecute event. This allows you to go through the page hierarchy and inject any dependencies before the page is being executed.

public class WebFormsDependencyInjectionHttpModule : IHttpModule {
    public static UnityContainer Container;
    private HttpApplication application;

    public void Init(HttpApplication context) {
        this.application = context;
        context.PreRequestHandlerExecute += this.PreRequestHandlerExecute;
    }

    public void Dispose() { }

    internal static void InitializeInstance(object instance) {
        Container.BuildUp(instance);
    }

    private void PreRequestHandlerExecute(object sender, EventArgs e) {
        if (Container == null) 
            throw new InvalidOperationException("Set Container first.");

        var handler = this.application.Context.CurrentHandler;
        if (handler != null) {
            InitializeHttpHandler(handler);
        }
    }

    private void InitializeHttpHandler(IHttpHandler handler) {
        InitializeInstance(handler);
        if (handler is Page) {
            PageInitializer.HookEventsForUserControlInitialization((Page)handler);
        }
    }
    private sealed class PageInitializer { ... }
}

This module just makes sure that Unity's BuildUp method is called very early in the page lifestyle to build up the Page or IHttpHandler instance. This allows you to inject dependencies into your Page classes, but won't inject any dependencies in any used UserControl instances. To enable this, the module calls the special PageInitializer.HookEventsForUserControlInitialization method. Here the PageInitializer class is:

internal sealed class PageInitializer {
    private HashSet<Control> alreadyInitializedControls = new HashSet<Control>();
    private Page page;

    internal PageInitializer(Page page) {
        this.page = page;
    }

    internal static void HookEventsForUserControlInitialization(Page page) {
        var initializer = new PageInitializer(page);
        page.PreInit += initializer.PreInit;
        page.PreLoad += initializer.PreLoad;
    }

    private void PreInit(object sender, EventArgs e) {
        this.RecursivelyInitializeMasterPages();
    }

    private void RecursivelyInitializeMasterPages() {
        foreach (var masterPage in this.GetMasterPages())
            this.InitializeUserControl(masterPage);
    }

    private IEnumerable<MasterPage> GetMasterPages() {
        MasterPage master = this.page.Master;

        while (master != null) {
            yield return master;
            master = master.Master;
        }
    }

    private void PreLoad(object sender, EventArgs e) {
        this.InitializeControlHierarchy(this.page);
    }

    private void InitializeControlHierarchy(Control control) {
        var dataBoundControl = control as DataBoundControl;

        if (dataBoundControl != null) {
            dataBoundControl.DataBound += this.InitializeDataBoundControl;
        } else {
            var userControl = control as UserControl;

            if (userControl != null)
                this.InitializeUserControl(userControl);

            foreach (var childControl in control.Controls.Cast<Control>()) {
                this.InitializeControlHierarchy(childControl);
            }
        }
    }

    private void InitializeDataBoundControl(object sender, EventArgs e) {
        var control = (DataBoundControl)sender;
        if (control != null) {
            control.DataBound -= this.InitializeDataBoundControl;
            this.InitializeControlHierarchy(control);
        }
    }

    private void InitializeUserControl(UserControl instance)
    {
        if (!this.alreadyInitializedControls.Contains(instance)) {
            WebFormsDependencyInjectionHttpModule.InitializeInstance(instance);
            // Ensure every user control is only initialized once.
            this.alreadyInitializedControls.Add(instance);
        }
    }
}

The PageInitializer class will take the process a step further and will hook onto the Page's PreInit and PreLoad events to allow dependencies to be injected into master pages and to go through the complete control hierarchy to inject dependencies into any UserControl. It even hooks onto the DataBound event of any DataBoundControl in the control hierarchy, to make sure that any UserControl that is loaded by a DataBoundControl gets initialized.

I think this should do the trick :-)

Sign up to request clarification or add additional context in comments.

1 Comment

How may I adjust this code to inject the services in my project: stackoverflow.com/questions/45216358/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.