如何使用Ninject InRequestScope处理异步调用?

时间:2014-11-06 08:44:32

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

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

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

以下是情况的简化示例。

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实例的生命周期?或者Callback方法是否应该创建全新的DbContext?如果应该,它应该使用什么范围?

1 个答案:

答案 0 :(得分:8)

无法明确延长.InRequestScope()对象的生命周期,以便在请求结束后延伸到该对象。

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

条件绑定备选

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

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

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

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

另外,实际上你的应用程序可能更复杂,.WhenInjectedInto<SomeController>不够/正确,因为您可能希望将相同的实例注入控制器以及存储库和查询对象。什么不是。

这意味着您需要确定范围,但范围与.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