如何使用NinjectInRequestScope处理异步调用

本文关键字:异步 调用 处理 NinjectInRequestScope 何使用 | 更新日期: 2023-09-27 18:29:25

我们在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吗?如果应该,它应该使用什么范围?

如何使用NinjectInRequestScope处理异步调用

在请求结束后,没有办法用.InRequestScope()显式延长对象的生存期。

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

条件绑定备选方案

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

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

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

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

这意味着您将需要作用域,但作用域不同于.InRequestScope()。您可以使用.InCallScope()或命名作用域-这两个都包含在命名作用域扩展中。

此外,您还需要调整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