在OWIN中间件

时间:2016-08-09 04:18:37

标签: dependency-injection asp.net-web-api2 unity-container owin

我想根据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.OwinAutofac.OwinAutofac.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管道中稍后的任何其他内容,将根据工厂中的属性配置实例。

1 个答案:

答案 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);
}

...