Autofac共享对象要求每个控制器不同的登记,但InstancePerApiControllerType将无法正常工作 [英] Autofac shared objects require different registrations per controller but InstancePerApiControllerType won't work

查看:286
本文介绍了Autofac共享对象要求每个控制器不同的登记,但InstancePerApiControllerType将无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

InstancePerApiControllerType不工作详细,我无法使用 InstancePerApiControllerType 配置我的解决方案。答案只要有工作,只要我直接注入 ConnectionContext 到控制器,或以其他方式知道一个类只能由特定的控制器。不幸的是,是不是在我的情况的情况:

  ControllerA  - > EngineA  - > RepositoryA  - > GenericEntityAccessorControllerB  - > EngineB  - > RepositoryB  - > GenericEntityAccessor

问题是,当我们通过 ControllerA GenericEntityAccessor ​​需要字符串A,并从<$ C进来$ C> ControllerB 它需要字符串B。

当然,真实的情况是有点更复杂,有一些不好的做法,如code直接新闻 - 最多一个 ConnectionContext (这是传统code)。我目前正在探索提供了提供了一个懒使用通过Autofac注入并配置控制器的连接字符串的另一个组成部分,但不好的做法,也有导致问题(即一旦我开始改变的东西在界面,所有的多米诺骨牌开始翻倒,我结束了15个班后来想知道如何到达那里)。

是否有解决这种类型的东西任何模式,技术等?我无法想象这一切都让少见。

更新:

要提供一些更多的细节,因为我有一些麻烦这个工作,一般我们有以下的层次,呈现出的作用域我申请

 控制器 - &GT; InstancePerApiRequest()
I *库 - &GT; ?
I *经理 - &GT; ?
I *生成器 - &GT; ?
I *适配器 - &GT; ?
ISqlServerConnectionContext - &GT; ?
IConnectionContextCache - &GT; InstancePerApiRequest()

我已经得到了一些组件,这些组件直接拿ISqlServerConntectionContext,我试图提供像这样:

  container.Register(C =&GT;
{
    VAR connectionContextCache = c.Resolve&LT; IConnectionContextCache&GT;();
    VAR连接=(ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;    返回连接;
})为&lt; ISqlServerConnectionContext方式&gt;()InstancePerD​​ependency();

不幸的是在这一点上我得到了CurrectConnectionContext空。我在这一点上的猜测是我有未从控制器植根一些组件,我目前正在经历的依赖手工试图找到它(据我所知的是不是为我找出引发哪些对象的一种方式Autofac试图提供当我调试ISqlServerConnectionContext)。

更新2:

原来我也有一些问题,我在那里不当注册的事情,而对于 DocumentController ISqlServerConnectionContext 的依赖C>,即使它没有(在此是通过委托的东西它没有依赖于创建的)。

现在我有一个循环引用,我是pretty确保我创建自己的注册:

container.Register(X =&GT;
{
    如果(x.IsRegistered&LT; Htt的prequestMessage&GT;())
    {
        VAR HTT prequestMethod = x.Resolve&LT; Htt的prequestMessage&GT;();        VAR tokenHelper = x.Resolve&LT; ITokenHelper&GT;();
        VAR令牌= tokenHelper.GetToken(HTT prequestMethod);        返回令牌?新NullMinimalSecurityToken();
    }    返回新NullMinimalSecurityToken();
})为&lt; IMinimalSecurityToken方式&gt;()InstancePerApiRequest();container.Register(C =&GT;
{
    VAR connectionContextCache = c.Resolve&LT; IConnectionContextCache&GT;();
    VAR令牌= c.Resolve&LT; IMinimalSecurityToken&GT;();
    VAR连接=(ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;    connection.Token =记号。    返回连接;
})为&lt; ISqlServerConnectionContext方式&gt;()InstancePerApiRequest();

问题是 ISqlServerConnectionContext 的类型 IMinimalSecurityToken 这是可选的属性,绝对不使用时的 ISqlServerConnectionContext 被用于查找 IMinimalSecurityToken ,它依赖于 ISqlServerConnectionContext ITokenHelper

更新3:

有关完整性,为了解决我的循环引用的问题,我需要使用命名服务,并使用 SqlServerConnectionContext 这并没有 IMinimalSecurityToken IOAuthTokenManager 注册属性集。现在我越来越可怕


  

没有一个标签匹配'AutofacWebRequest'的范围是可见的。


错误,但我认为这是值得一个新的问题,如果我不能够解决这个问题。

container.Register(C =&GT;
{
    VAR的productId = WellKnownIdentifierFactory.Instance.GetWellKnownProductIdentifier(WellKnownProductIdentifiers.RESTSearchService);
    VAR的connectionString = ConfigurationManager.AppSettings [AppSettingsNames.DatabaseConnection]    VAR newConnectionContext =新SqlServerConnectionContext(的connectionString){产品id =的productId};
    newConnectionContext.Open();    返回newConnectionContext;
})命名为&lt; ISqlServerConnectionContext方式&gt;(OAuthTokenConnectionContext)InstancePerApiRequest();
container.Register(C =&gt;新建SqlServerBuilderFactory(c.ResolveNamed<ISqlServerConnectionContext>(\"OAuthTokenConnectionContext\"))).Named<IBuilderFactory>(\"OAuthTokenBuilderFactory\").InstancePerApiRequest();
container.Register(C =&gt;新建OAuthTokenManager(c.ResolveNamed<IBuilderFactory>(\"OAuthTokenBuilderFactory\"))).As<IOAuthTokenManager>().InstancePerApiRequest();


解决方案

这可以使用对象图一辈子作用域AutoFac的支持解决了。


  1. 缓存中的对象当前 SqlServerConnectionContext 范围到控制器的使用寿命。

  2. SqlServerConnectionContext 工厂类型,一旦连接创建其分配到的当前生存范围的高速缓存的支持字段

  3. 任何类型的控制器的寿命范围内的范围则可以通过访问高速缓存与控制器相关的连接

我能想到的唯一的复杂性是:


  1. 如果控制器的不可以实际上是对所有类型的特定连接上的依赖一辈子范围的根源。即如果它们属于控制器的寿命之外。

  2. 如果任何依赖注册为单个实例。在这种情况下,他们将不能够解决高速缓存,因为它是目前实现,因为它是PerApiRequest

例如:

 公共接口ISqlServerConnectionContextCache
{
    ISqlServerConnectionContext CurrentContext {搞定;组; }
}公共类SqlServerConnectionContextScopeCache:ISqlServerConnectionContextCache
{
    公共ISqlServerConnectionContext CurrentContext {搞定;组; }
}公共接口ISqlServerConnectionContextFactory
{
  ISqlServerConnectionContext创建();
}//本厂拥有高速缓存作为一个依赖关系
//这将是首次使用的缓存并且因此
// AutoFac将创建在控制器的范围一个新
公共类SqlServerConnectionContextFactory:ISqlServerConnectionContextFactory
{
  私人字符串_connectionString;
  私人ISqlServerConnectionContextCache _connectionCache;  公共SqlServerConnectionContextFactory(ISqlServerConnectionContextCache connectionCache,
    字符串的connectionString)
  {
    _connectionCache = connectionCache;
    _connectionString =的connectionString;
  }  公共ISqlServerConnectionContext的Create()
  {
    VAR connectionContext =新SqlServerConnectionContext(_connectionString);
    connectionContext.Open();
    _sqlServerConnectionContextProvider.CurrentContext = connectionContext;
    返回connectionContext;
  }
}公共类myController的:ApiController
{
  私人ISqlServerConnectionContext _sqlServerConnectionContext;  公共myController的(Func键&LT;字符串,ISqlServerConnectionContextFactory&GT;的connectionFactory)
  {
    _sqlServerConnectionContext = connectionFactory的(MyConnectionString);
  }
}//由于缓存寿命范围会收到单个实例
//与当前寿命范围相关的高速缓存的
//假设我们是控制器的范围之内,这将得到
//这是由工厂发起缓存
公共类MyTypeScopedByController
{
    公共MyTypeScopedByController(ISqlServerConnectionContextCache connectionCache)
    {
        VAR sqlServerConnectionContext = connectionCache.CurrentContext;
    }
}// AutoFac布线
builder.RegisterType&LT; SqlServerConnectionContextScopeCache&GT;()
    。至于&LT; ISqlServerConnectionContextCache&GT;()
    .InstancePerApiRequest();
builder.RegisterType&LT; SqlServerConnectionContextFactory&GT;()
    。至于&LT; ISqlServerConnectionContextFactory&GT;()
    .InstancePerD​​ependency();

As detailed in InstancePerApiControllerType not working, I am unable to use the InstancePerApiControllerType to configure my solution. The answer provided there works so long as I am directly injecting a ConnectionContext into the controller, or otherwise know that a class is only used by a specific controller. Unfortunately that is not the case in my situation:

ControllerA -> EngineA -> RepositoryA -> GenericEntityAccessor

ControllerB -> EngineB -> RepositoryB -> GenericEntityAccessor

The issue is when we come in through ControllerA, GenericEntityAccessor needs "string A" and from ControllerB it needs "string B".

Of course, the real situation is a little more complicated and there are some bad practices such as code that directly "news"-up a ConnectionContext (it's legacy code). I'm currently exploring providing another component that provides the connection string that is injected via Autofac and configured in the controller using Lazy, but the bad practices are causing problems there also (i.e. once I start to change things in the interface, all the dominoes start to fall over and I end up 15 classes later wondering how I got there).

Are there any patterns, techniques, etc. that address this type of thing? I can't imagine it's all that uncommon.

UPDATE:

To provide a few more specifics, since I'm having some trouble getting this to work, in general we have the following hierarchy, showing which scopes I've applied

Controller -> InstancePerApiRequest()
I*Repository -> ?
I*Manager -> ?
I*Builder -> ?
I*Adapter -> ?
ISqlServerConnectionContext -> ?
IConnectionContextCache -> InstancePerApiRequest()

I've got a number of components that directly take ISqlServerConntectionContext and I'm trying to provide it like so:

container.Register(c =>
{
    var connectionContextCache = c.Resolve<IConnectionContextCache>();
    var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;

    return connection;
}).As<ISqlServerConnectionContext>().InstancePerDependency();

Unfortunately at that point I'm getting a null for CurrectConnectionContext. My guess at this point is I've got some component that isn't rooted from the controller and I'm currently going through the dependencies manually attempting to find it (AFAIK the isn't a way for my to find out which object triggered Autofac to attempt to provide the ISqlServerConnectionContext when I'm debugging).

UPDATE 2:

It turns out I did have some issues where I was registering things improperly, and creating a dependency on ISqlServerConnectionContext for DocumentController, even though it did not have one (this was created through the delegate for something it did depend on).

Now I've got a circular reference that I'm pretty sure I've created myself in the registrations:

container.Register(x =>
{
    if (x.IsRegistered<HttpRequestMessage>())
    {
        var httpRequestMethod = x.Resolve<HttpRequestMessage>();

        var tokenHelper = x.Resolve<ITokenHelper>();
        var token = tokenHelper.GetToken(httpRequestMethod);

        return token ?? new NullMinimalSecurityToken();
    }

    return new NullMinimalSecurityToken();
}).As<IMinimalSecurityToken>().InstancePerApiRequest();

container.Register(c =>
{
    var connectionContextCache = c.Resolve<IConnectionContextCache>();
    var token = c.Resolve<IMinimalSecurityToken>();
    var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;

    connection.Token = token;

    return connection;
}).As<ISqlServerConnectionContext>().InstancePerApiRequest();

The problem is ISqlServerConnectionContext has a property of type IMinimalSecurityToken which is optional, and definitely not used when the ISqlServerConnectionContext is being used to look up IMinimalSecurityToken, which depends on ISqlServerConnectionContext through ITokenHelper.

UPDATE 3:

For completeness, in order to solve my circular reference problem I needed to use named services, and use a SqlServerConnectionContext that did not have the IMinimalSecurityToken property set for the IOAuthTokenManager registration. Now I'm getting the dreaded

No scope with a Tag matching 'AutofacWebRequest' is visible

error, but I think that warrants a new question if I'm not able to solve it.

container.Register(c =>
{
    var productId = WellKnownIdentifierFactory.Instance.GetWellKnownProductIdentifier(WellKnownProductIdentifiers.RESTSearchService);
    var connectionString = ConfigurationManager.AppSettings[AppSettingsNames.DatabaseConnection];

    var newConnectionContext = new SqlServerConnectionContext(connectionString) { ProductID = productId };
    newConnectionContext.Open();

    return newConnectionContext;
}).Named<ISqlServerConnectionContext>("OAuthTokenConnectionContext").InstancePerApiRequest();
container.Register(c => new SqlServerBuilderFactory(c.ResolveNamed<ISqlServerConnectionContext>("OAuthTokenConnectionContext"))).Named<IBuilderFactory>("OAuthTokenBuilderFactory").InstancePerApiRequest();
container.Register(c =>new OAuthTokenManager(c.ResolveNamed<IBuilderFactory>("OAuthTokenBuilderFactory"))).As<IOAuthTokenManager>().InstancePerApiRequest();

解决方案

This can be solved using AutoFac's support for object graph lifetime scoping.

  1. Cache the current SqlServerConnectionContext in an object scoped to the lifetime of your controller.
  2. Within the SqlServerConnectionContext factory type, once the connection is created assign it to the backing field of the current lifetime-scoped cache
  3. Any types scoped within the lifetimes scope of a controller can then access the connection associated with that controller through the cache

The only complexities I can think of are:

  1. If the controller is not actually the root of a lifetime scope for all types with a dependency on a specific connection. I.e. if they fall outside the lifetime of the controller.
  2. If any of the dependencies are registered as single instance. In which case they will not be able to resolve the Cache as it is currently implemented as it is PerApiRequest.

For example:

public interface ISqlServerConnectionContextCache
{
    ISqlServerConnectionContext CurrentContext { get; set; }
}

public class SqlServerConnectionContextScopeCache : ISqlServerConnectionContextCache
{
    public ISqlServerConnectionContext CurrentContext { get; set; }
}

public interface ISqlServerConnectionContextFactory
{
  ISqlServerConnectionContext Create();
}

// The factory has the cache as a dependancy
// This will be the first use of the cache and hence
// AutoFac will create a new one at the scope of the controller
public class SqlServerConnectionContextFactory : ISqlServerConnectionContextFactory
{
  private string _connectionString;
  private ISqlServerConnectionContextCache _connectionCache;

  public SqlServerConnectionContextFactory(ISqlServerConnectionContextCache connectionCache,
    string connectionString)
  {
    _connectionCache = connectionCache;
    _connectionString = connectionString;  
  }

  public ISqlServerConnectionContext Create()
  {
    var connectionContext = new SqlServerConnectionContext(_connectionString);
    connectionContext.Open();
    _sqlServerConnectionContextProvider.CurrentContext = connectionContext;
    return connectionContext;
  }
}

public class MyController : ApiController
{
  private ISqlServerConnectionContext _sqlServerConnectionContext;

  public MyController(Func<string, ISqlServerConnectionContextFactory> connectionFactory)
  {
    _sqlServerConnectionContext = connectionFactory("MyConnectionString");
  }
}

// As the cache is lifetime scoped it will receive the single instance
// of the cache associated with the current lifetime scope
// Assuming we are within the scope of the controller this will receive
// the cache that was initiated by the factory
public class MyTypeScopedByController
{
    public MyTypeScopedByController(ISqlServerConnectionContextCache connectionCache)
    {
        var sqlServerConnectionContext = connectionCache.CurrentContext;
    }
}

// AutoFac wiring
builder.RegisterType<SqlServerConnectionContextScopeCache>()
    .As<ISqlServerConnectionContextCache>()
    .InstancePerApiRequest();
builder.RegisterType<SqlServerConnectionContextFactory>()
    .As<ISqlServerConnectionContextFactory>()
    .InstancePerDependency();

这篇关于Autofac共享对象要求每个控制器不同的登记,但InstancePerApiControllerType将无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆