Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • John McKillip 33 posts 115 karma points
    Apr 03, 2019 @ 19:33
    John McKillip
    0

    Constructor Injection in an IComponent Implementation

    Hi. I am running into an issue with using constructor injection in an IComponent implementation. Here is the scenario:

    I have a class I am using that implements IUserComposer to wire up my services into the container {Composer1}. I have a second class that implements IUserComposer that I am using to hook into content events {Composer2}. If I try to inject services from {Composer1] into a component in {Composer2}, I get a nasty Boot failed: Umbraco cannot run. See Umbraco's log file for more details. error. The logs have no useful info on what is happening. I thought it may be an issue with the order the Composers were run in, so I tried using the [ComposeBefore] and [ComposeAfter] attributes to no avail.

    The behavior is strange, because constructor injection seems to work for me on a component where I inject Umbraco.Core.Services.IUserService. So I tested it out and added an injection for UmbracoHelper and that resulted in the Boot failed error.

    I can add specific code I am using if that would be helpful. At the moment, I'm just wondering if what I am trying to do is even possible and if so, where I may be going wrong. Any input would be greatly appreciated, as, the v8 docs are pretty lacking at the moment.

    Cheers! -John

  • Marc Goodson 1003 posts 6598 karma points MVP 4x c-trib
    Apr 04, 2019 @ 19:59
    Marc Goodson
    0

    Hi John

    If you have a look at this issue: https://github.com/umbraco/Umbraco-CMS/issues/5005#issuecomment-478126317 and scroll down are you describing the same thing I'm experiencing? eg not able to inject UmbracoHelper ... eg in your scenario if you for example you inject Ilogger instead of UmbracoHelper... ?

    regards

    Marc

  • John McKillip 33 posts 115 karma points
    Apr 04, 2019 @ 20:21
    John McKillip
    0

    Hi Marc,

    Not exactly, no. In my hijacked controllers, I can inject UmbracoHelper and it works as expected. The odd behavior is specifically when I am using constructor injection in an IComponent implementation like so:

    public class StartupEvent : IUserComposer
    {
        public void Compose(Composition composition)
        {
             composition.Components().Append<PublishEventsComponent>();
        }
    
         public class PublishEventsComponent : IComponent
         {
             private readonly IUserService _UserService;
             private readonly ILogger _Logger;
             private readonly IUmbracoMapper _UmbracoMapper;
             private readonly UmbracoHelper _UHelper;
    
             public PublishEventsComponent(
                 IUserService userService,
                 ILogger logger,
                 IUmbracoMapper umbracoMapper,
                 UmbracoHelper uHelper)
             {
                 _UserService = userService;
                 _Logger = logger;
                 _UmbracoMapper = umbracoMapper;
                 _UHelper = uHelper;
             }
    
             public void Initialize()
             {
                 // Do Something on Publish
             }
    
             public void Terminate() { }
         }
     }
    

    The stack trace for the error I am getting is the same as the one you posted in the issue tracker. Playing around with this some more, I am starting to suspect that it may be related to the Lifetime of the service being injected into the IComponent implementation.

  • Marc Goodson 1003 posts 6598 karma points MVP 4x c-trib
    Apr 04, 2019 @ 21:54
    Marc Goodson
    0

    Hi John

    Just trying to replicate your situation I find that this works:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Umbraco.Core.Composing;
    using Umbraco.Core.Services;
    using Umbraco.Core.Services.Implement;
    
    namespace Umbraco8.Components
    {
        public class PublishEventsComposer : ComponentComposer<PublishEventsComponent>
        {
    
        }
        public class PublishEventsComponent : IComponent
        {
            private readonly IUserService _UserService;
            public PublishEventsComponent(IUserService userService)
            {
                _UserService = userService;
            }
    
            public void Initialize()
            {
                ContentService.Published += ContentService_Published;
            }
    
            private void ContentService_Published(IContentService sender, Umbraco.Core.Events.ContentPublishedEventArgs e)
            {
                var myUser = _UserService.GetByEmail("marc@test.co.uk");
            }
    
            public void Terminate()
            {
                throw new NotImplementedException();
            }
        }
    }
    

    The UserService is injected into the component and can be used inside the published event that it is subscribes to...

    but as soon as I try to inject UmbracoHelper then I get the boot error...

    eg the following doesn't work.

    public class PublishEventsComponent : IComponent
        {
            private readonly IUserService _UserService;
            private readonly UmbracoHelper _umbracoHelper;
            public PublishEventsComponent(IUserService userService, UmbracoHelper umbracoHelper)
            {
                _UserService = userService;
                _umbracoHelper = umbracoHelper;
            }
    

    that's why it seemed connected to the issue I had, and it feels like instances that rely on UmbracoContext being created suffer the issue, but other services get injected like you'd expect.

    regards

    marc

  • John McKillip 33 posts 115 karma points
    Apr 04, 2019 @ 21:59
    John McKillip
    0

    Yes, so, it seems that UmbracoHelper is a scoped object. Looks like you can't inject that or any service that depends on it inside of an IComponent. That makes sense that it would not work, but it sucks because it seems that is the only way to get IPublishedContent instances, which is ultimately what I am trying to do in my component.

    John

  • Marc Goodson 1003 posts 6598 karma points MVP 4x c-trib
    Apr 04, 2019 @ 22:48
    Marc Goodson
    1

    Yes, in my case ITypedContentQuery is the only thing I need to inject into my customer service class! (and I inject it in V7 via ninject ok ...)

    What you can do inside a Component is inject IUmbracoContextFactory, and use this to get access to the content cache by EnsureUmbracoContext(),

    so depending what you need to do in your event:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Umbraco.Core.Composing;
    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Core.Services;
    using Umbraco.Core.Services.Implement;
    using Umbraco.Web;
    
    namespace Umbraco8.Components
    {
        public class PublishEventsComposer : ComponentComposer<PublishEventsComponent>
        {
    
        }
        public class PublishEventsComponent : IComponent
        {
            private readonly IUserService _UserService;
            private readonly IUmbracoContextFactory _umbracoContextFactory;
            public PublishEventsComponent(IUserService userService, IUmbracoContextFactory umbracoContextFactory)
            {
                _UserService = userService;
                _umbracoContextFactory = umbracoContextFactory;
            }
    
            public void Initialize()
            {
                ContentService.Published += ContentService_Published;
            }
    
            private void ContentService_Published(IContentService sender, Umbraco.Core.Events.ContentPublishedEventArgs e)
            {
                using (var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext())
                {
                    var contentCache = umbracoContextReference.UmbracoContext.ContentCache;
                    //get IPublishedContent
                    IEnumerable<IPublishedContent> rootNodes = contentCache.GetAtRoot();
                    IPublishedContent particularItem = contentCache.GetById(1234);
                }
            }
    
            public void Terminate()
            {
                throw new NotImplementedException();
            }
        }
    }
    

    regards

    Marc

  • John McKillip 33 posts 115 karma points
    Apr 05, 2019 @ 14:23
    John McKillip
    0

    Hi Marc

    Nice. I did not know about IUmbracoContextFactory. That will work for what I need to do. Thanks for pointing me in the right direction!

    John

  • Stephen 767 posts 2263 karma points c-trib
    Apr 06, 2019 @ 17:14
    Stephen
    1

    What Marc says. Don't inject UmbracoHelper. It's a transient thing that lives in the context of a request -- and therefore cannot be injected in non-transient, non-request things such as IComponent.

    The "right" way is what Marc explained.

    And yes, that should be documented somewhere, in a "best practices and FAQ" page -- which is work-in-progress at the moment.

  • John McKillip 33 posts 115 karma points
    1 week ago
    John McKillip
    0

    Hello. So, Marc's example has been working great for me. I just upgraded to Umbraco 8.2.2 and I am now getting this Boot failed error:

    System.InvalidOperationException: The published snapshot service has not properly initialized.

    Wondering what changed and what the proper way to initialize the published snapshot service is? Any help would be appreciated.

    Also, I should clarify: I upgraded from 8.2 to 8.2.2. Everything worked fine in 8.2 and I don't see any breaking changes in the release notes for 8.2.1 or 8.2.2.

    Thanks! John

  • Thomas Rydeen Skyldahl 13 posts 128 karma points
    1 week ago
    Thomas Rydeen Skyldahl
    100

    Did the upgrade complete or are you in the process performing the upgrade?

    As some services are not available when performing the upgrade/install process.

    See here: and read the section on about Runtime Levels and the [RuntimeLevel] attribute https://our.umbraco.com/documentation/implementation/composing/

  • John McKillip 33 posts 115 karma points
    7 days ago
    John McKillip
    0

    Hi. The upgrade completed and the site re-compiled before I tested and ran into the issue.

    -John

  • Thomas Rydeen Skyldahl 13 posts 128 karma points
    7 days ago
    Thomas Rydeen Skyldahl
    1

    With upgrade complete I ment did you get to the login screen an see upgrade dialog after installing the package and recompile.

    Or does it fail om first run? As the first run the site is in upgrade mode and in that mode it's not all services that are available.

  • John McKillip 33 posts 115 karma points
    6 days ago
    John McKillip
    0

    Hi Thomas,

    Let me check. I refactored my code to not use the published snapshot service so, I am definitely past upgrade mode. Let me drop the old code back in and see if I get the same result.

    Thanks! -John

  • John McKillip 33 posts 115 karma points
    6 days ago
    John McKillip
    1

    Hello again,

    Thomas, you were right. Things are working as expected now. I suppose I need to make sure the component which uses that service has a RuntimeLevel = run. Currently, it was set to boot. Appreciate you pointing me in the right direction!

    Thanks! John

Please Sign in or register to post replies

Write your reply to:

Draft