【问题标题】:WebApi: Per Request Per Action DbSession using IoC, how?WebApi:Per Request Per Action DbSession 使用 IoC,如何?
【发布时间】:2017-07-31 02:11:22
【问题描述】:

我们现有的数据库部署有一个“主”和一个只读副本。使用 ASP.NET 的 Web API2 和 IoC 容器,我想创建控制器操作,其属性(或缺少属性)指示要用于该请求的数据库连接(请参阅下面的控制器和服务用法)...

public MyController :  ApiController
{
    public MyController(IService1 service1, IService2 service2) { ... }

    // this action just needs the read only connection
    // so no special attribute is present
    public Foo GetFoo(int id)
    {
        var foo = this.service1.GetFoo(id);
        this.service2.GetSubFoo(foo);
        return foo;
    }

    // This attribute indicates a readwrite db connection is needed
    [ReadWrteNeeded]
    public Foo PostFoo(Foo foo)
    {
        var newFoo = this.service1.CreateFoo(foo);
        return newFoo;
    }
}

public Service1 : IService1
{
    // The dbSession instance injected here will be
    // based off of the action invoked for this request
    public Service1(IDbSession dbSession) { ... }

    public Foo GetFoo(int id) 
    { 
        return this.dbSession.Query<Foo>(...);
    }

    public Foo CreateFoo(Foo newFoo)
    {
        this.dbSession.Insert<Foo>(newFoo);
        return newFoo;
    }
}

我知道如何设置我的 IoC(结构映射或 Autofac)来处理每个请求的 IDbSession 实例。

但是,我不确定如何为请求创建 IDbSession 实例的类型,以关闭匹配控制器操作上的指示符属性(或缺少指示符属性)。我假设我需要创建一个 ActionFilter 来查找指标属性,并使用该信息识别或创建正确的 IDbSession 类型(只读或读写)。但是如何确保创建的 IDbSession 的生命周期是由容器管理的呢?您不会在运行时将实例注入容器,这很愚蠢。我知道过滤器在启动时创建一次(使它们单例),所以我不能将值注入过滤器的 ctor。

我考虑过创建一个 IDbSessionFactory,它具有“CreateReadOnlyDbSession”和“CreateReadWriteDbSession”接口,但我不需要 IoC 容器(及其框架)来创建实例,否则它无法管理其生命周期(调用 dispose当http请求完成时)。

想法?

PS 在开发过程中,我刚刚为每个操作创建了一个读写连接,但我真的想长期避免这种情况。我还可以将 Services 方法拆分为单独的只读和读写类,但我想避免这种情况以及将 GetFoo 和 WriteFoo 放在两个不同的 Service 实现中似乎有点不靠谱。

更新:

我开始使用 Steven 提出的制作 DbSessionProxy 的建议。那行得通,但我真的在寻找一个纯粹的 IoC 解决方案。不得不使用 HttpContext 和/或(在我的情况下)Request.Properties 对我来说有点脏。所以,如果我要弄脏,我还不如一路走下去,对吧?

对于 IoC,我使用了 Structuremap 和 WebApi.Structuremap。后一个包为每个 Http 请求设置一个嵌套容器,并且它允许您将当前的 HttpRequestMessage 注入 到服务中(这很重要)。这就是我所做的......

IoC 容器设置:

For<IDbSession>().Use(() => DbSession.ReadOnly()).Named("ReadOnly");
For<IDbSession>().Use(() => DbSession.ReadWrite()).Named("ReadWrite");
For<ISampleService>().Use<SampleService>();

DbAccessAttribute (ActionFilter):

public class DbAccessAttribute : ActionFilterAttribute
{
    private readonly DbSessionType dbType;

    public DbAccessAttribute(DbSessionType dbType)
    {
        this.dbType = dbType;
    }

    public override bool AllowMultiple => false;

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var container = (IContainer)actionContext.GetService<IContainer>();
        var dbSession = this.dbType == DbSessionType.ReadOnly ?
                container.GetInstance<IDbSession>("ReadOnly") :
                container.GetInstance<IDbSession>("ReadWrite");

        // if this is a ReadWrite HttpRequest start an Request long
        // database transaction
        if (this.dbType == DbSessionType.ReadWrite)
        {
            dbSession.Begin();
        }

        actionContext.Request.Properties["DbSession"] = dbSession;
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var dbSession = (IDbSession)actionExecutedContext.Request.Properties["DbSession"];
        if (this.dbType == DbSessionType.ReadWrite)
        {
            // if we are responding with 'success' commit otherwise rollback
            if (actionExecutedContext.Response != null &&
                actionExecutedContext.Response.IsSuccessStatusCode &&
                actionExecutedContext.Exception == null)
            {
                dbSession.Commit();                    
            }
            else
            {
                dbSession.Rollback();
            }
        }
    }
}

更新服务1:

public class Service1: IService1
{
    private readonly HttpRequestMessage request;
    private IDbSession dbSession;

    public SampleService(HttpRequestMessage request)
    {
        // WARNING: Never attempt to access request.Properties[Constants.RequestProperty.DbSession]
        // in the ctor, it won't be set yet.
        this.request = request;
    }

    private IDbSession Db => (IDbSession)request.Properties["DbSession"];

    public Foo GetFoo(int id) 
    { 
        return this.Db.Query<Foo>(...);
    }

    public Foo CreateFoo(Foo newFoo)
    {
        this.Db.Insert<Foo>(newFoo);
        return newFoo;
    }
}

【问题讨论】:

  • but don't I need the IoC container (and its framework) to create the instance otherwise it can't manage its lifecycle 使工厂也是一次性的。让它跟踪它创建的实例并在处置时让它检查任何尚未处置的子实例并处置它们。容器会创建工厂并跟踪它,因为它是一次性的。

标签: c# asp.net-web-api inversion-of-control autofac structuremap


【解决方案1】:

我假设我需要创建一个 ActionFilter 来查找指标属性,并使用该信息识别或创建正确的 IDbSession 类型(只读或读写)。

对于您当前的设计,我会说 ActionFilter 是要走的路。但是我确实认为另一种设计会更好地为您服务,这是一种业务操作更多explicitly modelled behind a generic abstraction,因为在这种情况下您可以将属性放在业务操作中,并且当您明确地将读取操作与写入操作分开时( CQS/CQRS),你甚至可能根本不需要这个属性。但是我现在会认为这超出了您的问题范围,因此这意味着 ActionFilter 是您的最佳选择。

但是如何确保创建的 IDbSession 的生命周期是由容器管理的呢?

诀窍是让 ActionFilter 在请求全局值中存储有关使用哪个数据库的信息。这允许您为IDbSession 创建一个代理实现,该代理实现能够基于此设置在内部在可读和可写实现之间切换。

例如:

public class ReadWriteSwitchableDbSessionProxy : IDbSession
{
    private readonly IDbSession reader;
    private readonly IDbSession writer;

    public ReadWriteSwitchableDbSessionProxy(
        IDbSession reader, IDbSession writer) { ... }

    // Session operations
    public IQueryable<T> Set<T>() => this.CurrentSession.Set<T>();

    private IDbSession CurrentSession
    {
        get
        {
            var write = (bool)HttpContext.Current.Items["WritableSession"];

            return write ? this.writer : this.reader;
        }
    }
}

【讨论】:

  • 这不是一个糟糕的解决方案。我曾考虑过传入只读和读写实例,但代理更有意义。我会调查一下,谢谢。
  • 如果proxy实例是IoC容器“创建”的,那么Proxy如何获取HttpContext?
  • @Tony:HttpContext.Current 就是这样做的。它将获得“当前”HttpContext
  • 没有意识到您可以从任何地方获取 HttpContext。我总是将它从请求消息或操作上下文等中拉出来。等等,我正在将 request.Properites 与 HttpContext 混合。我需要进一步研究一下,谢谢。
猜你喜欢
  • 1970-01-01
  • 2017-06-19
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
相关资源
最近更新 更多