【问题标题】:How to handle async calls with Ninject InRequestScope?如何使用 Ninject InRequestScope 处理异步调用?
【发布时间】:2014-11-06 08:44:32
【问题描述】:

我们在 ASP.NET Web Api 应用程序中使用 Ninject,我们将 DbContextInRequestScope 绑定。这适用于我们的大多数请求,因为它们同步完成所有工作,因此可以在请求完成后安全地释放上下文。

但是,我们根据请求执行异步 Web 服务调用,该调用具有作为回调传递的延续方法,并且该回调方法需要使用数据库上下文。但是我们的请求不应该等待异步服务调用完成,而是立即返回(这是一个明确的要求)。

这是一个简化的例子。

public class MyController : ApiController
{
    private readonly MyDbContext dbContext;
    private readonly SomeWebService service;

    public MyController(MyDbContext dbContext, SomeWebService service)
    {
        this.dbContext = dbContext;
        this.service = service;
    }

    public IHttpActionResult MyActionWithAsyncCall()
    {
        // Doing stuff.

        // Calling webservice method, passing the Callback as the continuation.
        service.MethodWithCallback(param1, param2, this.Callback);

        // Returning without waiting for the service call to be completed.
        return Ok();
    }

    private void Callback()
    {
        // Trying to use the DbContext:
        var person = dbContext.People.First();
        // The above line sometimes throws exception, because the context has been disposed.
    }
}

Ninject 应该如何处理这种情况?有没有办法以某种方式显式地“延长”绑定 DbContext 实例的生命周期?或者回调方法应该创建全新的DbContext?如果应该,应该使用什么范围?

【问题讨论】:

  • 听起来像是消息队列的工作。

标签: c# asp.net asp.net-web-api ninject


【解决方案1】:

没有办法通过.InRequestScope() 显式延长对象的生命周期以延长到请求结束后。

如果没有业务要求请求和@回调期间的工作必须在单个事务中发生,我会使用两个DbContext 实例。一个在请求期间,一个在回调期间。注意:据我所知,这也意味着您不能从第一个上下文中获取实体并在第二个上下文中更新/保存它。这意味着您只能将标识符(和与操作相关的其他数据)从请求传递到回调。回调必须“创建”一个新的 DbContext 并从上下文中检索相应的实体。

条件绑定替代

作为替代方案,您可以为这种特殊情况声明一个特殊绑定。 Ninject 支持所谓的contextual bindings。这意味着您将有两个绑定,标准绑定和上下文的特殊情况绑定:

Bind<DbContext>().ToSelf().InRequestScope();

Bind<DbContext>().ToSelf()
    .WhenInjectedInto<SomeController>();

请注意,第二个绑定没有指定范围 - 这意味着 SomeController 负责调用 .Dispose()。在您的情况下,这意味着回调必须处理上下文。您还需要在所有错误情况下处理上下文(回调代码中的错误、触发回调之前发生的错误......)。

此外,实际上您的应用程序可能更复杂,.WhenInjectedInto&lt;SomeController&gt; 还不够/正确,因为您可能希望将相同的实例注入控制器加上存储库和查询对象.. 什么不是。

这意味着您将需要范围界定,但范围不同于 .InRequestScope()。您可以使用 .InCallScope() 或命名范围 - 两者都包含在 named scope extension 中。

此外,您还需要调整 When 条件。您可以对其进行调整以遍历请求并查看请求链中的任何位置是否有FooController。但这不是很高效,相反,我建议使用 ninject IParameter 来指定您想要特殊情况处理。参数是:

public class NonRequestScopedParameter : Ninject.Parameters.IParameter
{
    public bool Equals(IParameter other)
    {
        if (other == null)
        {
            return false;
        }

        return other is NonRequestScopedParameter;
    }

    public object GetValue(IContext context, ITarget target)
    {
        throw new NotSupportedException("this parameter does not provide a value");
    }

    public string Name
    {
        get { return typeof(NonRequestScopedParameter).Name; }
    }

    // this is very important
    public bool ShouldInherit
    {
        get { return true; }
    }
}

这将应用于以下绑定:

kernel.Bind<SomeController>().ToSelf()
      .WithParameter(new NonRequestScopedParameter());

kernel.Bind<DbContext>().ToSelf()
       .When(x => x.Parameters.OfType<NonRequestScopedParameter>().Any())
       .InCallScope(); // or whatever scope you're using

【讨论】:

  • 感谢您的详细回答。最后我们选择了第一种方法,异步回调使用单独的上下文(实际上回调是在另一个类中实现的,该类注入了一个 IServiceLocator 接口,并在每个回调调用中要求一个新的上下文。)
猜你喜欢
  • 1970-01-01
  • 2012-06-24
  • 1970-01-01
  • 1970-01-01
  • 2012-05-22
  • 2011-09-18
  • 1970-01-01
  • 2011-05-28
  • 2020-02-21
相关资源
最近更新 更多