Hi,
I am trying to figure out some v8 stuff, including how to execute background jobs that want to read some Umbraco data as IPublishedContent. I was happy to see v8 provides an IUmbracoContextAccessor and a IPublishedSnapshotAccessor, either of which would probably give me access to IPublishedContent.
However, it seems that both of those will not work properly when used on application boot in an IComponent and I was wondering if this is a bug or expected behavior. If this is expected behavior and I should not be using an IComponent like that, then please guide me in how I should be doing this.
I was checking Umbraco.Web.Scheduling.SchedulerComponent for inspiration but it uses the IContentService instead which is not what I would prefer. Surely the PublishedContentCache should also be available outside of a request in one way or another?
Especially since Umbraco now contains the HybridUmbracoContextAccessor class which is referenced in the WebRuntimeComposer with the explicit comment mentioning situations without HttpContext I was hoping it would "just" work now:
// register the http context and umbraco context accessors
// we should use the HttpContextUmbracoContextAccessor, however there are cases when
// we have no http context, eg when booting Umbraco or in background threads, so instead
// let's use an hybrid accessor that can fall back to a ThreadStatic context.
composition.RegisterUnique();
UmbracoContext / IPublishedSnapshot are both null in an implementation of IComponent at its Initialize() method.
Tested in version 8.0.0-alpha.58.2091.
Execute this code. If I should not be using Composer / Component in this way please forgive me and tell me how it should be done instead :)
using System;
using Umbraco.Core.Components;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
namespace UmbracoV8.Features.BackgroundJobs
{
public class PerplexBackgroundJobsComposer
: ComponentComposer<PerplexBackgroundJobsComponent>, IUserComposer
{
}
public class PerplexBackgroundJobsComponent : IComponent
{
private readonly IUmbracoContextAccessor _contextAccessor;
private readonly IPublishedSnapshotAccessor _snapshotAccessor;
public PerplexBackgroundJobsComponent(
IUmbracoContextAccessor contextAccessor,
IPublishedSnapshotAccessor snapshotAccessor)
{
_contextAccessor = contextAccessor;
_snapshotAccessor = snapshotAccessor;
}
public void Initialize()
{
if (_contextAccessor.UmbracoContext is UmbracoContext umbCtx)
{
// Do something with umbCtx ...
}
else if (_snapshotAccessor.PublishedSnapshot is IPublishedSnapshot snapshot)
{
// Do something with snapshot ...
}
else
{
// :-(
throw new Exception("Cannot get IPublishedContent in any way!?");
}
}
public void Terminate()
{
}
}
}
_contextAccessor.UmbracoContext and _snapshotAccessor.PublishedSnapshot are both available and grant access to IPublishedContent. No exception is thrown.
_contextAccessor.UmbracoContext and _snapshotAccessor.PublishedSnapshot are both null, exception is thrown.
It would be great if there would be a single unified way to obtain IPublishedContent, with or without HttpContext / active request / etc. In v7 this was always painful and hacky to make work (EnsureContext shenanigans), I hope v8 can streamline this.
UmbracoContext is a web based lifetime, it is created based on an HttpContext, just like v7. It is not a singleton but there is a new singleton accessor. If you are using that, and you are not in a web context, you will get a null because it cannot exist there. Just like v7.
As for accessing the content cache outside of a web based lifetime, I'm sure that's possible but AFAIK it's simply down to current time constraints that it's not available. I could be wrong but i think that is the case. In the meantime you might have to resort to v7 tactics and fake a context, etc...
@zpqrtbnk will know more on the subject and i'll chat to him later to see what the status is about accessing the content cache outside of a web request.
That said, the umbraco context accessor will never return an umbraco context that's not in a web request because that is it's lifetime.
Thanks for the very swift reply Shannon. It makes sense there is no UmbracoContext outside of a web request indeed, I can totally understand that. I'd rather use a more focused dependency like IPublishedSnapshot or just the IPublishedContentCache to query some IPublishedContent anyway. If that would somehow be made available that would be awesome. I hope @zpqrtbnk could perhaps comment a bit on how developers should go about obtaining IPublishedContent outside of a web request.
I'm closing the issue for now.
@zpqrtbnk if you have time to comment on it, feel free to 馃槂
So... UmbracoContext is created as part of a front-end request, and creating a context creates an IPublishedSnapshot which represents a snapshot of the whole content cache - and that snapshot is attached to the UmbracoContext.
Outside of a request, you have to manage a snapshot by yourself.
using (var snapshot = publishedSnapshotService.CreatePublishedSnapshot(previewToken: null))
{
var document = snapshot.Content.GetById(preview: false, contentId: 1234);
// use the document - it's an `IPublishedContent` or a strongly-typed model
}
Here, publishedSnapshotService is an IPublishedSnapshotService which you can inject.
Making sense?
Excellent, thank you Stephan (@zpqrtbnk) for your reply. Will look into your suggested approach. Something like this is what I was hoping for would be possible in v8.
For future readers, core team is discussing a similar issue and possible solutions in #4572
Also for future readers: the code in the referenced issue works more reliably and is as follows:
// _umbracoContextFactory is an injected IUmbracoContextFactory
using (var reference = _umbracoContextFactory.EnsureUmbracoContext())
{
var content = reference.UmbracoContext.ContentCache.GetById(...)
}
Most helpful comment
So...
UmbracoContextis created as part of a front-end request, and creating a context creates anIPublishedSnapshotwhich represents a snapshot of the whole content cache - and that snapshot is attached to theUmbracoContext.Outside of a request, you have to manage a snapshot by yourself.
Here,
publishedSnapshotServiceis anIPublishedSnapshotServicewhich you can inject.Making sense?