In my application I am in need of running background jobs as well as scheduled repeating job and found Hangfire to be just what I needed. I am also using Autofac as my IoC container and this seems to be causing problem. One of the tasks I want to do is send emails with Postal so this sample seemed just what I needed: http://docs.hangfire.io/en/latest/tutorials/send-email.html
I immediately ran into problems with Autofac when I placed the email sending in an Autofac activated service class so I moved it into a static class like this:
public static class EmailSenderService
{
public static void SendTestEmail()
{
// Prepare Postal classes to work outside of ASP.NET request
var viewsPath = Path.GetFullPath(HostingEnvironment.MapPath(@"~/Views/EmailTemplates"));
var engines = new ViewEngineCollection();
engines.Add(new FileSystemRazorViewEngine(viewsPath));
var emailService = new EmailService(engines);
dynamic email = new Email("Hello");
// Will look for Test.cshtml or Test.vbhtml in Views directory.
email.To = "krjgerkge@kergergerg.com";
email.Person = "Hello, non-asp.net world!";
emailService.Send(email);
}
}
In my controller I create the hangfire job with this code:
Despite the fact that there is no Autofac configuration for the EmailSenderService class it still throws this error when trying to send the email:
System.InvalidOperationException
The request lifetime scope cannot be created because the HttpContext is not available.System.InvalidOperationException: The request lifetime scope cannot be created because the HttpContext is not available.
at Autofac.Integration.Mvc.RequestLifetimeScopeProvider.GetLifetimeScope(Action`1 configurationAction)
at Autofac.Integration.Mvc.AutofacDependencyResolver.get_RequestLifetimeScope()
at Autofac.Integration.Mvc.AutofacDependencyResolver.GetServices(Type serviceType)
at System.Web.Mvc.DependencyResolverExtensions.GetServices[TService](IDependencyResolver resolver)
at System.Web.Mvc.MultiServiceResolver.GetCombined[TService](IList`1 items, IDependencyResolver resolver)
at System.Web.Mvc.ViewEngineCollection.get_CombinedItems()
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 lookup, Boolean trackSearchedPaths)
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 cacheLocator, Func`2 locator)
at System.Web.Mvc.ViewEngineCollection.FindView(ControllerContext controllerContext, String viewName, String masterName)
at Postal.EmailViewRenderer.CreateView(String viewName, ControllerContext controllerContext)
at Postal.EmailViewRenderer.Render(Email email, String viewName)
at Postal.EmailService.CreateMailMessage(Email email)
at Postal.EmailService.Send(Email email)
at CallSite.Target(Closure , CallSite , EmailService , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
at GroupTasks.Services.EmailSenderService.SendTestEmail() in c:\Users\gustav.ringbom\Repos\grouptasks\GroupTasksMVP\GroupTasksMVP\GroupTasks.Services\EmailSenderService.cs:line 36
If I simply call EmailSenderService.SendTestEmail(); from the controller it works fine so I know the email sending part is not the problem. There is something going on with Autofac which I cannot figure out. According to this article: http://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html HttpContext is not available in Autofac activated code but what I cant see is where Autofac comes into the picture at all. If anyone has any clues as to what I am missing here please help me. Thanks!
Hangfire is completely innocent here. Autofac exception is being thrown, because there is no HttpContext.Current available for your jobs, but some service is registered using InRequestScope method in Autofac configuration, either by your application, or by Autofac’s MVC integration. You need to find this service and re-register it using another scope (for example, transient).
Postal is using MVC’s ViewEngineCollection (as you can see from the call stack), and the latter implicitly uses DependencyResolver, and in your application AutofacDependencyResolver is being used as MVC integration.
Thanks a lot for your reply! I seem to be out of my league now with my understanding of IoC in general and Autofac in particular. As you said the call stack gives some clue that Postal is trying to resolve something that finally ends up at something that needs to be resolved. Although I cannot understand what.
Currently the solution is very small and the IoC setup code is very short so I cannot really understand what I need to do. If anyone has any experience with this I would appreciate any help.
The cshtml for the email template looks like this:
To: @Model.To
Subject: @Model.Subject
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title></title>
</head>
<body>
<div>
Hello @Model.Person
Your new password is: @Model.NewPassword
Regards
</div>
</body>
</html>
The entire IoC config looks like this:
public class IocConfig
{
private static WeakReference<IContainer> _container;
public static void RegisterDependencies()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterModule<AutofacWebTypesModule>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
//My business logic services like this example below
builder.RegisterType<BusinessLogicService>().As<IBusinessLogicService>();
builder.RegisterType<MemoryCacheWrapper>().As<IMemoryCache>();
builder.RegisterType<TypeLessMvcFileHelper>().AsSelf();
//Our context is disposed after each request
builder.RegisterType<DbContext>().As<IDbContext>().InstancePerRequest();
builder.Register(b => NLogLogger.Instance).SingleInstance();
builder.RegisterModule(new IdentityModule());
builder.RegisterModelBinderProvider();
builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
var container = builder.Build();
_container = new WeakReference<IContainer>(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
Good, thank you for the detailed response. Looks like the problem is non-trivial for Autofac and MVC implementation.
The Cause
We are using MVC’s ViewEngineCollection class to help Postal’s EmailService class to render the view:
var engines = new ViewEngineCollection();
The ViewEngineCollection class implicitly uses MVC’s DependencyResolver.Current, that is set to Autofac’s AutofacDependencyResolver class as we can see from the stack trace. And since the latter class usesRequestLifetimeScope class to obtain registered services, it requires HttpContext.Current to be available every time we try to resolve a dependency.
As we can see from the ViewEngineCollectionsource code, there is a constructor overload to pass custom DependencyResolver instance to the ViewEngineCollection instance, but it is an internal one. So, the simplest and obvious solution – pass another dependency resolver – is unavailable.
Possible solutions
Simplest, but hacky – set the private _dependencyResolver field of our ViewEngineCollection class instance through the reflection. Please google how to do it.
Derive the ViewEngineCollection class and override its FindView(ControllerContext, string, string) method to not to use DependencyResolver.Current at all.
Re-engineer Postal to not to use ViewEngineCollection class at all. As I understand, it is possible.
I ran into this same problem too. Tried finding a way to implement solution one but i guess i’m very much a novice. I had to change to Ninject and did not have this problem.
@odinserj: Can you please provide us a solution to this issue? I have ran into same issue; tried searching on google but sadly couldn’t find a working solution.