How to handle asynchronous calls using Ninject InRequestScope?

We use Ninject in an ASP.NET Web Api application, and we bind our DbContext to InRequestScope . This works well with most of our queries, because they do their job synchronously, so the context can be safely removed after the query completes.

However, we have a request in which we make an asynchronous call to a web service that has a continuation method passed as a callback, and that the callback method should use the database context. However, our request should not wait for the completion of the asynchronous service call, but return immediately (this is an explicit requirement).

Here is a simplified example of the situation.

 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. } } 

How should this situation be handled with Ninject? Is there a way to somehow “extend” the lifetime of a related instance of DbContext explicitly? Or should the callback method create a completely new DbContext ? If necessary, how much should it use?

+5
source share
1 answer

It is not possible to explicitly extend the lifetime of an object with .InRequestScope() to extend to the end of the request.

If there are no business requirements that the work during the request and @callback should happen in one transaction, I would use two instances of DbContext . One during the request and one during the callback. Note. As far as I know, this also means that you cannot take an entity from the first context and update / save it in the second context. This means that you should only pass the identifier (and other data related to the operation) from the request to the callback. The callback should “create” a new DbContext and obtain the appropriate permissions from the context.

Conditional Linking Alternative

Alternatively, you can declare a special binding for this special case. Ninject supports so-called contextual bindings . This means that you will have two bindings, a standard binding and a contextual binding to a special case:

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

Note that the second binding does not indicate the scope — this means that SomeController is responsible for calling .Dispose() . In your case, this would mean that the callback would have to use the context. You should also get rid of the context in all cases of errors (errors in the callback code, errors that occur before the callback is triggered, ...).

Also, in fact your application is probably more of a bite, and .WhenInjectedInto<SomeController> will not be sufficient / correct, because you may want to inject the same instance into the controller plus the repository plus the request object .. which is not.

This means you need a scope, but the scope is different from .InRequestScope() . You can use .InCallScope() or a named scope - both are included in the named scope extension.

In addition, you will need to adapt the When condition. You can adapt it to cross requests and see if there is a FooController anywhere in the request chain. But this is not very indicative; instead, I would recommend using ninject IParameter to indicate that you want to use a special case. Parameter:

 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; } } } 

which will be used for type bindings:

 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 
+7
source

Source: https://habr.com/ru/post/1206328/


All Articles