Boot fail when using an injected service (recursive dependency: IUmbracoContextFactory )
First of all, this is great little package Lee. It's gonna be an essential bit of kit for v8 site builds.
As the title says, I ran into a problem when trying to inject a simple service (which uses IUmbracoContextFactory to fetch some Umbraco content) into my custom DataList class (which implements IDataListSource):
public class CountryDataList : IDataListSource
{
private readonly ISiteService _siteService;
public CountryDataList(ISiteService siteService)
{
_siteService = siteService ?? throw new ArgumentNullException(nameof(siteService));
}
public IEnumerable<DataListItem> GetItems(Dictionary<string, object> config)
{
var items = new List<DataListItem>();
foreach (var country in _siteService.GetCountries())
{
items.Add(new DataListItem
{
Name = country.Name,
Value = country.IsoCode
});
}
return items;
}
}
The SiteService is identical in structure to the (excellent) Services Implementation documentation. It is registered with the DI container as per the docs too:
This works great when I consume it in a controller or view, but if I try and inject it into my Contentment DataListSource class, I get a Boot Fail: Recursive dependency detected: IUmbracoContextFactory. I believe the problem is to do with the service registration order, I tried the following, which looked like it might work:
But this is internal and the public types in Umbraco.Community.Contentment.Composing are invalid here because they do not implement Umbraco.Core.Composing.IComposer.
Not sure if ContentmentComposer needs exposing within the package or if I'm completely barking up the wrong tree. Any thoughts much appreciated!
Update: I forked the reop to open up ContentmentComposer so I could decorate my own service composer with
[ComposeAfter(typeof(ContentmentComposer))]
But also I still have the same error with a recursive dependency:
System.InvalidOperationException: Recursive dependency detected: ServiceType:Umbraco.Web.IUmbracoContextFactory, ServiceName:]
at LightInject.ServiceContainer.<>cDisplayClass153_0.0(IEmitter ms) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3849
at LightInject.ServiceContainer.EmitConstructorDependency(IEmitter emitter, Dependency dependency) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4158
Correct, the DI container complains about a recursive dependency for IUmbracoContextFactory. To clarify, this only happens when I inject the ISiteService into the IDataListSource class - if I exclude the Contentment stuff and inject it into a controller, (or get the instance from a View) it works fine.
Sorry Barry, so I get my reproduction test correct, where is the IUmbracoContextFactory being injected? into the ISiteService or IDataListSource directly?
Alternatively, you could try using IUmbracoContextAccessor instead? That has been working for me.
(I'm tempted to think there's an issue with using IUmbracoContextFactory ... but probably needs someone who's more knowledgeable with Umbraco/DI to comment on that.)
Good find Lee, thanks! Injecting IUmbracoContextAccessor into my service contructor accessing .UmbracoContext from there worked a charm. From the docs, it sounds like this would only work on a request thread, and I may need to catch UmbracoContext being null in other scenarios. Not sure, my service is a singleton so perhaps it is held with the single class instance when created. The service is for fetching some site-wide data so does not vary based on the context in any case. Be interesting to know if my implementation is wrong or if it's a problem upstream. Anyway - it works for my purposes, thank you for your help!
re: IUmbracoContextFactory, I've been trying to figure out what the issue might be - probably something to do with the other injected params on its constructor:
But that's where my DI knowledge is limited, no idea how to dig deeper. I'm facing the same concern, I wouldn't be surprised if my implementation was wrong, or needed tweaking in some way.
Another trick you can try if you have a circular dependency is to request a lazy version of the dependency so then it wouldn't evaluate immediately and instead would only evaluate when you try to access it
public class SiteService : ISiteService
{
private Lazy<IUmbracoContextFactory> _contextFactory;
public SiteService(Lazy<IUmbracoContextFactory> contextFactory)
{
_contextFactory = contextFactory;
}
public IEnumerable<Country> GetCountries()
{
using(var ctx = _contextFactory.Value.EnsureUmbracoContext())
{
// Do your thing
}
}
}
Nice Matt! Thanks for the chipping in, this works a treat and feels a bit more elegant than @Lee's suggestion. This should make it into the documentation I think.
Normally those errors are just due to circular dependencies one way or another, since LazyT solves it, that certainly sounds like a circular dependency issue :) many times in our code circular dependencies are caused by PropertyEditorCollection because property editors do far too much and when you resolve this collection you also create all of it's instances. If it's not this collection, than it could be another collection that is creating instances that result in circular references.
Boot fail when using an injected service (recursive dependency: IUmbracoContextFactory )
First of all, this is great little package Lee. It's gonna be an essential bit of kit for v8 site builds.
As the title says, I ran into a problem when trying to inject a simple service (which uses IUmbracoContextFactory to fetch some Umbraco content) into my custom DataList class (which implements
IDataListSource
):The SiteService is identical in structure to the (excellent) Services Implementation documentation. It is registered with the DI container as per the docs too:
This works great when I consume it in a controller or view, but if I try and inject it into my Contentment DataListSource class, I get a Boot Fail: Recursive dependency detected:
IUmbracoContextFactory
. I believe the problem is to do with the service registration order, I tried the following, which looked like it might work:But this is internal and the public types in
Umbraco.Community.Contentment.Composing
are invalid here because they do not implementUmbraco.Core.Composing.IComposer
.Not sure if
ContentmentComposer
needs exposing within the package or if I'm completely barking up the wrong tree. Any thoughts much appreciated!Update: I forked the reop to open up
ContentmentComposer
so I could decorate my own service composer withBut also I still have the same error with a recursive dependency:
System.InvalidOperationException: Recursive dependency detected: ServiceType:Umbraco.Web.IUmbracoContextFactory, ServiceName:] at LightInject.ServiceContainer.<>cDisplayClass153_0.0(IEmitter ms) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3849 at LightInject.ServiceContainer.EmitConstructorDependency(IEmitter emitter, Dependency dependency) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4158
Hi Barry,
I'll take a look at it this morning, see what I can find.
Just checking, so this is with the latest build from the 'develop' branch?
Thanks,
- Lee
Hi Barry,
To clarify, when you inject the
ISiteService
into anIDataListSource
it works fine? butIUmbracoContextFactory
fails?If so, I can reproduce it. (Not sure what the resolution is yet, figuring it out - DI still feels like dark magic to me).
Correct, the DI container complains about a recursive dependency for
IUmbracoContextFactory
. To clarify, this only happens when I inject theISiteService
into theIDataListSource
class - if I exclude the Contentment stuff and inject it into a controller, (or get the instance from a View) it works fine.Sorry Barry, so I get my reproduction test correct, where is the
IUmbracoContextFactory
being injected? into theISiteService
orIDataListSource
directly?Alternatively, you could try using
IUmbracoContextAccessor
instead? That has been working for me.(I'm tempted to think there's an issue with using
IUmbracoContextFactory
... but probably needs someone who's more knowledgeable with Umbraco/DI to comment on that.)Good find Lee, thanks! Injecting
IUmbracoContextAccessor
into my service contructor accessing.UmbracoContext
from there worked a charm. From the docs, it sounds like this would only work on a request thread, and I may need to catchUmbracoContext
being null in other scenarios. Not sure, my service is a singleton so perhaps it is held with the single class instance when created. The service is for fetching some site-wide data so does not vary based on the context in any case. Be interesting to know if my implementation is wrong or if it's a problem upstream. Anyway - it works for my purposes, thank you for your help!H5YR
Cool, glad there's a workaround/solution.
re:
IUmbracoContextFactory
, I've been trying to figure out what the issue might be - probably something to do with the other injected params on its constructor:https://github.com/umbraco/Umbraco-CMS/blob/release-8.6.1/src/Umbraco.Web/UmbracoContextFactory.cs#L38
But that's where my DI knowledge is limited, no idea how to dig deeper. I'm facing the same concern, I wouldn't be surprised if my implementation was wrong, or needed tweaking in some way.
We live and learn.
Cheers,
- Lee
What is the code for the
SiteService
instance?Another trick you can try if you have a circular dependency is to request a lazy version of the dependency so then it wouldn't evaluate immediately and instead would only evaluate when you try to access it
Nice Matt! Thanks for the chipping in, this works a treat and feels a bit more elegant than @Lee's suggestion. This should make it into the documentation I think.
Update: Made a PR for it :-)
Normally those errors are just due to circular dependencies one way or another, since LazyT solves it, that certainly sounds like a circular dependency issue :) many times in our code circular dependencies are caused by PropertyEditorCollection because property editors do far too much and when you resolve this collection you also create all of it's instances. If it's not this collection, than it could be another collection that is creating instances that result in circular references.
is working on a reply...