I'm using LightInject and now trying to inject a custom service in my scheduled job class. Like so:
public class ImportValuesJob
{
private IImportService _importService;
public ImportValuesJob(IImportService importService)
{
_importService = importService;
}
public void SetTimeInterval()
{
RecurringJob.AddOrUpdate(() => SetTimeInterval(), Cron.MinuteInterval(1));
}
public void Import(PerformContext context)
{
var success = _importService.ImportData();
}
}
However, this would require me to pass IImportService as a parameter in my OwinStartup class when creating a new instance of ImportValuesJob, and so far I have found no way to access my service.
var importJob = new ImportValuesJob();
importJob .SetTimeInterval();
Is there a different, clever way of accomplishing this?
Hi
I had the same problem, but I solved it yesterday.
First set upp Hangfire with dependency incjection in UmbracoDefaultOwinStartup.Configuration (I'm using Autofac and with package Hangfire.Autofac):
var builder = new ContainerBuilder();
builder.RegisterType<QueService>().As<IQueService>();
Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(builder.Build());
Than add jobs like this:
RecurringJob.AddOrUpdate<IQueService>("QueService.ProcessQue", que => que.ProcessQue(), processQueCron);
Are you using Autofac alongside Umbraco 8's LightInject implementation (and is that even possible??)?
The reason I ask is, that if I change the dependency resolver to Autofac, the backoffice doesn't work. My guess is that the backoffice is using dependencies registered in umbracos LightInject implementation, and that they are now missing in Autofac.
I also found that i have to register umbraco's services in Autofac setup to be able to use them in my hangfirejob. Have you found an elegant solution to this, where you can reuse the registrations already done by Umbraco?
// register all controllers found in this assembly
builder.RegisterControllers(Assembly.GetExecutingAssembly()); //.PropertiesAutowired();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// register Umbraco MVC + web API controllers used by the admin site
builder.RegisterControllers(typeof(UmbracoApplication).Assembly);
builder.RegisterApiControllers(typeof(UmbracoApplication).Assembly);
builder.RegisterInstance(applicationContext.Services.MemberService).As<IMemberService>();
//builder.RegisterType<Members>().AsSelf();
// umbraco helper
builder.Register(c => ApplicationContext.Current).InstancePerRequest();
builder.Register(c => UmbracoContext.Current).InstancePerRequest();
builder.Register(c => new UmbracoHelper(c.Resolve<UmbracoContext>())).InstancePerRequest();
// plugins
builder.RegisterApiControllers(typeof(Lecoati.LeBlender.Extension.Controllers.LeBlenderEditorManagerTreeController).Assembly);
builder.RegisterApiControllers(typeof(Diplo.GodMode.Controllers.GodModeTreeController).Assembly);
builder.RegisterApiControllers(typeof(Jumoo.uSync.BackOffice.Controllers.uSyncApiController).Assembly);
builder.RegisterApiControllers(Assembly.Load("Umbraco.Forms.Web"));
The default LightInject scope management in Umbraco 8 will give you issues if you don't stick to Singleton services in your Hangfire jobs. as it relies on HttpContext to store the active scope.
We have done workarounds to get around the issue, but it isn't exactly the most beautiful code I have ever seen.
but here are the basic steps to get Hangfire hooked up.
You can get the LightInject ServiceContainer by using the following inside an IComposer Compose method.
var container = composition.Concrete as LightInject.ServiceContainer;
You can copy the code from the source repository for Hangfire.LightInject to your project and compile it against the embedded verison of LightInject inside Umbraco.
We hookup the Hangfire Config inside the an IComposer something like the following, you can't use this exactly as is but it will give you an idea, ours is a little convoluted as we have parts that are .net standard and .net framework so we use the DI as if it was based on the .NET Core IServiceProvider interface, to be ready for the shift when it comes:
private void ComposeHangfire(IServiceProvider serviceProvider)
{
var connectionString = System.Configuration
.ConfigurationManager
.ConnectionStrings[HangfireConnectionStringName]
.ConnectionString;
// configure Hangfire storage and ServiceProvider
GlobalConfiguration.Configuration
.UseSqlServerStorage(connectionString, new SqlServerStorageOptions { PrepareSchemaIfNecessary = true })
.UseServiceProviderJobActivator(serviceProvider);
}
Last part is to override the UmbracoDefaultOwinStartup, remember to replace it in web.config as well.
[assembly: OwinStartup(nameof(HangfireUmbracoOwinStartup),
typeof(HangfireUmbracoOwinStartup))]
namespace ContentService.Infrastructure
{
public class HangfireUmbracoOwinStartup : UmbracoDefaultOwinStartup
{
public ConnectionStringsConfiguration ConnectionStrings { get; set; }
public override void Configuration(IAppBuilder app)
{
//ensure the default options are configured
base.Configuration(app);
try
{
// NOTE: Triggers exception if the Storage hasn't been configured at this point
// ReSharper disable once UnusedVariable
var currentStorage = JobStorage.Current;
app.UseHangfireServer();
// place inside umbraco folder to avoid exposing it
app.UseHangfireDashboard("/umbraco/hangfire", new DashboardOptions
{
Authorization = new[]
{
// Only grant access to users in Umbraco with access to the Hangfire Section
new UmbracoAuthorizationFilter()
},
// avoid showing back to site link
AppPath = null
});
}
catch (InvalidOperationException ex)
{
// JobStorage.Current is not configured when installing updates
if (!ex.Message.StartsWith("JobStorage.Current"))
throw;
}
}
}
Thank you for getting back to me, it gives me something to go on.
So Im hitting a wall here. Ive installed the "Hangfire.LightInject" package, it matches our umbraco 8 solution just fine. So unless your modding the hangfire.lightinject package.
The things ive done so far is to move the config of hangfire into an IUserComposer like this:
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class HangfireJobsComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Register<SomeService>(Lifetime.Singleton);
// Configure hangfire
var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
var connectionString = System.Configuration
.ConfigurationManager
.ConnectionStrings["umbracoDbDSN"]
.ConnectionString;
var container = composition.Concrete as LightInject.ServiceContainer;
GlobalConfiguration.Configuration.UseLightInjectActivator(container);
GlobalConfiguration.Configuration
.UseSqlServerStorage(connectionString, options)
.UseConsole();
}
}
And in my owinStartup im doing this:
public override void Configuration(IAppBuilder app)
{
//ensure the default options are configured
base.Configuration(app);
try
{
var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
app.UseHangfireDashboard("/hangfire", dashboardOptions);
app.UseHangfireServer();
RecurringJob.AddOrUpdate<SomeService>("SendMail", x => x.SendMail(null), Cron.HourInterval(12));
}
catch (InvalidOperationException ex)
{
// JobStorage.Current is not configured when installing updates
if (!ex.Message.StartsWith("JobStorage.Current"))
throw;
}
}
So hangfire spins up just fine, and my job is registered, when im fireing my job it give me this error:
System.NullReferenceException: Object reference not set to an instance of an object.
at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 148
at LightInject.ScopeManager.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6325
at Hangfire.LightInject.LightInjecterScope..ctor(ServiceContainer container)
at Hangfire.LightInject.LightInjectJobActivator.BeginScope()
at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_0.<PerformJobWithFilters>b__0()
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable`1 filters)
at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)
My service still looks like this:
public class MyService
{
private readonly OtherService _otherService;
public MyService(OtherService otherService)
{
_otherService= otherService;
}
public void SomeJob(PerformContext hangfire)
{
//DO IT! :D
}
yep, thats caused by not having HttpContext for the scope creation in Hangfire, we faked the HttpContext before any scope access,
during scope creation and scope disposal,
That means you will need to source from Hangfire.LightInject and modify it to create a fake http context before any scope access or component activation.
or resort to a seperate container for Hangfire usage, but that kind of defeats the point.
ahh okay, i was missing the point of where the httpcontext was missing for lightinject. Yeah i thought of doing it all in seperate container but as you say that defeats the whole purpose.
I just added the hangfire.lightinject to my project, so i can customize it. Do you have anything to share here?
I have been talking to Shannon about the issue, hopefully we can get something done about it, as there are multiple better ways to store the scope today than inside the HttpContext.Items collection.
What we have done is fake a http context inside
LightInjectServiceProvider.GetService()LightInjectServiceProvider.Dispose()
LightInjectServiceScope.Dispose()
With code like this before any code inside the methods mentioned above.
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if(HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "http://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
do note that you can end up with scopes not being disposed if the thread changes during the execution of Hangfire jobs, its a risk we are willing to take, but it depends on your requirements and usage.
For people comming here to look for a solution, as you can see its a bit messy. It should work if you follow my sudo files above. And you have to pull in Hangfire.LightInject into your project(or build it on the sideline), and here your file LightInjectJobActivator.cs Should look like this:
public class LightInjectJobActivator : JobActivator
{
private readonly ServiceContainer _container;
internal static readonly object LifetimeScopeTag = new object();
public LightInjectJobActivator(ServiceContainer container, bool selfReferencing = false)
{
if (container == null)
throw new ArgumentNullException("container");
this._container = container;
}
public override object ActivateJob(Type jobType)
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
// this will fail if you do self referencing job queues on a class with an interface:
// BackgroundJob.Enqueue(() => this.SendSms(message));
var instance = _container.TryGetInstance(jobType);
// since it fails we can try to get the first interface and request from container
if (instance == null && jobType.GetInterfaces().Count() > 0)
instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
return instance;
}
public override JobActivatorScope BeginScope()
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
return new LightInjecterScope(_container);
}
}
class LightInjecterScope : JobActivatorScope
{
private readonly ServiceContainer _container;
private readonly Scope _scope;
public LightInjecterScope(ServiceContainer container)
{
_container = container;
_scope = _container.BeginScope();
}
public override object Resolve(Type jobType)
{ // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
var instance = _container.TryGetInstance(jobType);
// since it fails we can try to get the first interface and request from container
if (instance == null && jobType.GetInterfaces().Count() > 0)
instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
return instance;
}
public override void DisposeScope()
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
_scope?.Dispose();
}
}
Hi Rasmus, I've followed (most of) your example but now getting an error
I didn't follow the step of putting the config of Hangfire into an IUserComposer but instead added
var container = new LightInject.ServiceContainer();
// registrations
GlobalConfiguration.Configuration.UseLightInjectActivator(container);
to the owin file. Note that I followed this example to install and configure Hangfire. (this is all new to me and trying to keep it as simple as possible) I want to run some C# code to export data on demand or on a schedule. It seems like the LightInjectActivator is not initializing properly but I'm not completely sure. Do you have any suggestions? Thanks, Steve
Thanks for reaching out! Did you implement your own version of Hangfire.Lightinject where you fake the httpcontext? As Thomas suggests above? Because that was what did the trick for me.
Hi Rasmus, Thanks for your reply! I downloaded the Hangfire.LightInject source from GitHub and replaced the contents of LightInjectJobActivator.cs with your code above, built the project on the side and put the .dll into the Umbraco 8 project. Hangfire had been operating correctly with simple tasks (context.WriteLine... ) so it seems like it is basically set up properly however I think the trouble is combining the code from Sabastiaan's tutorial with the GlobalConfiguration.Configuration.UseLightInjectActivator(container); code to use LightInject. Any ideas of how to correctly add that code or change the Hangfire configuration to allow use of the 'UseLightInjectActivator'? Sorry if my terminology is a little off - this is all new to me! Steve
NO worries about terminology, let worry about a solution 🤓👍
SO lets try an compare setups, im running an umbraco 8.1.3.
Yes Hangfire it self works, if you dont touch anything Umbraco related and Dependency Injection related. Correct?
The issues I had, the first one was getting it to work with DI(Dependency Injection). To Fix this we are using the Hangfire.LightInject package, but the other issue is that if we touch any Umbraco stuff, like the contentCache, we get another error, because Hangfire is running in a not so weblike context and Umbraco is dependent on the httpContext.
So for it to work we have to fake the httpcontext, and this is why we have to roll our own version of Hangfire.LightInject where we change the code inside LightInjectJobActivator.
My file looks like this:
public class LightInjectJobActivator : JobActivator
{
private readonly ServiceContainer _container;
internal static readonly object LifetimeScopeTag = new object();
public LightInjectJobActivator(ServiceContainer container, bool selfReferencing = false)
{
if (container == null)
throw new ArgumentNullException("container");
this._container = container;
}
public override object ActivateJob(Type jobType)
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
// this will fail if you do self referencing job queues on a class with an interface:
// BackgroundJob.Enqueue(() => this.SendSms(message));
var instance = _container.TryGetInstance(jobType);
// since it fails we can try to get the first interface and request from container
if (instance == null && jobType.GetInterfaces().Count() > 0)
instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
return instance;
}
public override JobActivatorScope BeginScope()
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
return new LightInjecterScope(_container);
}
}
class LightInjecterScope : JobActivatorScope
{
private readonly ServiceContainer _container;
private readonly Scope _scope;
public LightInjecterScope(ServiceContainer container)
{
_container = container;
_scope = _container.BeginScope();
}
public override object Resolve(Type jobType)
{ // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
var instance = _container.TryGetInstance(jobType);
// since it fails we can try to get the first interface and request from container
if (instance == null && jobType.GetInterfaces().Count() > 0)
instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
return instance;
}
public override void DisposeScope()
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
new HttpResponse(new StringWriter()));
}
_scope?.Dispose();
}
}
So now we should have some components that works, now we need to initate everything correctly. In my setup, I have this process split into 2 areas, my OwinStartup.cs and my HangfireJobsComposer.cs
HangfireJobsComposer.cs
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class HangfireJobsComposer : IComposer
{
public void Compose(Composition composition)
{
// Configure hangfire
var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
var connectionString = System.Configuration
.ConfigurationManager
.ConnectionStrings["umbracoDbDSN"]
.ConnectionString;
var container = composition.Concrete as LightInject.ServiceContainer;
GlobalConfiguration.Configuration
.UseSqlServerStorage(connectionString, options)
.UseConsole()
.UseLightInjectActivator(container);
composition.Register<InvitationJobs>(Lifetime.Singleton);
}
OwinStartup.cs
public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
{
public override void Configuration(IAppBuilder app)
{
//ensure the default options are configured
base.Configuration(app);
try
{
var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
app.UseHangfireDashboard("/hangfire", dashboardOptions);
app.UseHangfireServer();
RecurringJob.AddOrUpdate<InvitationJobs>("DeleteOldInvitations", x => x.DeleteOldInvitations(null), Cron.HourInterval(23));
}
}
}
So this is my setup for hangfire. Now lets look at my InvitationJobs.
public class InvitationJobs
{
private readonly IInvitationDbService _invitationDbService;
public InvitationJobs(
IInvitationDbService invitationDbService
)
{
_invitationDbService = invitationDbService;
}
[AutomaticRetry(Attempts = 0)]
public void DeleteOldInvitations(PerformContext hangfire)
{
try
{
var invitations = _invitationDbService.GetAll();
foreach (var invitation in invitations)
{
hangfire.WriteLine($"Deletes invitation {invitation.Id} for tenant {invitation.Tenant} on rental {invitation.RentalId} at state {invitation.State}");
_invitationDbService.Delete(invitation.Id);
}
}
}
catch (Exception e)
{
Current.Logger.Error(this.GetType(), e, "Error on DeleteOldInvitations");
hangfire.WriteLine("ERROR ON removal of old invitations");
}
hangfire.WriteLine("Finished removal of old invitations");
}
}
And again my invitationJobs is also registered in my hangfireJobComposer.
Hi Rasmus, Thanks for taking the time to write up this very thorough reply! I've got Umbraco 8.2.1 installed and everything with Hangfire seems to be running correctly however in the tasks that I need to perform require the HttpContext so I'm pursuing this thread with keen interest. Thanks for all of the code. I've added to the project and working on integrating with what I've already got set up, including resolving a few minor glitches. Learning more about Umbraco 8 by the minute! Thanks, Steve
Hi Rasmus, I followed everything in your code as best as I could but still seem to be getting a NULL error related to the type characteristic of the job. I simplified InvitationJob.cs to the following.
I had to add
public interface IInvitationDbService
{
}
Otherwise was getting an error "the type or namespace 'IInvitationDbService' could not be found.." Perhaps this is part of the problem? (DI is all new to me!) With it though the errors in Visual Studio disappear but see this when the job runs:
I checked the Hanfire source and it appears this error has something to do with resolving the type on the job being run.
Specifically:
if (!context.BackgroundJob.Job.Method.IsStatic)
{
instance = scope.Resolve(context.BackgroundJob.Job.Type);
if (instance == null)
{
throw new InvalidOperationException(
$"JobActivator returned NULL instance of the '{context.BackgroundJob.Job.Type}' type.");
}
}
That's the only reference to this error I could find. It seems to me that the issue in my job class file. Can you send the full code of your InvitationJobs.cs file or let me know how to resolve the error that comes up on the IInvitationDbServive ? Thanks - and I definitely owe you a🍺(or two - ). Steve
A quick answer, sorry for the whole misunderstanding :) the invitationservice, is something from my code, its just an example of how we are using hangfire :)
So everything besides the invitationjobs is our setup for hangfire, while the InvitationJobs is one of many hangfire scheduled tasks running in our setup. I will just rewrite the example for you so the interface and DI wont confuse :)
And again no worries about the being new to DI and Umbraco 8, same here, just started in august with umb8 and DI.
This could be your job class instead of the our invitationJobs:
public class StevesSuperJobClass
{
//THIS PART IS WHERE YOU HOOK IN YOUR DI COMPONENTS/SERVICES/REPOS/INSERT-ANOTHER-NAME-FOR-A-CLASS-THAT-DOES-STUFF-FOR-YOU :)
public StevesSuperJobClass()
{
}
[AutomaticRetry(Attempts = 0)]
public void SomeJobSteveWantsPerformed(PerformContext hangfire)
{
try
{
//LETS GO STEVE DO YOUR BEST HERE !
//SEND THOSE SEXY EMAILS :D
hangfire.WriteLine("write awesome console points to hangfire,");
}
catch (Exception e)
{
Current.Logger.Error(this.GetType(), e, "Error on SomeJobSteveWantsPerformed");
hangfire.WriteLine("ERROR ON SomeJobSteveWantsPerformed");
}
hangfire.WriteLine("Finished SomeJobSteveWantsPerformed");
}
}
A thing to notice, i dont know if you have the Hangfire.Console package installed? With this you can do as above, adding in the console message.
Now in OwinStartup it should look like this:
OwinStartup.cs
public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
{
public override void Configuration(IAppBuilder app)
{
//ensure the default options are configured
base.Configuration(app);
try
{
var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
app.UseHangfireDashboard("/hangfire", dashboardOptions);
app.UseHangfireServer();
RecurringJob.AddOrUpdate<StevesSuperJobClass>("SomeJobSteveWants", x => x.SomeJobSteveWantsPerformed(null), Cron.HourInterval(23));
}
}
}
Hope this makes it a bit more clear :) Else please share you job class and owin startup up, assuming the rest is already in place. Also ensure that you hook up your new job class inside the HangfireJobsComposer as this
:D A beer is always welcome :) Lets get this thing fixed for you. If this dosnt work, lets go another around the christames tree, and try to describe to me what it is you want to achive, or we could a quick skype session.
Hi Rasmus, Thanks for yet another detailed reply and your patience working through this. I've now got it working with access to the Http context in the Hangfire job. The solution in my case was just to have an empty class constructor for the job.
public StevesSuperJobClass()
{
}
I'm not using any DI components or other services right now but need to learn how that works. For now it is empty like above and job is running smoothly. Thanks again for all of your help - and let me know how to buy you a beer! :) Steve
For me I was having issues with LightInjectJobActivator stuff. I was attempting all sorts of stuff. Installing Hangfire.LightInject through nuget, navigating referencing issues then copying the new LightInjectJobActivator classes into my project.
Replace the LightInjectJobActivator.cs with the contents in Ramus' example.
Build this then use the Hangfire.LightInject from this project and include it in your umbraco project.
Once this is done you can pretty much copy and paste the rest of the stuff in Ramus' example.
I plan to write a blog for this end to end starting with the hangfire install. There was a lot of scrolling up and down and referencing other forum posts.
I've just been experimenting with this, finally, after reading all these posts.
You don't even need to do all that, you can tell Hangfire to use any JobActivator you want. My composer looks like this:
using Hangfire;
using Hangfire.Console;
using Hangfire.SqlServer;
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Example.Namespace
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class HangfireJobsComposer : IComposer
{
public void Compose(Composition composition)
{
// Configure hangfire
var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
const string umbracoConnectionName = Constants.System.UmbracoConnectionName;
var connectionString = System.Configuration
.ConfigurationManager
.ConnectionStrings[umbracoConnectionName]
.ConnectionString;
var container = composition.Concrete as LightInject.ServiceContainer;
GlobalConfiguration.Configuration
.UseSqlServerStorage(connectionString, options)
.UseConsole()
.UseActivator(new LightInjectJobActivator(container));
}
}
}
Note the .UseActivator(new LightInjectJobActivator(container)); at the end.
For the LightInjectJobActivator class I used (and refactored) Rasmus' version. I'm not sure if the URL in there should point to an actual website though? Seems to work as it is.. I actually am not even sure why all these hoops are necessary, was it because you need to be able to use the ContentService and we need to wait for an Umbraco Context to exist first? Currently I'm not doing anything with ContentService so I am not sure I need the activator to be like this.
My version just removes a bit of repetition of that Http Context initialization:
using System;
using System.IO;
using System.Linq;
using System.Web;
using Hangfire;
using LightInject;
namespace Example.Namespace
{
public class LightInjectJobActivator : JobActivator
{
private readonly ServiceContainer _container;
public LightInjectJobActivator(ServiceContainer container, bool selfReferencing = false)
{
_container = container ?? throw new ArgumentNullException(nameof(container));
}
public override object ActivateJob(Type jobType)
{
Context.InitializeFakeHttpContext();
// this will fail if you do self referencing job queues on a class with an interface:
// BackgroundJob.Enqueue(() => this.SendSms(message));
var instance = _container.TryGetInstance(jobType);
// since it fails we can try to get the first interface and request from container
if (instance == null && jobType.GetInterfaces().Any())
instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
return instance;
}
[Obsolete("Please implement/use the BeginScope(JobActivatorContext) method instead. Will be removed in 2.0.0.")]
public override JobActivatorScope BeginScope()
{
Context.InitializeFakeHttpContext();
return new LightInjectScope(_container);
}
}
internal class LightInjectScope : JobActivatorScope
{
private readonly ServiceContainer _container;
private readonly Scope _scope;
public LightInjectScope(ServiceContainer container)
{
_container = container;
_scope = _container.BeginScope();
}
public override object Resolve(Type jobType)
{
Context.InitializeFakeHttpContext();
var instance = _container.TryGetInstance(jobType);
// since it fails we can try to get the first interface and request from container
if (instance == null && jobType.GetInterfaces().Any())
instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());
return instance;
}
public override void DisposeScope()
{
Context.InitializeFakeHttpContext();
_scope?.Dispose();
}
}
internal static class Context
{
public static void InitializeFakeHttpContext()
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
// Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
if (HttpContext.Current == null)
{
HttpContext.Current = new HttpContext(
new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager",
string.Empty),
new HttpResponse(new StringWriter()));
}
}
}
}
And for completion's sake, here's my OwinStartup class. The combination of these three should get you set up.
using Hangfire;
using Microsoft.Owin;
using Owin;
using Umbraco.Web;
[assembly: OwinStartup("UmbracoStandardOwinStartup", typeof(UmbracoStandardOwinStartup))]
namespace Example.Namespace
{
public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
{
public override void Configuration(IAppBuilder app)
{
//ensure the default options are configured
base.Configuration(app);
// Give hangfire a URL and start the server
var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
app.UseHangfireDashboard("/hangfire", dashboardOptions);
app.UseHangfireServer();
// Add jobs
RecurringJob.AddOrUpdate<MyService>(x => x.DoStuff(), Cron.Daily);
}
}
}
My only problem is that I need 1 Service to be injected into this OwinStartup class so I can make a list of jobs where just 1 variable changes based on a list of string (that list comes from the Service). But oh well, I can live with newing up that single Service.
I am running into a weird issue here. When my task runs, I get a System.NullReferenceException at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() at first. After a varying number of retries, it works fine. I have seen it work after between 1-3 retries, but never on the first try.
Also, my breakpoint is being hit in LightInjectJobActivator.cs where it manually sets HttpContext.Current, and it sets it, but by the time I stop at a breakpoint inside my task, HttpContext.Current is back to being null.
My task is injecting the following dependencies: IUmbracoContextFactory, ILogger, IContentService.
Any idea why this might be? As far as I can tell, my code follows Sebastiaan's code above exactly.
I know this is a while back. But have you ever tried this code with the MembershipHelper injected into a service that is used by in a job?
I have a problem relating to this, and it seems to come down to the access of umbraco's cache.
I have an build a cachehandler (using umbracos buildin cache) and injected it into a service and also inject the MemberShipHelper (which as far as I can see also uses cache).
If I remove those 2 dependencies in my service, the service and found by the LightInjectJobActivator (of course runs with failure, since the two dependencies are needed). But never the less runs. As soon as I inject the 2 dependencies, it fails during:
Resolve(Type jobType) -> var instance = _container.TryGetInstance(jobType);
Hi all, I know this is fairly olf but just found this code used somewhere.
Could we not do this:
private UmbracoContextReference EnsureContexts()
{
// IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
var contextFactory = _container.GetInstance<IUmbracoContextFactory>();
return contextFactory.EnsureUmbracoContext(true);
}
Actually i found another "issue" with DI/LightInject and Hangfire.
In some of my services I call a membership helper to get an ipublishedcontent of a member.
I do this like this:
var helper = Current.Factory.GetInstance<MembershipHelper>();
Normally this works in every other situation, but when used through hangfire and LightInject i get a Object not set error like this:
Object reference not set to an instance of an object.
at Umbraco.Web.Runtime.WebInitialComposer.<>c.<Compose>b__0_2(IFactory factory)
at LightInject.PerRequestLifeTime.GetInstance(Func`1 createInstance, Scope scope)
at Umbraco.Core.FactoryExtensions.GetInstance[T](IFactory factory)
at my services.....
In umbraco 7, when we needed this we had to ensure an umbraco context, gussing its something like this aswell, just like we are faking the httpcontext, but cant figure out how.
you properly need to constructor inject the IUmbracoContextFactory and
use it like
using (var context = _umbracoContextFactory.EnsureUmbracoContext())
{
// do stuff here that require umbraco context
}
if you need the MembershipHelper you should inject the MembershipHelper into the constructor of your service and rely on then code we did earlier today to fake the context.
When you access the Current.Factory you are going around that work, and have to fake the context again.
but accessing the Current.Factory is an anti pattern in DI terms, as you dont rely on the lift time scopes and other stuff provided by the IoC container.
Its going back to the Service Locator Pattern and you should try to avoid that.
using (var context = _umbracoContextFactory.EnsureUmbracoContext())
{
// do stuff here that require umbraco context
var helper = Current.Factory.GetInstance<MembershipHelper>();
var member = helper.GetById(memberId);
}
The above didnt work before but i was missing the ensureumbracocontext.
The way i want to do it is this, but this dosnt work:
My constructor
Its like im not allowed to inject the MembershipHelper, ive been looking into the source code and trying to find a service or something that can help me here to get ipublished member content, but nothing seems to work correct.
I think LightInject also support Func factories like
public class MyService {
private readonly Func<MembershipHelper> _membershipHelperFactory;
private readonly IUmbracoContextFactory _umbracoContextFactory
public MyService(
IUmbracoContextFactory umbracoContextFactory, Func<MembershipHelper> membershipHelperFactory)
{
_umbracoContextFactory = umbracoContextFactory;
_membershipHelperFactory= membershipHelperFactory;
}
public void DoMagicStuffWithMember()
{
using (var context = _umbracoContextFactory.EnsureUmbracoContext())
{
// do stuff here that require umbraco context
var helper = _membershipHelperFactory();
var member = helper.GetById(memberId);
}
}
}
But this could get you back to the old PerWebRequestScopeManagerProvider issue
Hope you can help me a bit here again, thought it was working but not a 100%, so hopin you have another input.
So im trying your above solution like this :
using (var context = _umbracoContextFactory.EnsureUmbracoContext())
{
// do stuff here that require umbraco context
var helper = _membershipHelperFactory();
var mem = helper.GetById(memberId);
l.UmbracoMember = new Umbraco.Models.MyMemberModel(mem);
}
The wierd thing is that the member itself is being pulled out, but not all the properties on the member, they return a NullException. The Umbraco.Models.MyMemberModel is just the generated modelsbuilder model.
(update)
Any ideas why the property binding is broken? Something with NuCache? The wierd thing is, if I step into the code, and step throught it, then sometimes the properties actually comes out.
A stack trace, just for the "fun" of it :)
System.NullReferenceException: Object reference not set to an instance of an object.
at Umbraco.Web.Templates.TemplateUtilities.ParseInternalLinks(String text, Boolean preview, UmbracoContext umbracoContext) in d:\a\1\s\src\Umbraco.Web\Templates\TemplateUtilities.cs:line 22
at Umbraco.Web.PropertyEditors.ValueConverters.TextStringValueConverter.ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, Object source, Boolean preview) in d:\a\1\s\src\Umbraco.Web\PropertyEditors\ValueConverters\TextStringValueConverter.cs:line 32
at Umbraco.Core.Models.PublishedContent.PublishedPropertyType.ConvertSourceToInter(IPublishedElement owner, Object source, Boolean preview) in d:\a\1\s\src\Umbraco.Core\Models\PublishedContent\PublishedPropertyType.cs:line 208
at Umbraco.Web.PublishedCache.NuCache.Property.GetInterValue(String culture, String segment) in d:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\Property.cs:line 192
at Umbraco.Web.PublishedCache.NuCache.Property.GetValue(String culture, String segment) in d:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\Property.cs:line 211
at Umbraco.Web.PublishedPropertyExtension.Value[T](IPublishedProperty property, String culture, String segment, Fallback fallback, T defaultValue) in d:\a\1\s\src\Umbraco.Web\PublishedPropertyExtension.cs:line 39
at Umbraco.Web.PublishedContentExtensions.Value[T](IPublishedContent content, String alias, String culture, String segment, Fallback fallback, T defaultValue) in d:\a\1\s\src\Umbraco.Web\PublishedContentExtensions.cs:line 155
So i found out that if i loop over all the properties before mapping it to my modelsbuilder model it worked. #hacky
using (var context = _umbracoContextFactory.EnsureUmbracoContext())
{
var umbracoContext = context.UmbracoContext;
// do stuff here that require umbraco context
var mem = _membershipHelperFactory.Invoke().GetById(l.MemberId);
var props = mem.Properties;
foreach (var prop in props)
{
var p = prop.GetValue();
}
l.UmbracoMember = new Umbraco.Models.MyMember(mem);
}
Did you every figure out a more solid solution for this?
I have been experiencing the exact same issue. Your comment above also seemed to work for me bit I am just worried about other problems might come of the back of this.
But we ended up dishing Umbraco members for the parts we needed, but our case is not comparable though :(
But for the time we used it, it worked for us, but we needed to loop over the properties, and if you debugged into this, you could see that the properties are in the "Properties" list, but still cannot get mapped.
My hunt for the holy hack grail stopped here hehe :)
I have also found another way to get around the HttpContext issue but its not fully tested yet.
by replacing the ScopeManagerProvider on the Container we can use another scope manager when there is no HttpContext.
// its important to inherit from the Umbraco MixedLightInjectScopeManagerProvider
// as they are testing for it with the 'is' keyword
public class ReworkedMixedLightInjectScopeManagerProvider : MixedLightInjectScopeManagerProvider, IScopeManagerProvider
{
private readonly IScopeManagerProvider _noneHttpProvider;
public ReworkedMixedLightInjectScopeManagerProvider()
{
_noneHttpProvider = new PerLogicalCallContextScopeManagerProvider();
}
// override via explicit interface implementation
IScopeManager IScopeManagerProvider.GetScopeManager(IServiceFactory factory)
{
if (HttpContext.Current == null)
{
return _noneHttpProvider.GetScopeManager(factory);
}
return base.GetScopeManager(factory);
}
}
and then assigning this as the ScopeManagerProvider on the container
container.ScopeManagerProvider = new ReworkedMixedLightInjectScopeManagerProvider();
Awesome stuff, I just tested it and its a no go or im doing it wrong.
private readonly MembershipHelper _membershipHelperFactory;
public MyService(
Func<MembershipHelper> membershipHelperFactory
)
{
_membershipHelperFactory = membershipHelperFactory();
}
//USING IT LIKE SO:
var helper = _membershipHelperFactory();
var member = helper.GetById(memberId);
Its a great idea for those who can live with stuff only running in a web context, and dosnt take long time to run, and also you dont want feedback inside of hangfire.
The main idea we use hangfire for is longer running tasks, something that might exceed the webserver process lifetime, also we use the logs on hangfire aswell(of course with other logging aswell).
But generally, i got it working with all i wanted.
The schedule task seems to be firing every minute like intended. My service does not seem to be getting fired though.
I am getting the following errors. Any ideas?
System.NullReferenceException
Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 148
at LightInject.Web.PerWebRequestScopeManager.getCurrentScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 129
at LightInject.ScopeManager.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6325
at LightInject.ServiceContainer.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 2587
at Hangfire.LightInject.LightInjecterScope..ctor(ServiceContainer container)
at Hangfire.LightInject.LightInjectJobActivator.BeginScope()
at Hangfire.JobActivator.BeginScope(JobActivatorContext context)
at Hangfire.JobActivator.BeginScope(PerformContext context)
at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.BackgroundJobPerformer.<>cDisplayClass90.
Does anyone keep getting these errors. I have had it a few times and not sure how I fixed it. I think restarting my solution or something.
I am getting it quite often now and want to get to the bottom of it.
This is related to the hangfire / LightInject stuff.
Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.TypeLoadException: Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[TypeLoadException: Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.]
Umbraco.Core.Composing.LightInject.LightInjectContainer.CreateServiceContainer() +0
Umbraco.Web.Composing.LightInject.LightInjectContainer.Create() in D:\a\1\s\src\Umbraco.Web\Composing\LightInject\LightInjectContainer.cs:24
[TargetInvocationException: Exception has been thrown by the target of an invocation.]
System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0
System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) +91
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +105
Umbraco.Core.Composing.RegisterFactory.Create() in D:\a\1\s\src\Umbraco.Core\Composing\RegisterFactory.cs:45
Umbraco.Web.UmbracoApplicationBase.GetRegister() in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:30
Umbraco.Web.UmbracoApplicationBase.HandleApplicationStart(Object sender, EventArgs evargs) in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:63
Umbraco.Web.UmbracoApplicationBase.Application_Start(Object sender, EventArgs evargs) in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:74
[HttpException (0x80004005): Exception has been thrown by the target of an invocation.]
System.Web.HttpApplicationFactory.EnsureAppStartCalledForIntegratedMode(HttpContext context, HttpApplication app) +475
System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) +118
System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) +176
System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +220
System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +303
[HttpException (0x80004005): Exception has been thrown by the target of an invocation.]
System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +659
System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +89
System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +189
I have tracked it down to when I accidentally click clean solution. Building and re-building works ok but if I clean this error is presented and its very difficult to fix up.
The only way I can seem to fix this up is by restoring in Git. Not ideal if I have already done a lot of work.
I cant seem to figure this out but it's something to do with the LightInject been in my core project.
I noticed when I do a clean there is 3 new dlls which make their way to the Web project. I am pretty sure it's related but not sure how to fix this yet. Ideally I would like to get this project cleaned up so these issues don't happen.
This Hangfire stuff has been a nightmare to get up and running. Umbraco 8 has been awesome up to now but I think it's been a little let down with the scheduled tasks. I wish this was built into the core.
So I had edited the source code of the Hangfire.LightInject project. Updated the Activator.
I then used this DLL to include in my project. I added this into my WEB project. This seamed to work but as soon as I click clean solution is Visual Studios it breaks everything as per above and I needed to restore.
To stop this happening I deleted the reference to Hangfire.LightInject.dll from my Web project and instead moved this to the Core project.
I tested and all the dependency injection stuff is still working. I also tested cleaning my solution and the website built and run fine.
I updated my blog article with a full step by step how to get up and running with this including all the issues I encountered along the way.
Scheduled tasks ARE built into the core, they don't have a lovely interface like this though.
In 99.99999999% of the time a "clean" or a "rebuild" of the solution is never necessary, you do not need to do this. A regular build WILL write the changes to the bin folder, if something doesn't appear to be working, "clean" or "rebuild" is not the answer, the answer is to review why it wasn't working. :-)
That said, to fix your problem: the 3 dlls you're missing are most likely NOT referenced in your website project, with no reference a fresh version will not be copied over when you build (probably leading you to see weird errors, prompting you to "clean" or "rebuild").
This is, by the way, a VS setup problem and not something that Umbraco is responsible for.
It's hard to give you a specific example, but you can test this easily:
Delete all the files in the bin folder of your website (locally)
Do a regular build in VS
See which dlls are missing and add a reference to them in Visual Studio
Rinse & repeat until all required dlls show up during a regular build after the bin folder was empty
I don't know if you've seen my previous replies, but you can create a class and use it, instead of recompiling Hangfire.LightInject, you don't need that project to be installed at all :-)
I did consider your comments prior. A couple of follow up points...
I never usually touch the clean solution but sometime I miss click when I am in a rush. Its just annoying when this happens and the solution breaks. That and when there's multiple people working on the same project in git it becomes a nightmare. There is always someone who clicks it then the possibility of checking in broken code.
Regarding the built in Scheduled tasks. I did look at this. With Umbraco 8 I noticed all the config had disappeared form the umbracoSettings.config. I google around and I am almost certain I read somewhere that it had been removed from Umbraco 8. That's why I moved back to Hangfire which had proven to be awesome with my Umbraco 8 projects. I was actually meaning I wish Hangfire had been built into Umbraco any actually. It's been a tried and tested feature in Umbraco 7 and from the comments around it's looks as though widely used.
I also saw you comment before regarding not needing the do the dll stuff with the Activator. I actually tried to implement it but I was getting an error. From memory I think it was saying something like the container is null when it hit my Owin startup. I fiddled about for an hour but couldn't get it to work. I ended up rolling back to the working solution which proved to have it's own issues as per this message.
Anyhow it seems to be working now.
I might give you solution another go through for completeness. It makes much more since than hacking at a dll and referencing it in my project.
Cheers for your help on all this.
Both you and Rasmus have been amazing help. I owe you both a beer if I see you at codegarden anytime.
Sure thing! Clean solution should be harmless, most of the time it is that you're missing a reference somehow and the correct dlls don't get copied in after. So it is actually a great idea to use it to test that your team has added the correct references! ;-)
In v8 we have an excellent BackgroundTaskRunner -https://our.umbraco.com/documentation/Reference/Scheduling/
However it does not come with any UI, just code. I still think that Hangfire is by far superior for this due to the UI and built in logging and retries, etc.
The scheduler you're talking about was very very old and very very silly (it would just ping a public endpoint, so if someone guessed the URL to your content importer and called it a 100 times, your site would go down).
I don't know why the Activator wouldn't work for you but it might again relate to missing references. It's working beautifully for me!
At some point I'll split out this code into an example project but don't have the time right now to make this into a nice thing :-)
If anyone is having similiar issues related to background tasks and LightInject exceptions, in combination with an umbraco cloud hosted solution, here is a very simple workaround:
Where ever I needed umbraco context it was simply a matter of using DI and the calling it as in some of the examples here.
using (UmbracoContextReference contextReference = _umbracoContextFactory.EnsureUmbracoContext())
{
}
So recently though with a few upgrades here and there I seem to be getting this error on build.
Severity Code Description Project File Line Suppression State
Error CS0122 'OwinStartupAttribute' is inaccessible due to its protection level
So wanted to find out what version of hangfire.core everyone is using because I came across a post somewhere else that confirmed a lower version somewhere in 1.6 would fix this error. I am hoping downgrading is not the only option.
Hangfire dependency injection
Hi!
I'm using Hangfire with Umbraco 8 and followed this post to set it up: https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/
I'm using LightInject and now trying to inject a custom service in my scheduled job class. Like so:
However, this would require me to pass IImportService as a parameter in my OwinStartup class when creating a new instance of ImportValuesJob, and so far I have found no way to access my service.
Is there a different, clever way of accomplishing this?
Thanks!
Hi I had the same problem, but I solved it yesterday.
First set upp Hangfire with dependency incjection in UmbracoDefaultOwinStartup.Configuration (I'm using Autofac and with package Hangfire.Autofac):
Than add jobs like this:
Hi Andreas
Are you using Autofac alongside Umbraco 8's LightInject implementation (and is that even possible??)?
The reason I ask is, that if I change the dependency resolver to Autofac, the backoffice doesn't work. My guess is that the backoffice is using dependencies registered in umbracos LightInject implementation, and that they are now missing in Autofac.
I also found that i have to register umbraco's services in Autofac setup to be able to use them in my hangfirejob. Have you found an elegant solution to this, where you can reuse the registrations already done by Umbraco?
Thanks
Hi Judy, the advise would be similar to the advise here: register your importservice into dependency injection and then you're good to go: https://our.umbraco.com/forum/extending-umbraco-and-using-the-api/96310-umbraco-8-package-rewrite-some-help-please#comment-304955
So something like:
Hey Sebastiaan
There is something here where im missing a link.
So its the same case as the thread here is about, using DI, with Hangfire, but it just dosnt work, and im gussing its because im doing it wrong :)
So I have a ton of services working in my project with DI. Hope you can help me. The setup is like this:
MyService:
Composer:
OwinStartup
But this gets me a "parameterless constructor not found" on the OwinStartup.
I cant seem to figur out how to use my DI services with hangfire. Ive tried some packages like :
Couldnt seem to get any of it to work really.
We still use Umbraco 7 (latest version).
I had to add this:
The default LightInject scope management in Umbraco 8 will give you issues if you don't stick to Singleton services in your Hangfire jobs. as it relies on HttpContext to store the active scope.
We have done workarounds to get around the issue, but it isn't exactly the most beautiful code I have ever seen.
but here are the basic steps to get Hangfire hooked up.
You can get the LightInject ServiceContainer by using the following inside an IComposer Compose method.
You can copy the code from the source repository for Hangfire.LightInject to your project and compile it against the embedded verison of LightInject inside Umbraco.
We hookup the Hangfire Config inside the an IComposer something like the following, you can't use this exactly as is but it will give you an idea, ours is a little convoluted as we have parts that are .net standard and .net framework so we use the DI as if it was based on the .NET Core IServiceProvider interface, to be ready for the shift when it comes:
Last part is to override the UmbracoDefaultOwinStartup, remember to replace it in web.config as well.
Hey Thomas,
Thank you for getting back to me, it gives me something to go on.
So Im hitting a wall here. Ive installed the "Hangfire.LightInject" package, it matches our umbraco 8 solution just fine. So unless your modding the hangfire.lightinject package.
The things ive done so far is to move the config of hangfire into an IUserComposer like this:
And in my owinStartup im doing this:
}
So hangfire spins up just fine, and my job is registered, when im fireing my job it give me this error:
My service still looks like this:
{ private readonly OtherService _otherService;
}
yep, thats caused by not having HttpContext for the scope creation in Hangfire, we faked the HttpContext before any scope access, during scope creation and scope disposal,
That means you will need to source from Hangfire.LightInject and modify it to create a fake http context before any scope access or component activation.
or resort to a seperate container for Hangfire usage, but that kind of defeats the point.
ahh okay, i was missing the point of where the httpcontext was missing for lightinject. Yeah i thought of doing it all in seperate container but as you say that defeats the whole purpose.
I just added the hangfire.lightinject to my project, so i can customize it. Do you have anything to share here?
I have been talking to Shannon about the issue, hopefully we can get something done about it, as there are multiple better ways to store the scope today than inside the HttpContext.Items collection.
What we have done is fake a http context inside LightInjectServiceProvider.GetService() LightInjectServiceProvider.Dispose()
LightInjectServiceScope.Dispose()
With code like this before any code inside the methods mentioned above.
do note that you can end up with scopes not being disposed if the thread changes during the execution of Hangfire jobs, its a risk we are willing to take, but it depends on your requirements and usage.
Oh sorry
In your code that only uses Hangfire.LightInject
the naming is different.
Awesome Thomas!
IT WORKS :D
For people comming here to look for a solution, as you can see its a bit messy. It should work if you follow my sudo files above. And you have to pull in Hangfire.LightInject into your project(or build it on the sideline), and here your file LightInjectJobActivator.cs Should look like this:
A giant thank you to Thomas.
Hi Rasmus,
to the owin file.I've followed (most of) your example but now getting an error
I didn't follow the step of putting the config of Hangfire into an IUserComposer but instead added
Note that I followed this example to install and configure Hangfire.
(this is all new to me and trying to keep it as simple as possible)
I want to run some C# code to export data on demand or on a schedule.
It seems like the LightInjectActivator is not initializing properly but I'm not completely sure.
Do you have any suggestions?
Thanks, Steve
Hey Steve!
Thanks for reaching out! Did you implement your own version of Hangfire.Lightinject where you fake the httpcontext? As Thomas suggests above? Because that was what did the trick for me.
Hi Rasmus,
Thanks for your reply!
I downloaded the Hangfire.LightInject source from GitHub and replaced the contents of LightInjectJobActivator.cs with your code above, built the project on the side and put the .dll into the Umbraco 8 project.
Hangfire had been operating correctly with simple tasks (context.WriteLine... ) so it seems like it is basically set up properly however I think the trouble is combining the code from Sabastiaan's tutorial with the
GlobalConfiguration.Configuration.UseLightInjectActivator(container);
code to use LightInject.
Any ideas of how to correctly add that code or change the Hangfire configuration to allow use of the 'UseLightInjectActivator'?
Sorry if my terminology is a little off - this is all new to me!
Steve
Good morning Steve :)
NO worries about terminology, let worry about a solution 🤓👍
SO lets try an compare setups, im running an umbraco 8.1.3.
Yes Hangfire it self works, if you dont touch anything Umbraco related and Dependency Injection related. Correct?
The issues I had, the first one was getting it to work with DI(Dependency Injection). To Fix this we are using the Hangfire.LightInject package, but the other issue is that if we touch any Umbraco stuff, like the contentCache, we get another error, because Hangfire is running in a not so weblike context and Umbraco is dependent on the httpContext.
So for it to work we have to fake the httpcontext, and this is why we have to roll our own version of Hangfire.LightInject where we change the code inside LightInjectJobActivator.
My file looks like this:
So now we should have some components that works, now we need to initate everything correctly. In my setup, I have this process split into 2 areas, my OwinStartup.cs and my HangfireJobsComposer.cs
HangfireJobsComposer.cs
OwinStartup.cs
So this is my setup for hangfire. Now lets look at my InvitationJobs.
And again my invitationJobs is also registered in my hangfireJobComposer.
Hope this gets you futher :)=
Hi Rasmus,
Thanks for taking the time to write up this very thorough reply!
I've got Umbraco 8.2.1 installed and everything with Hangfire seems to be running correctly however in the tasks that I need to perform require the HttpContext so I'm pursuing this thread with keen interest.
Thanks for all of the code.
I've added to the project and working on integrating with what I've already got set up, including resolving a few minor glitches.
Learning more about Umbraco 8 by the minute!
Thanks, Steve
Hi Rasmus,
I followed everything in your code as best as I could but still seem to be getting a NULL error related to the type characteristic of the job.
I simplified InvitationJob.cs to the following.
I had to add
Otherwise was getting an error "the type or namespace 'IInvitationDbService' could not be found.."
Perhaps this is part of the problem? (DI is all new to me!)
With it though the errors in Visual Studio disappear but see this when the job runs:
I checked the Hanfire source and it appears this error has something to do with resolving the type on the job being run.
Specifically:
That's the only reference to this error I could find.
It seems to me that the issue in my job class file.
Can you send the full code of your InvitationJobs.cs file or let me know how to resolve the error that comes up on the IInvitationDbServive ?
Thanks - and I definitely owe you a🍺(or two - ).
Steve
A quick answer, sorry for the whole misunderstanding :) the invitationservice, is something from my code, its just an example of how we are using hangfire :)
So everything besides the invitationjobs is our setup for hangfire, while the InvitationJobs is one of many hangfire scheduled tasks running in our setup. I will just rewrite the example for you so the interface and DI wont confuse :)
And again no worries about the being new to DI and Umbraco 8, same here, just started in august with umb8 and DI.
This could be your job class instead of the our invitationJobs:
A thing to notice, i dont know if you have the Hangfire.Console package installed? With this you can do as above, adding in the console message.
Now in OwinStartup it should look like this:
OwinStartup.cs
Hope this makes it a bit more clear :) Else please share you job class and owin startup up, assuming the rest is already in place. Also ensure that you hook up your new job class inside the HangfireJobsComposer as this
:D A beer is always welcome :) Lets get this thing fixed for you. If this dosnt work, lets go another around the christames tree, and try to describe to me what it is you want to achive, or we could a quick skype session.
Hi Rasmus,
Thanks for yet another detailed reply and your patience working through this.
I've now got it working with access to the Http context in the Hangfire job.
The solution in my case was just to have an empty class constructor for the job.
I'm not using any DI components or other services right now but need to learn how that works. For now it is empty like above and job is running smoothly.
Thanks again for all of your help - and let me know how to buy you a beer! :)
Steve
Wuhuu awesome :)
Got there in the end!
Thanks Rasmus.
For me I was having issues with LightInjectJobActivator stuff. I was attempting all sorts of stuff. Installing Hangfire.LightInject through nuget, navigating referencing issues then copying the new LightInjectJobActivator classes into my project.
I later realised none of this is required.
With the LightInjectJobActivator stuff.
Download the package source from https://github.com/sbosell/Hangfire.LightInject
Replace the LightInjectJobActivator.cs with the contents in Ramus' example.
Build this then use the Hangfire.LightInject from this project and include it in your umbraco project.
Once this is done you can pretty much copy and paste the rest of the stuff in Ramus' example.
I plan to write a blog for this end to end starting with the hangfire install. There was a lot of scrolling up and down and referencing other forum posts.
I will pop it all in one place.
I've just been experimenting with this, finally, after reading all these posts.
You don't even need to do all that, you can tell Hangfire to use any JobActivator you want. My composer looks like this:
Note the
.UseActivator(new LightInjectJobActivator(container));
at the end.For the
LightInjectJobActivator
class I used (and refactored) Rasmus' version. I'm not sure if the URL in there should point to an actual website though? Seems to work as it is.. I actually am not even sure why all these hoops are necessary, was it because you need to be able to use theContentService
and we need to wait for an Umbraco Context to exist first? Currently I'm not doing anything withContentService
so I am not sure I need the activator to be like this.My version just removes a bit of repetition of that Http Context initialization:
And for completion's sake, here's my
OwinStartup
class. The combination of these three should get you set up.My only problem is that I need 1 Service to be injected into this
OwinStartup
class so I can make a list of jobs where just 1 variable changes based on a list of string (that list comes from the Service). But oh well, I can live with newing up that single Service.I am running into a weird issue here. When my task runs, I get a System.NullReferenceException at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() at first. After a varying number of retries, it works fine. I have seen it work after between 1-3 retries, but never on the first try.
Also, my breakpoint is being hit in LightInjectJobActivator.cs where it manually sets HttpContext.Current, and it sets it, but by the time I stop at a breakpoint inside my task, HttpContext.Current is back to being null.
My task is injecting the following dependencies: IUmbracoContextFactory, ILogger, IContentService.
Any idea why this might be? As far as I can tell, my code follows Sebastiaan's code above exactly.
Thanks for sharing Sebastiaan #h5yr It works great to me injecting the following
Hey Sebastiaan and everyone else :)
I know this is a while back. But have you ever tried this code with the MembershipHelper injected into a service that is used by in a job?
I have a problem relating to this, and it seems to come down to the access of umbraco's cache.
I have an build a cachehandler (using umbracos buildin cache) and injected it into a service and also inject the MemberShipHelper (which as far as I can see also uses cache).
If I remove those 2 dependencies in my service, the service and found by the LightInjectJobActivator (of course runs with failure, since the two dependencies are needed). But never the less runs. As soon as I inject the 2 dependencies, it fails during:
Can see it fails in WebInitialComposer:
Hope you have some insight to share :)
Hi all, I know this is fairly olf but just found this code used somewhere.
Could we not do this:
Because it doesn't seem good to have this string
Actually i found another "issue" with DI/LightInject and Hangfire.
In some of my services I call a membership helper to get an ipublishedcontent of a member.
I do this like this:
Normally this works in every other situation, but when used through hangfire and LightInject i get a Object not set error like this:
In umbraco 7, when we needed this we had to ensure an umbraco context, gussing its something like this aswell, just like we are faking the httpcontext, but cant figure out how.
you properly need to constructor inject the IUmbracoContextFactory and
use it like
if you need the MembershipHelper you should inject the MembershipHelper into the constructor of your service and rely on then code we did earlier today to fake the context.
When you access the Current.Factory you are going around that work, and have to fake the context again.
but accessing the Current.Factory is an anti pattern in DI terms, as you dont rely on the lift time scopes and other stuff provided by the IoC container.
Its going back to the Service Locator Pattern and you should try to avoid that.
Hey again Thomas,
im starting to owe you a beer ;)
So, ive tried 2 things, one thing works, but i shouldnt use it because it defeats the purpose of the DI as you say. The other way around dosnt work.
So this works: My constructor
The method :
The above didnt work before but i was missing the ensureumbracocontext.
The way i want to do it is this, but this dosnt work: My constructor
The method
Its like im not allowed to inject the MembershipHelper, ive been looking into the source code and trying to find a service or something that can help me here to get ipublished member content, but nothing seems to work correct.
I think LightInject also support Func factories like
But this could get you back to the old PerWebRequestScopeManagerProvider issue
Think i replyed to the wrong message : https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection#comment-313144
Hey Thomas,
Hope you can help me a bit here again, thought it was working but not a 100%, so hopin you have another input.
So im trying your above solution like this :
The wierd thing is that the member itself is being pulled out, but not all the properties on the member, they return a NullException. The Umbraco.Models.MyMemberModel is just the generated modelsbuilder model.
(update) Any ideas why the property binding is broken? Something with NuCache? The wierd thing is, if I step into the code, and step throught it, then sometimes the properties actually comes out.
A stack trace, just for the "fun" of it :)
at Umbraco.Web.Templates.TemplateUtilities.ParseInternalLinks(String text, Boolean preview, UmbracoContext umbracoContext) in d:\a\1\s\src\Umbraco.Web\Templates\TemplateUtilities.cs:line 22 at Umbraco.Web.PropertyEditors.ValueConverters.TextStringValueConverter.ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, Object source, Boolean preview) in d:\a\1\s\src\Umbraco.Web\PropertyEditors\ValueConverters\TextStringValueConverter.cs:line 32 at Umbraco.Core.Models.PublishedContent.PublishedPropertyType.ConvertSourceToInter(IPublishedElement owner, Object source, Boolean preview) in d:\a\1\s\src\Umbraco.Core\Models\PublishedContent\PublishedPropertyType.cs:line 208 at Umbraco.Web.PublishedCache.NuCache.Property.GetInterValue(String culture, String segment) in d:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\Property.cs:line 192 at Umbraco.Web.PublishedCache.NuCache.Property.GetValue(String culture, String segment) in d:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\Property.cs:line 211 at Umbraco.Web.PublishedPropertyExtension.Value[T](IPublishedProperty property, String culture, String segment, Fallback fallback, T defaultValue) in d:\a\1\s\src\Umbraco.Web\PublishedPropertyExtension.cs:line 39 at Umbraco.Web.PublishedContentExtensions.Value[T](IPublishedContent content, String alias, String culture, String segment, Fallback fallback, T defaultValue) in d:\a\1\s\src\Umbraco.Web\PublishedContentExtensions.cs:line 155
So i found out that if i loop over all the properties before mapping it to my modelsbuilder model it worked. #hacky
Great you found a solution, but not so great that we have to hack our way around stuff because of a Scope Manager in Umbraco
Hi Rasmus,
Did you every figure out a more solid solution for this?
I have been experiencing the exact same issue. Your comment above also seemed to work for me bit I am just worried about other problems might come of the back of this.
No sadly not :(
But we ended up dishing Umbraco members for the parts we needed, but our case is not comparable though :(
But for the time we used it, it worked for us, but we needed to loop over the properties, and if you debugged into this, you could see that the properties are in the "Properties" list, but still cannot get mapped.
My hunt for the holy hack grail stopped here hehe :)
I have also found another way to get around the HttpContext issue but its not fully tested yet.
by replacing the ScopeManagerProvider on the Container we can use another scope manager when there is no HttpContext.
and then assigning this as the ScopeManagerProvider on the container
This is in no way tested so be careful using it.
Hey Thomas,
Awesome stuff, I just tested it and its a no go or im doing it wrong.
Hi, i have an idea.
What if you used hangfire as a webhook only?
So instead of running methods in a hangfire event, then only call a url with authentication?
Then you can make an UmbracoApi and get a full context thread?
Its a great idea for those who can live with stuff only running in a web context, and dosnt take long time to run, and also you dont want feedback inside of hangfire.
The main idea we use hangfire for is longer running tasks, something that might exceed the webserver process lifetime, also we use the logs on hangfire aswell(of course with other logging aswell).
But generally, i got it working with all i wanted.
Hi Guys,
I have followed the examples about.
My InvitationJobs looks something like this.
public class InvitationJobs { private readonly ISiteService _siteService;
The schedule task seems to be firing every minute like intended. My service does not seem to be getting fired though.
I am getting the following errors. Any ideas?
System.NullReferenceException Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object. at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 148 at LightInject.Web.PerWebRequestScopeManager.getCurrentScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 129 at LightInject.ScopeManager.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6325 at LightInject.ServiceContainer.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 2587 at Hangfire.LightInject.LightInjecterScope..ctor(ServiceContainer container) at Hangfire.LightInject.LightInjectJobActivator.BeginScope() at Hangfire.JobActivator.BeginScope(JobActivatorContext context) at Hangfire.JobActivator.BeginScope(PerformContext context) at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.BackgroundJobPerformer.<>cDisplayClass90.
So lets go through the checks :)
Did you do the follwing:
Please share :)
Hi Guys,
As promised I wrote a blog article to put all this together in one place.
https://www.umbrajobs.com/blog/posts/2020/january/umbraco-8-scheduled-tasks-with-hangfire-dependency-injection/
I think you can follow this guide and get up and running pretty easily if you closely follow each step.
(Credit: Sebastiaan Janssen) https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/
And obviously (Credit: Rasmus Fjord)
Hope this helps someone. It's took me a fair while to get up and running. Just hope this speeds the process up for you.
Regards
David
Hey Peeps,
Does anyone keep getting these errors. I have had it a few times and not sure how I fixed it. I think restarting my solution or something.
I am getting it quite often now and want to get to the bottom of it.
This is related to the hangfire / LightInject stuff.
Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.TypeLoadException: Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[TypeLoadException: Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.] Umbraco.Core.Composing.LightInject.LightInjectContainer.CreateServiceContainer() +0 Umbraco.Web.Composing.LightInject.LightInjectContainer.Create() in D:\a\1\s\src\Umbraco.Web\Composing\LightInject\LightInjectContainer.cs:24
[TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0 System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) +91 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +105 Umbraco.Core.Composing.RegisterFactory.Create() in D:\a\1\s\src\Umbraco.Core\Composing\RegisterFactory.cs:45 Umbraco.Web.UmbracoApplicationBase.GetRegister() in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:30 Umbraco.Web.UmbracoApplicationBase.HandleApplicationStart(Object sender, EventArgs evargs) in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:63 Umbraco.Web.UmbracoApplicationBase.Application_Start(Object sender, EventArgs evargs) in D:\a\1\s\src\Umbraco.Web\UmbracoApplicationBase.cs:74
[HttpException (0x80004005): Exception has been thrown by the target of an invocation.] System.Web.HttpApplicationFactory.EnsureAppStartCalledForIntegratedMode(HttpContext context, HttpApplication app) +475 System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) +118 System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) +176 System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +220 System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +303
[HttpException (0x80004005): Exception has been thrown by the target of an invocation.] System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +659 System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +89 System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +189
Further to this error.
I have tracked it down to when I accidentally click clean solution. Building and re-building works ok but if I clean this error is presented and its very difficult to fix up.
The only way I can seem to fix this up is by restoring in Git. Not ideal if I have already done a lot of work.
I cant seem to figure this out but it's something to do with the LightInject been in my core project.
I noticed when I do a clean there is 3 new dlls which make their way to the Web project. I am pretty sure it's related but not sure how to fix this yet. Ideally I would like to get this project cleaned up so these issues don't happen.
This Hangfire stuff has been a nightmare to get up and running. Umbraco 8 has been awesome up to now but I think it's been a little let down with the scheduled tasks. I wish this was built into the core.
So I actually figured out how to fix this...
So I had edited the source code of the Hangfire.LightInject project. Updated the Activator.
I then used this DLL to include in my project. I added this into my WEB project. This seamed to work but as soon as I click clean solution is Visual Studios it breaks everything as per above and I needed to restore.
To stop this happening I deleted the reference to Hangfire.LightInject.dll from my Web project and instead moved this to the Core project.
I tested and all the dependency injection stuff is still working. I also tested cleaning my solution and the website built and run fine.
I updated my blog article with a full step by step how to get up and running with this including all the issues I encountered along the way.
https://www.umbrajobs.com/blog/posts/2020/january/umbraco-8-scheduled-tasks-with-hangfire-dependency-injection/
Regards
David
Scheduled tasks ARE built into the core, they don't have a lovely interface like this though.
In 99.99999999% of the time a "clean" or a "rebuild" of the solution is never necessary, you do not need to do this. A regular build WILL write the changes to the bin folder, if something doesn't appear to be working, "clean" or "rebuild" is not the answer, the answer is to review why it wasn't working. :-)
That said, to fix your problem: the 3 dlls you're missing are most likely NOT referenced in your website project, with no reference a fresh version will not be copied over when you build (probably leading you to see weird errors, prompting you to "clean" or "rebuild").
This is, by the way, a VS setup problem and not something that Umbraco is responsible for.
It's hard to give you a specific example, but you can test this easily:
I don't know if you've seen my previous replies, but you can create a class and use it, instead of recompiling
Hangfire.LightInject
, you don't need that project to be installed at all :-)https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection#comment-316038
Hi Sebastiaan,
Thanks for your quick response.
I did consider your comments prior. A couple of follow up points...
I never usually touch the clean solution but sometime I miss click when I am in a rush. Its just annoying when this happens and the solution breaks. That and when there's multiple people working on the same project in git it becomes a nightmare. There is always someone who clicks it then the possibility of checking in broken code.
Regarding the built in Scheduled tasks. I did look at this. With Umbraco 8 I noticed all the config had disappeared form the umbracoSettings.config. I google around and I am almost certain I read somewhere that it had been removed from Umbraco 8. That's why I moved back to Hangfire which had proven to be awesome with my Umbraco 8 projects. I was actually meaning I wish Hangfire had been built into Umbraco any actually. It's been a tried and tested feature in Umbraco 7 and from the comments around it's looks as though widely used.
I also saw you comment before regarding not needing the do the dll stuff with the Activator. I actually tried to implement it but I was getting an error. From memory I think it was saying something like the container is null when it hit my Owin startup. I fiddled about for an hour but couldn't get it to work. I ended up rolling back to the working solution which proved to have it's own issues as per this message.
Anyhow it seems to be working now.
I might give you solution another go through for completeness. It makes much more since than hacking at a dll and referencing it in my project.
Cheers for your help on all this.
Both you and Rasmus have been amazing help. I owe you both a beer if I see you at codegarden anytime.
Regards
David
Sure thing! Clean solution should be harmless, most of the time it is that you're missing a reference somehow and the correct dlls don't get copied in after. So it is actually a great idea to use it to test that your team has added the correct references! ;-)
In v8 we have an excellent
BackgroundTaskRunner
-https://our.umbraco.com/documentation/Reference/Scheduling/However it does not come with any UI, just code. I still think that Hangfire is by far superior for this due to the UI and built in logging and retries, etc.
The scheduler you're talking about was very very old and very very silly (it would just ping a public endpoint, so if someone guessed the URL to your content importer and called it a 100 times, your site would go down).
I don't know why the Activator wouldn't work for you but it might again relate to missing references. It's working beautifully for me!
At some point I'll split out this code into an example project but don't have the time right now to make this into a nice thing :-)
Thanks for pointing the BackgroundTaskRunner out. I really didn't know about that.
That's a viable solution for a lot of people. Sometimes I don't need the logging anyway.
If anyone is having similiar issues related to background tasks and LightInject exceptions, in combination with an umbraco cloud hosted solution, here is a very simple workaround:
https://github.com/umbraco/Umbraco-CMS/issues/9164#issuecomment-709222714
2020 is almost over, but this article still helps! Thank you all!
I followed Sebastiaan's example at first
https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/
and this with custom LightInjectJobActivator:
https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection#comment-316037
But run into problems with null exceptions. I am using Umbraco's MembershipService.
After I followed Rasmus' example (line by line):
https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection#comment-314585
I found out that I was missing
in my Composer.Compose method.
And all is working now! YES!
h5yr to all of You!
Best regards
Tomasz
Thanks All, I managed to get Hangfire working with DI and have access to the IUmbracoContextFactory
How do I access UmbracoHelper to get to GetDictionaryValue ?
Super good that you got it working. I think this might help you, I remember that another thread talked about this.
https://our.umbraco.com/forum/extending-umbraco-and-using-the-api/93790-access-umbracocontext-in-hangfire
Hi Rasmus,
Thanks for your quick reply
I am missing something here;
I can access the content and media, but from here I cannot access the culture and dictionary
Hmm I dont think i can help there sadly :( Is it Umbraco 8 ? What version? Then we can maybe look it up in the codebase.
Something like GetDictionaryValue somewhere, maybe on Content?
Yes sir, U8 and thank you, you reminded me that when in doubt go an read the code.
The Dictionary can be accessed via DI in ICultureDictionaryFactory
and then
Super great ! :D
So I followed Sebastiaan's example as well early 2019 and its been working well without the light inject stuff.
https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/
Where ever I needed umbraco context it was simply a matter of using DI and the calling it as in some of the examples here.
So recently though with a few upgrades here and there I seem to be getting this error on build.
So wanted to find out what version of hangfire.core everyone is using because I came across a post somewhere else that confirmed a lower version somewhere in 1.6 would fix this error. I am hoping downgrading is not the only option.
Any suggestions?
Hi Francis,
I'm using Hangfire 1.7.22, Umbraco 8.13 and I've also followed Sebastiaan's example. And all is working fine.
But I had to implement the LightInjectJobActivator.
Regards Tomasz
Thanks for the reply Tomasz. In your version that class is still marked as internal. I will play around a bit and see what I come up with
Turned out my problem was my Microsoft.Owin version in my core project
Hi,
I am getting this error on runtime. How to resolve this.
@Bishwajit As noted in https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection#comment-324416
If you're on Cloud, try setting
UmbracoCloud.LiveEditingEnabled
and set it tofalse
. If you're not on Cloud then I have no idea unfortunately.is working on a reply...