Search code examples
c#dependency-injectionautofacproperty-injection

How to inject a property into a class that I don't control the creation of


How do I inject the IServiceManager property into this class using autofac? This is a custom resource provider factory class that gets called when you make a call to HttpContext.GetGlobalResourceObject("", "MyResource") to get a resource string.

public class SqlResourceProviderFactory : ResourceProviderFactory
{
    // needs autofac property injection
    public IServiceManager ServiceManager { get; set; }

    public override IResourceProvider CreateGlobalResourceProvider(string classKey)
    {
        ...
    }

    public override IResourceProvider CreateLocalResourceProvider(string virtualPath)
    {
        ...
    }

    public static string GetAppRelativePath(string logicalPath)
    {
        ...
    }
}

Solution

  • I've faced this exact same problem - with an ASP.NET ResourceProviderFactory - and the answer is, unfortunately, that you have to use service location.

    There's no "hook" into the pipeline anywhere that you can inject anything or change the built-in ASP.NET behavior. Thus, if you need something put into a property, it has to be set in the constructor.

    public class SqlResourceProviderFactory : ResourceProviderFactory
    {
      public IServiceManager ServiceManager { get; set; }
    
      public SqlResourceProviderFactory()
      {
        this.ServiceManager =
          DependencyResolver.Current.GetService<IServiceManager>();
      }
    }
    

    Yeah, it's really ugly.

    Something very important to consider here, especially with respect to Autofac, is the lifetime scope for which your IServiceManager is registered.

    The ResourceProviderFactory is created once and cached. Same with the global/local resource providers that come out of the Create* methods. we had a heck of a time with this because it means there's not necessarily an HttpContext at the time the factories get created, and even if there is, if any of the downstream dependencies are registered InstancePerHttpRequest then they'll be disposed of and you're hosed.

    • Anything used by your ResourceProviderFactory or generated resource providers - all the way down the stack - should be registered either SingleInstance or InstancePerDependency.
    • If possible, instead of using DependencyResolver.Current (which, for Autofac, requires an active HttpContext), reference the application container directly. That means you need to store a reference to it somewhere else (a global static variable?) and use that.

    This is what a more complete solution might involve:

    // Create some "holder" for the app container.
    public static class ApplicationContainerProvider
    {
      public static ILifetimeScope Container { get; set; }
    }
    
    
    // In Global.asax, build your container and set it in both
    // the DependencyResolver AND in the holder class.
    var builder = new ContainerBuilder();
    builder.RegisterType<Something>().As<ISomething>();
    var container = builder.Build();
    var resolver = new AutofacDependencyResolver(container);
    DependencyResolver.SetResolver(resolver);
    ApplicationContainerProvider.Container = container;
    
    
    // In your service location, reference the container instead of
    // DependencyResolver.
    public class SqlResourceProviderFactory : ResourceProviderFactory
    {
      public IServiceManager ServiceManager { get; set; }
    
      public SqlResourceProviderFactory()
      {
        this.ServiceManager =
          ApplicationContainerProvider.Container.Resolve<IServiceManager>();
      }
    }
    

    Note that since you're resolving that out of the root container, it'll stick around for the lifetime of the application. Even if you register it as InstancePerDependency, because of the internal caching .NET does, it'll only get created once.

    If you don't like creating your own static holder class like that, you can abstract it away by using the CommonServiceLocator and the Autofac.Extras.CommonServiceLocator packages.