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.
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... ?
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.
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("[email protected]");
}
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.
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.
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.
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.
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.
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!
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
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
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:
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.
Hi John
Just trying to replicate your situation I find that this works:
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.
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
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
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:
regards
Marc
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
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 asIComponent
.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.
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:
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
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/
Hi. The upgrade completed and the site re-compiled before I tested and ran into the issue.
-John
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.
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
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
is working on a reply...