Как внедрить свойство в класс, который я не контролирую создание

Как я могу ввести свойство IServiceManager в этот класс с помощью autofac? Это пользовательский класс фабрики поставщиков ресурсов, который вызывается при вызове HttpContext.GetGlobalResourceObject("", "MyResource") чтобы получить строку ресурса.

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)
    {
        ...
    }
}

1 ответ

Решение

Я столкнулся с такой же проблемой - с ASP.NET ResourceProviderFactory - и ответ, к сожалению, заключается в том, что вы должны использовать услугу определения местоположения.

В любом месте нет "зацепки" в конвейер, когда вы можете что-нибудь добавить или изменить встроенное поведение ASP.NET. Таким образом, если вам нужно что-то поместить в свойство, оно должно быть установлено в конструкторе.

public class SqlResourceProviderFactory : ResourceProviderFactory
{
  public IServiceManager ServiceManager { get; set; }

  public SqlResourceProviderFactory()
  {
    this.ServiceManager =
      DependencyResolver.Current.GetService<IServiceManager>();
  }
}

Да, это действительно ужасно

Здесь очень важно учитывать, особенно в отношении Autofac, срок службы, для которого IServiceManager зарегистрирован.

ResourceProviderFactory создается один раз и кэшируется. То же самое с глобальными / локальными поставщиками ресурсов, которые выходят из методов Create *. у нас было чертовски время с этим, потому что это означает, что не обязательно HttpContext в то время, когда фабрики создаются, и даже если таковые имеются, если какие-либо из нижестоящих зависимостей зарегистрированы InstancePerHttpRequest тогда они будут утилизированы, и вы будете в безопасности.

  • Все, что используется вашим ResourceProviderFactory или сгенерированные поставщики ресурсов - все время вниз по стеку - должны быть зарегистрированы либо SingleInstance или же InstancePerDependency,
  • Если возможно, вместо использования DependencyResolver.Current (что для Autofac требует активного HttpContext), ссылка на контейнер приложения напрямую. Это означает, что вам нужно хранить ссылку на него где-то еще (глобальная статическая переменная?) И использовать его.

Вот что может включать в себя более полное решение:

// 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>();
  }
}

Обратите внимание, что, поскольку вы решаете это из корневого контейнера, он останется на всю жизнь приложения. Даже если вы зарегистрируете его как InstancePerDependency из-за внутреннего кэширования.NET он создается только один раз.

Если вам не нравится создавать свой собственный класс статического держателя, вы можете абстрагировать его, используя CommonServiceLocator и Autofac.Extras.CommonServiceLocator пакеты.

Другие вопросы по тегам