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 cannot access published content during startup #4572

Closed
Shazwazza opened this issue Feb 14, 2019 · 2 comments

Comments

@Shazwazza
Copy link
Member

commented Feb 14, 2019

This might not be super common but i'm sure it's common enough to need.

Articulate currently accesses the content cache on startup because it needs to define MVC routes based on what routes umbraco has already created for Articulate root nodes.

Currently you can't really access the content cache from within the Initialize method of your composer.

  • UmbracoContext is created during the BeginRequest phase as part of the umbraco module which means IUmbracoContextAccessor.UmbracoContext returns null at that stage
  • IPublishedSnapshotAccessor.PublishedSnapshot returns null at that stage
  • You can however inject the IPublishedSnapshotService and do var snapshot = _publishedSnapshotService.CreatePublishedSnapshot(null); but you will get null ref exceptions when trying return anything from the cache because the PublishContent.cs relies on it's injected IPublishedSnapshotAccessor, IUmbracoContextAccessor, IVariationContextAccessor, etc... which will all return null at that stage. I'm sure there's some interesting work arounds that can be done with this to force it to work but seems like a lot of code to go through to make that possible.

Another oddity is that the IPublishedSnapshotAccessor has a get/set for the PublishedSnapshot, however this setter is never used anywhere in umbraco. Also the only implementation of IPublishedSnapshotAccessor is UmbracoContextPublishedSnapshotAccessor which throws when you try to use the setter ... seems this setter shouldn't exist?

To work around all of this, what I need to do in Articulate is:

  • In my component Initlialize method i attach to this event UmbracoApplicationBase.ApplicationInit += UmbracoApplicationBase_ApplicationInit;
  • Then in that event I attach to app.ResolveRequestCache += App_ResolveRequestCache; which is the request phase after an UmbracoContext is available but before routing occurs
  • Then in that handler, i lazy initialize my routes so it only occurs once before any routing occurs to populate the routing table ... i don't get errors now but i haven't got far enough in testing to see if this actually works yet.
@zpqrtbnk

This comment has been minimized.

Copy link
Contributor

commented Feb 14, 2019

Workaround should not be needed - but should not require tons of code to get things to work.

Fixing.

@zpqrtbnk

This comment has been minimized.

Copy link
Contributor

commented Feb 14, 2019

See PR #4577 - and we really should merge this, or anything similar, before releasing.

What I have done:

  • replace all usages of UmbracoContext.Current with Current.UmbracoContext, thus killing one more singleton
  • move UmbracoContext management to an IUmbracoContextFactory

So...

Current.UmbracoContext, or UmbracoContext in controllers, return the "current" context, as managed by IUmbracoContextAccessor. A context is created by the Umbraco module at the beginning of every request, and disposed at the end of the request.

Now, components run before any request, and so don't have a "current" context. They need to explicitely use a context. The following code works on my machine:

public class TestComponent : IComponent
{
    private readonly IUmbracoContextFactory _umbracoContextFactory;
    private readonly ILogger _logger;

    public TestComponent(IUmbracoContextFactory umbracoContextFactory, ILogger logger)
    {
        _umbracoContextFactory = umbracoContextFactory;
        _logger = logger;
    }

    public void Initialize()
    {
        using (var reference = _umbracoContextFactory.EnsureUmbracoContext())
        {
            var content = reference.UmbracoContext.ContentCache.GetById(1103);
            if (content == null) throw new Exception("!");
            _logger.Debug<TestComponent>("DEBUG " + content.Name);
        }
    }

    public void Terminate()
    { }
}

Note that withing the using block, the context is registered with the IUmbracoContextAccessor so there is a current context, and therefore a current snapshot and everything - hence, the content cache works.

The EnsureUmbracoContext method does: if there already is a current context, return a reference to that context - but the reference is configured to not dispose the context when it's disposed. So you're using the current context, and it remains there after you've used it.

OTOH, if there is no current context, it creates a context, makes it the current one, and returns a reference to that context - and the reference is configured so that when it's disposed, the context is disposed too, and it's de-registered (ie no more current context).

This way, if you write code that may run within a request, and also may run outside a request, wrapping things in such a using block will guarantee that it works nicely in both cases.

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.