Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v8: Reading IPublishedContent in a background task fails #4442

Closed
PerplexDaniel opened this issue Feb 6, 2019 · 7 comments
Closed

v8: Reading IPublishedContent in a background task fails #4442

PerplexDaniel opened this issue Feb 6, 2019 · 7 comments

Comments

@PerplexDaniel
Copy link
Contributor

PerplexDaniel commented Feb 6, 2019

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<IUmbracoContextAccessor, HybridUmbracoContextAccessor>();

Bug summary

UmbracoContext / IPublishedSnapshot are both null in an implementation of IComponent at its Initialize() method.

Steps to reproduce

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()
        {
        }
    }
}

Expected result

_contextAccessor.UmbracoContext and _snapshotAccessor.PublishedSnapshot are both available and grant access to IPublishedContent. No exception is thrown.

Actual result

_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.

@Shazwazza
Copy link
Contributor

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.

@PerplexDaniel
Copy link
Contributor Author

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.

@clausjensen
Copy link
Contributor

I'm closing the issue for now.
@zpqrtbnk if you have time to comment on it, feel free to 😃

@zpqrtbnk
Copy link
Contributor

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?

@PerplexDaniel
Copy link
Contributor Author

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.

@PerplexDaniel
Copy link
Contributor Author

For future readers, core team is discussing a similar issue and possible solutions in #4572

@PerplexDaniel
Copy link
Contributor Author

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(...)    
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants