I'm migrating a project from 7.X to 8 in order to familiarize myself with this brand new Umbraco version!
Happens that this project relies on background jobs (using Hangfire) for some recurring tasks, and those tasks need to access IPublishedContent.
In the previous version faking, an UmbracoContext was somewhat easy with a EnsureUmbracoContext method that would create a context using UmbracoContext.EnsureContext if UmbracoContext.Current was null and it worked pretty well.
With Umbraco8 and all the changes under the hood I have yet to figure out a way to access/create a UmbracoContext and/or IUmbracoContextAccessor for those cases or event setup a LightInject container within my Hangfire server to with all the dependencies resolved.
I have been fiddling in Umbraco source code in order to try to figure a way but without success so far.
Can anybody point me in the right direction?
Thanks
P.S.: I know that an easy way to solve this would be to have an API endpoint and make my background job call that endpoint, solving my issue. But I'm not interested in such inelegant solution.
So I've been playing with this on my spare time and while I now have hangfire working based on the blog you shared I'm still blocked.
When in jobs (or registering the jobs) Umbraco.Web.Composing.Current has some of the key elements to null (UmbracoContext, UmbracoHelper, ...) and this is kind of expected, since there is no HttpRequest tied to them.
But Umbraco.Core.Composing.Current has all the values set, and the Factory returns a lot of instances. But the one I really want is not there.
I really would like my background job to access IPublishContent, for example, I have a settings DocType/Node with some settings that I need to access so I really would like a way to load that Node from NuCache so I know that I have what is actually published (and not IContent that might have changes that I don't want, since they are not published).
I probably could use IContent and the versions to find the latest one that is/was published (?) ... but is this the better/only way to do it?
I believe Umbraco.Core.Composing.Current should have a way to access the an instace of IPublishedCache.
I'm not sure that my response is the correct one, but this is how I solved that issue:
Umbraco.Core.Composing.Current.Factory is your container.
So:
Current.Factory.GetInstance<ILogger>()
Will give you the logger.
I created a separate Composition for my Hangfire, because I want some services to have a different lifespan than in Umbraco.
I also created a CustomJobActivator and told Hangfire about it.
The sample code looks something like this:
// in your hangfire setup function
GlobalConfiguration.Configuration.UseActivator(new UmbracoJobActivator(GetFactoryForJobComposition()));
...
}
private IFactory GetFactoryForJobComposition()
{
// This is needed because we want to use a different IoC container than the one we use in Umbraco, specially because of the lifetime of certain objects
var composition = new Composition(RegisterFactory.Create(), Current.TypeLoader, Current.ProfilingLogger, Current.RuntimeState);
composition = RegisterCompositionServices(composition);
return composition.CreateFactory();
}
private Composition RegisterCompositionServices(Composition composition)
{
// because i want logging in my jobs
composition.Register(_ => Current.Factory.GetInstance<ILogger>());
// because i want profiling logging in my jobs
composition.Register(_ => Current.Factory.GetInstance<IProfilingLogger>());
// here you should register anything else you might need
// now register your jobs
composition.Register(typeof(SomeHangFireJob));
return composition;
}
// Job Activator class declaration
internal class UmbracoJobActivator : JobActivator
{
private readonly IFactory Factory;
public UmbracoJobActivator(IFactory factory)
{ Factory = factory; }
public override object ActivateJob(Type jobType)
{
return Factory.GetInstance(jobType);
}
}
Bear in mind that Current is Umbraco.Core.Composition.CurrentnotUmbraco.Web.Composition.Current
With this, all my jobs are activating correctly and the injection is working perfectly.
More details: the UmbracoContext is internally managed by an IUmbracoContextFactory - in most cases, you should not bother about it, but in your case, it's going to help.
There is, unfortunately, no Current.UmbracoContextFactory at the moment, though we may add it. So you will have to get the factory via:
var umbracoContextFactory = Current.Factory.GetInstance<IUmbracoContextFactory>();
(that is, assuming you cannot inject it - of course, ideally, you should inject it)
That factory can give you a reference to an UmbracoContext:
using (var contextReference = umbracoContextFactory.EnsureUmbracoContext())
{
var umbracoContext = contextReference.UmbracoContext;
}
What happens is: if there already is a "current" UmbracoContext, the reference will point to it. Otherwise, a new one will be created, and is registered as the curent one - until the reference is disposed, and then the context is disposed and is not current anymore.
Therefore, this is safe to use both within normal requests, and within background tasks.
You can access the context via the contextReference.UmbracoContext property but, since it's also the current one, Current.UmbracoContext will return it, and anything that requires a context will work.
Then, you can directly access umbracoContext.ContentCache to retrieve whatever you need.
I had trouble setting up Hangfire's IDashboardAuthorizationFilter for v8, so I wanted to share the code that ended up working for me:
public class UmbracoAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
var auth = new HttpContextWrapper(HttpContext.Current).GetUmbracoAuthTicket();
UmbracoBackOfficeIdentity user = UmbracoBackOfficeIdentity.FromClaimsIdentity(auth.Identity);
if (user.Roles.Contains("admin"))
{
return true;
}
return false;
}
}
The key part is UmbracoBackOfficeIdentity.FromClaimsIdentity(auth.Identity). In v8, from the context of Owin startup, the type you get back from GetUmbracoAuthTicket().Identity is System.Security.Claims.ClaimsIdentity, which doesn't contain a Roles property. That line will convert it to the Umbraco-specific type that does contain Roles.
Anyone who comes here with DI related issues trying to use the built in Light Inject container for Umbraco v8 this thread here talks about various ways around the DI issues with things like HTTP context not being available:
I had a strange issue where Hangfire installed but the dashboard could not load any css or js, despite these being available if I navigated to their URLs.
In that case, the fix was to add hangfire to the reserved paths in Umbraco. In the Web.Config:
Umbraco 8 and Hangfire
Hi,
I'm migrating a project from 7.X to 8 in order to familiarize myself with this brand new Umbraco version!
Happens that this project relies on background jobs (using Hangfire) for some recurring tasks, and those tasks need to access IPublishedContent.
In the previous version faking, an UmbracoContext was somewhat easy with a EnsureUmbracoContext method that would create a context using UmbracoContext.EnsureContext if UmbracoContext.Current was null and it worked pretty well.
With Umbraco8 and all the changes under the hood I have yet to figure out a way to access/create a UmbracoContext and/or IUmbracoContextAccessor for those cases or event setup a LightInject container within my Hangfire server to with all the dependencies resolved.
I have been fiddling in Umbraco source code in order to try to figure a way but without success so far.
Can anybody point me in the right direction?
Thanks
P.S.: I know that an easy way to solve this would be to have an API endpoint and make my background job call that endpoint, solving my issue. But I'm not interested in such inelegant solution.
For the quick-and-dirty solution:
using Umbraco.Web.Composing;
and useCurrent.UmbracoContext.what_you_need
.Don't forget to
Install-Package UmbracoCms.Web
, which is a new NuGet package that we didn't have for v7.Thanks for the quick reply! H5YR!
I will definitively try that.
But, will the UmbracoContext be setup in this case? Since there is no HttpRequest associated with my Job?
Well, going to try that out and will be back with an update.
Thanks again!
Yeah.. that is a really good question and I'm not sure of that either. Give it a go and see how far you get.
For future improvements I'm curious as to what exactly you're trying to access that need a request context, might be able to figure it out with you!
Might be of interest, just blogged about using Hangfire with Umbraco 8: https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/
Yes thank you, somebody actually already linked that blog post in our slack. And its exactly what I needed! Marking your answer as the accepted one.
Cheers!
Hey Sebastian,
So I've been playing with this on my spare time and while I now have hangfire working based on the blog you shared I'm still blocked.
When in jobs (or registering the jobs) Umbraco.Web.Composing.Current has some of the key elements to null (UmbracoContext, UmbracoHelper, ...) and this is kind of expected, since there is no HttpRequest tied to them.
But Umbraco.Core.Composing.Current has all the values set, and the Factory returns a lot of instances. But the one I really want is not there.
I really would like my background job to access IPublishContent, for example, I have a settings DocType/Node with some settings that I need to access so I really would like a way to load that Node from NuCache so I know that I have what is actually published (and not IContent that might have changes that I don't want, since they are not published).
I probably could use IContent and the versions to find the latest one that is/was published (?) ... but is this the better/only way to do it?
I believe Umbraco.Core.Composing.Current should have a way to access the an instace of IPublishedCache.
Thanks
Thank you Sebastiaan,
and what about IoC dependencies injected in the hangfire job class?
I see Hangfire has Hangfire.LightInject project but I have two problems:
Hey!
I'm not sure that my response is the correct one, but this is how I solved that issue:
Umbraco.Core.Composing.Current.Factory is your container.
So:
Will give you the logger.
The sample code looks something like this:
Bear in mind that
Current
isUmbraco.Core.Composition.Current
notUmbraco.Web.Composition.Current
With this, all my jobs are activating correctly and the injection is working perfectly.
Quick answer: there is a non-hackish way to access the front-end cache from within a background task - going to document it soon as I can.
Thank you Stephan!
I'll be waiting for your answer!
More details: the
UmbracoContext
is internally managed by anIUmbracoContextFactory
- in most cases, you should not bother about it, but in your case, it's going to help.There is, unfortunately, no
Current.UmbracoContextFactory
at the moment, though we may add it. So you will have to get the factory via:(that is, assuming you cannot inject it - of course, ideally, you should inject it)
That factory can give you a reference to an
UmbracoContext
:What happens is: if there already is a "current"
UmbracoContext
, the reference will point to it. Otherwise, a new one will be created, and is registered as the curent one - until the reference is disposed, and then the context is disposed and is not current anymore.Therefore, this is safe to use both within normal requests, and within background tasks.
You can access the context via the
contextReference.UmbracoContext
property but, since it's also the current one,Current.UmbracoContext
will return it, and anything that requires a context will work.Then, you can directly access
umbracoContext.ContentCache
to retrieve whatever you need.Making sense?
Yes! This should do the trick!
I'm going to try it later this week and will give you feedback then!
Thanks a lot for your help!
Works flawlessly! Thank you so much!
Hi Tiago!
I've looked at the samples that you provided, as far as I understand you need to read content from the cache in one of you jobs right?
Did you manage to get this working inside the job? Would you say that it's stable and "running fine"?
I would love know more about how you did this since I'm faced with the exact same issue in a project right now.
Any points in the right direction would be very much appreciated.
Cheers!
I had trouble setting up Hangfire's IDashboardAuthorizationFilter for v8, so I wanted to share the code that ended up working for me:
The key part is
UmbracoBackOfficeIdentity.FromClaimsIdentity(auth.Identity)
. In v8, from the context of Owin startup, the type you get back fromGetUmbracoAuthTicket().Identity
isSystem.Security.Claims.ClaimsIdentity
, which doesn't contain aRoles
property. That line will convert it to the Umbraco-specific type that does containRoles
.https://github.com/guru-digital/umbraco-hangfire
Attention
Anyone who comes here with DI related issues trying to use the built in Light Inject container for Umbraco v8 this thread here talks about various ways around the DI issues with things like HTTP context not being available:
https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection
I had a strange issue where Hangfire installed but the dashboard could not load any css or js, despite these being available if I navigated to their URLs.
In that case, the fix was to add hangfire to the reserved paths in Umbraco. In the Web.Config:
is working on a reply...