Search code examples
c#asp.netwebformsunity-containerwebusercontrol

ASP.NET WebForms dynamic load WebUserControls using IoC


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


Solution

  • 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 :-)