Autofac - The request lifetime scope cannot be created because the HttpContext is not available - due to async code?
Asked Answered
B

4

20

Short Question: Same as this unanswered problem

Long Question:

I just ported some code over from an MVC 4 + Web Api solution that was using Autofac into my new solution which is also using Autofac but only with Web Api 2 (no MVC 5.1 project, just a web api).

In my previous solution I had MVC4 and Web Api so I had 2 Bootstrapper.cs files, one for each. I copied over just the Web Api bootstrapper for the new project.

Now I have 2 other projects in the new solution that need to pull a dependency. Lets just assume I have to use DependencyResolver.Current.GetService<T>() despite it being an anti-pattern.

At first this was not working until I set the MVC Dependency Resolver to the same container:

GlobalConfiguration.Configuration.DependencyResolver = 
     new AutofacWebApiDependencyResolver(container);

//I had to pull in Autofac.Mvc and Mvc 5.1 integration but this line fixed it
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

The strange part is, doing that only fixed it in ONE of those projects! Here's the situation:

 Solution.Web project
      Bootstrapper.cs that registers both dependency resolvers for web api and mvc.

 Solution.ClassLib project
      var userRepo = DependencyResolver.Current.GetService<IUserRepo>(); //Good! :)

 Solution.WindowsWorkflow project
      var userRepo = DependencyResolver.Current.GetService<IUserRepo>(); //Throws exception :(

The exception is: The request lifetime scope cannot be created because the HttpContext is not available.

Now before we start blaming the workflow, just know I had this exact set up working just fine in another solution the workflow was able to use DependencyResolver just fine. So I suspect this had to do with using a newer version of Autofac and the fact that the workflow runs asynchronously (just like the question i linked to regarding async code)

I tried switching all the registration code to use InstancePerLifetimeScope() instead of InstancePerHttpRequest() and trying to create a scope:

using (var c= AutofacDependencyResolver.Current
                     .ApplicationContainer.BeginLifetimeScope("AutofacWebRequest"))
{
   var userRepo = DependencyResolver.Current.GetServices<IUserRepo>();
}

But it didnt change the exception. Breaking the code down even further here's the exact culprit:

var adr = AutofacDependencyResolver.Current; //Throws that exception 

Really need to get past this spent too much time stuck. Will reward existing answer with bounty in 2 days

Burushaski answered 15/2, 2014 at 23:0 Comment(1)
But does HttpContext actually exists in your Workflow project? can you create a container and try resolve IUserRepo directly from the container, without going to .Current? Also InstancePerLifetimeScope() is better than InstancePerHttpRequest.Jacobite
H
29

UPDATE Nov. 20, 2014: In releases of Autofac.Mvc5 since this question was released, the implementation of AutofacDependencyResolver.Current has been updated to remove the need for an HttpContext. If you are encountering this problem and found this answer, you can potentially easily solve things by updating to a later version of Autofac.Mvc5. However, I will leave the original answer intact for folks to understand why the original question asker was having issues.

Original answer follows:


AutofacDependencyResolver.Current requires an HttpContext.

Walking through the code, AutofacDependencyResolver.Current looks like this:

public static AutofacDependencyResolver Current
{
  get
  {
    return DependencyResolver.Current.GetService<AutofacDependencyResolver>();
  }
}

And, of course, if the current dependency resolver is an AutofacDependencyResolver then it's going to try to do a resolution...

public object GetService(Type serviceType)
{
  return RequestLifetimeScope.ResolveOptional(serviceType);
}

Which gets the lifetime scope from a RequestLifetimeScopeProvider...

public ILifetimeScope GetLifetimeScope(Action<ContainerBuilder> configurationAction)
{
  if (HttpContext.Current == null)
  {
    throw new InvalidOperationException("...");
  }

  // ...and your code is probably dying right there so I won't
  // include the rest of the source.
}

It has to work like that to support tools like Glimpse that dynamically wrap/proxy the dependency resolver in order to instrument it. That's why you can't just cast DependencyResolver.Current as AutofacDependencyResolver.

Pretty much anything using the Autofac.Integration.Mvc.AutofacDependencyResolver requires HttpContext.

That's why you keep getting this error. It doesn't matter if you have no dependencies that are registered InstancePerHttpRequest - AutofacDependencyResolver will still require a web context.

I'm guessing the other workflow app you had where this wasn't an issue was an MVC app or something where there was always a web context.

Here's what I'd recommend:

  • If you need to make use of components outside a web context and you're in WebApi, use the Autofac.Integration.WebApi.AutofacWebApiDependencyResolver.
  • If you're in WCF, make use of the standard AutofacHostFactory.Container and that host factory implementation to resolve dependencies. (WCF is a little weird with its singleton host potential, etc. so "per request" isn't quite as straightforward.)
  • If you need something "agnostic" of technology, consider the CommonServiceLocator implementation for Autofac. It doesn't create request lifetimes, but it may solve some problems.

If you keep those things straight and don't try to use the various resolvers outside their native habitats, as it were, then you shouldn't run into issues.

You can fairly safely use InstancePerApiRequest and InstancePerHttpRequest interchangeably in service registrations. Both of these extensions use the same lifetime scope tag so the notion of an MVC web request and a web API request can be treated similarly even if the underlying lifetime scope in one case is based on HttpContext and the other is based on IDependencyScope. So you could hypothetically share a registration module across apps/app types and it should do the right thing.

If you need the original Autofac container, store your own reference to it. Rather than assuming Autofac will return that container somehow, you may need to store a reference to your application container if you need to get it later for whatever reason.

public static class ApplicationContainer
{
  public static IContainer Container { get; set; }
}

// And then when you build your resolvers...
var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver =
  new AutofacWebApiDependencyResolver(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
ApplicationContainer.Container = container;

That will save you a lot of trouble down the road.

Haemophilic answered 17/2, 2014 at 16:31 Comment(6)
I haven't had the time to try this yet but I'm quite sure it will resolve my issue so the answer it is. And great explaination, thanks !Burushaski
Unfortunately it doesnt work :( : No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.Burushaski
Wait I got it! Works if I register everything as InstancePerLifetimeScope(). Good stuff, thanksBurushaski
Is this really a valid answer. Cause I'm doing this ``` var type = Type.GetType(identifier); return AutofacDependencyResolver.Current.GetService(type); ``` But still get the missing HttpContext exception. And I also see the reason in the Autofac. Code : github.com/autofac/Autofac.Mvc/blob/develop/src/… So how do I go around this.Antipope
@Antipope yes, it's a valid answer. You may need to post a new question. Missing HttpContext could be because you're calling GetService when there's no context, not due to AutofacDependencyResolver.Current. Read this FAQ and then if you still have trouble, post a new question with your code.Haemophilic
have this error on last autofac after try do that for render emails throw view #483591 (Fake context after request end)Corwun
J
3

My assumptions:

  1. You are running workflow project in a separate Thread/AppDomain from MVC project.
  2. IUserRepo is dependent on HttpContext

If my assumption correct, Workflow project would have no idea about HttpContext.Current.

WindowsWorkflow project runs all the time (If I understand it correctly - did not actually work with this tech). Where as MVC is based on HTTP requests. HttpContext.Current is populated only when there is a request coming in. If no request - this variable is null. What happens if there is no request, but Workflow instance is trying to access HttpContext? Correct - null reference exception. Or in your case dependency resolution exception.

What you need to do:

  1. Separate container registrations into modules - domain module for all your domain classes. Then MVC module: for all your MVC specifics, like User.Current or HttpContext.Current. And Workflow module (if required) with all Workflow specific implementations.
  2. On Workflow initialisation create autofac container with domain and workflow modules, exclude MVC dependencies. For MVC container - create it without workflow module.
  3. For IUserRepo create implementation that is not dependent on HttpContext. This probably will be the most problematic to do.

I've done something similar for Quartz.Net execution in Azure. See my blog post about this: http://tech.trailmax.info/2013/07/quartz-net-in-azure-with-autofac-smoothness/. This post will not help you directly, but explains my reasoning for splitting autofac modules.

Update as per comment: WebApi clarifies a lot of things here. WebApi request don't go through the same pipeline as your MVC requests. And WebApi controllers don't have access to HttpContext. See this answer.

Now, depending on what you are doing in your wepApi controller, you might want to change the IUserRepo implementation to be able to work with both MVC and WebApi.

Jacobite answered 16/2, 2014 at 0:2 Comment(4)
Thank you, I will look into your answer. But sorry on my part for not mentioning that this particular workflow only gets fired up from an api request. So the user click a link in the email to verify their account and the web api controller calls workflow.VerifyRegistration(verificationCodeFromUrl) which then fires up the workflow while an HttpContext is available. I need to be able to use that context as I have successfully in a previous implementation of the same.Burushaski
Thanks for getting back. That makes sense now with Web Api having no HttpContext but what would you suggest I do with that updated knowledge? The workflow doesn't actually NEED an HttpContext, it doesnt reference it anywhere it just needs to resolve the IUserRepo dependency like any other so I'm still not sure why it's complaining. Why do I need an HttpContext to resolve the dependency? Why can't I just resolve it without one. Sure it'll be in a different scope but I've already accepted that caveat by using DependencyResolver, its an edge case I'm ok with since I dont even call uow.Commit()Burushaski
Neither UserRepository nor any of its dependencies have a dependency on HttpContextBurushaski
Now that is an excellent question! can you post your registration code ?Jacobite
P
0

We're currently in a situation where we have tests that suffer from the 'missing httpcontext' issue but cannot yet use the excellent recommendations above due to version constraints.

The way we solved it was to create a 'mock' http context in our test setup: see: Mock HttpContext.Current in Test Init Method

Pryce answered 4/9, 2015 at 16:21 Comment(0)
M
0

For me to resolve this issue I click on the same button another few tries, which is usually no more than 4. If this is you you could put a message for the user to do so.

Minstrelsy answered 20/10, 2021 at 18:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.