我想根据HTTP请求的属性在ASP.NET Web API 2使用的Unity容器中配置注册。例如,对/api/database1/values
的请求应该导致为容器配置IDbContext
的Unity容器配置,而对/api/database4/values
的请求将为数据库4配置IDbContext
。
我已经使用UnityHierarchicalDependencyResolver
作为依赖项解析器,因此使用HierarchicalLifetimeManager
注册的类型仅在请求的生命周期内持续存在。这适用于根据请求获取已解决类型。但是如何使用OWIN中间件为每个请求注册让我们超越。
在我的中间件中,对System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUnityContainer))
的调用会获得IUnityContainer
的实例,但它是所有请求的相同容器,包括之前的任何注册请求。
通过使用我自己的UnityHierarchicalDependencyResolver
实现封装IDependencyResolver
,我可以看到IDependencyResolver.BeginScope
在此过程的后期才被调用。因此问题似乎是,在我的中间件调用Next(..)
很久之后,在Web API唤醒之前,不会创建子容器。
有没有办法可以让我的依赖解析器的范围更早开始?我还缺少其他一些策略吗?如果它有所不同,我在IIS中托管,但支持OWIN中间件方法。
这不是一个回答,而且它的评论太大了,但在努力用Unity解决这个问题后,我决定转而使用Autofac,而这一切都刚刚落下帷幕到位。
Autofac OWIN软件包(Autofac.Mvc5.Owin
,Autofac.Owin
,Autofac.WebApi2.Owin
)使得在OWIN管道中使用Autofac变得容易,并确保在ASP.NET MVC和Web API中进行适当的生命周期管理。这是缺失的环节。
我无法找到一种方法来重新配置每个请求的容器,但它确实至少可以配置每个请求的工厂(所以是的,@ Haukinger和@alltej,你是正确的推动在那个方向。
所以我注册了一家工厂:
builder.RegisterType<DataDependencyFactory>().InstancePerRequest();
并注册该工厂的create方法,如:
builder
.Register(c => c.Resolve<DataDependencyFactory>().CreateDataDependency())
.As<IDataDependency>()
.InstancePerRequest();
以这种方式注册工厂特别有用,因为下游家属不需要了解工厂。我喜欢这个,因为我的家属不需要工厂,他们需要一个实例。容器弯曲到我的家属的需要,而不是相反:)
然后,在一个OWIN中间件中,我解析工厂,并根据请求的属性在其上设置属性。随后在MVC或Web API控制器中解析IDataDependency
,或者在OWIN管道中稍后的任何其他内容,将根据工厂中的属性配置实例。
答案 0 :(得分:1)
基于你的api URL(“/ api / database4 / values”),我建议你创建一个过滤器属性(例如DbIdFilter),这样你就可以将filter属性重用于跟随类似url路径/段的其他控制器方法如下所示:
[HttpGet]
[DbIdFilter]
[Route("{databaseId}/values")]
public IHttpActionResult GetValues()
{
return Ok();
}
[HttpGet]
[DbIdFilter]
[Route("{databaseId}/products")]
public IHttpActionResult GetProducts()
{
return Ok();
}
首先,创建过滤器属性:
public class DbIdFilterAttribute : ActionFilterAttribute
{
private readonly string _routeDataId;
private const string defaultRouteName = "databaseId";
public DbIdFilterAttribute():this(defaultRouteName)
{}
public DbIdFilterAttribute(string routeDataId)
{
_routeDataId = routeDataId;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var routeData = actionContext.Request.GetRouteData();
var dbId = routeData.Values[_routeDataId] as string;
//here we create the db instance at the filter level.
DbInstanceFactory.RegisterDbInstance(dbId);
}
}
接下来,创建一个实例工厂,它将在运行时注册/解析db实例:
public class DbInstanceFactory : IDbInstanceFactory
{
public static IDbInstance RegisterDbInstance(string databaseId)
{
var factory = UnityConfig.GetConfiguredContainer().Resolve<IDbInstanceFactory>();
return factory.CreateInstance(databaseId);
}
public IDbInstance CreateInstance(string databaseId)
{
var container = UnityConfig.GetConfiguredContainer();
//container.RegisterType<IDbInstance, DbInstance>();
container.RegisterType<IDbInstance, DbInstance>(new InjectionConstructor(databaseId));
var dbInstance = container.Resolve<IDbInstance>();
return dbInstance;
}
public IDbInstance GetInstance()
{
var container = UnityConfig.GetConfiguredContainer();
var dbInstance = container.Resolve<IDbInstance>();
return dbInstance;
}
}
public interface IDbInstanceFactory
{
IDbInstance CreateInstance(string databaseId);
IDbInstance GetInstance();
}
在UnityConfig.cs中注册此工厂类(或在您当前注册类型的任何位置):
container.RegisterType<IDbInstanceFactory, DbInstanceFactory>
(new ContainerControlledLifetimeManager());
它已注册ContainerControlledLifetimeManager,因为此工厂不必是每个请求。
所以只是下面的一个基本DbInstance类(为了清楚起见)在构造函数中获取一个参数(此参数可以是您的连接字符串或命名连接):
public class DbInstance : IDbInstance
{
public string DbId { get; }
public DbInstance(string databaseId)
{
DbId = databaseId;
}
}
public interface IDbInstance
{
string DbId { get; }
}
在控制器类中,您可以像这样使用它:
....
private IDbInstanceFactory _dbFactory;
public MyController(IDbInstanceFactory dbFactory)
{
_dbFactory = dbFactory;
}
// Alternate, if you want to use property injection instead of constructor injection
//[Dependency]
//public IDbInstanceFactory DbFactory { get; set; }
[HttpGet]
[DbIdFilter]
[Route("{databaseId}/test")]
public IHttpActionResult Test()
{
var db = _dbFactory.GetInstance();
return Ok(db.DbId);
}
...