StructureMap.Microsoft.DependencyInjection
StructureMap.Microsoft.DependencyInjection copied to clipboard
Resolving concrete service type does not work in Middleware.Invoke
Trying to resolve a concrete type with dependencies does not work when I pass the IServiceProvicer
in as an argument to the Invoke-method. However, if I get the service via Container first, and then try to resolve with IServiceProvider
it works:
public async Task Invoke(HttpContext context, IServiceProvider serviceProvider)
{
var myService1 = serviceProvider.GetService(typeof(MyService)); // == null
var myService2 = Startup.Container.GetInstance<MyService>(); // != null
var myService2 = serviceProvider.GetService(typeof(MyService)); // != null
...
EDIT: This is for .NET Core 2.0.
You've left out quite a bit of information here... What are the dependencies of MyService
? What are their lifetimes? What's the lifetime of MyService
itself? Is it scoped? Why are you injecting IServiceProvider
in the Invoke
method instead of injecting MyService
directly? Have you tried injecting any of them in the constructor? Any difference? What does your Startup
(wire-up) look like?
ASP.NET Core uses TryGetInstance
instead of GetInstance
. Could you test that out and see if you get the same result? If that changes things, this is probably a bug (or feature) in StructureMap itself. Apparently there are very different code paths taken for GetInstance
and TryGetInstance
.
This is used for a web socket middleware. It's basically a modified clone of https://radu-matei.com/blog/aspnet-core-websockets-middleware/
Used to have the service provider in the ctor but after upgrade to .NET Core 2.0, for some reason, it wasn't possible to use reosolve the service provider from a ctor. I got exception:
System.Exception: Error creating hub FormTemplateHub ---> System.ObjectDisposedException: Cannot access a disposed object. Object name: 'StructureMap Nested Container'. at StructureMap.Container.assertNotDisposed() at StructureMap.Container.TryGetInstance(Type pluginType) at Barium.Forms.Designer.WebSockets.Hubs.WebSocketHubHandler.CreateHub(IServiceProvider serviceProvider, String socketId, HttpContext context) in C:\Barium\FormsDesigner\src\Barium.Forms.Designer\WebSockets\Hubs\WebSocketHubHandler.cs:line 147 --- End of inner exception stack trace --- at Barium.Forms.Designer.WebSockets.Hubs.WebSocketHubHandler.CreateHub(IServiceProvider serviceProvider, String socketId, HttpContext context) in C:\Barium\FormsDesigner\src\Barium.Forms.Designer\WebSockets\Hubs\WebSocketHubHandler.cs:line 152 at Barium.Forms.Designer.WebSockets.Hubs.WebSocketHubHandler.<OnConnected>d__6.MoveNext() in C:\Barium\FormsDesigner\src\Barium.Forms.Designer\WebSockets\Hubs\WebSocketHubHandler.cs:line 48 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Barium.Forms.Designer.WebSockets.WebSocketManagerMiddleware.<Invoke>d__4.MoveNext() in C:\Barium\FormsDesigner\src\Barium.Forms.Designer\WebSockets\WebSocketManagerMiddleware.cs:line 35 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.<Invoke>d__3.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.<Invoke>d__3.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.<Invoke>d__7.MoveNext()
I tested with TryGetInstance as you suggested and it indeed returns null. GetInstance returns correct service. After calling GetInstance it works with TryGetInstance, so this seems to be a StructureMap issue?
This seems to be by design in StructureMap, if something isn't registered in the container.
http://structuremap.github.io/resolving/try-getting-an-optional-service-by-plugin-type/
so this seems to be a StructureMap issue?
Yeah, that's what I was afraid of. I'm not sure what I can do with this in the adapter. For some reason, StructureMap doesn't "try as hard" when you do TryGetInstance
as when you do GetInstance
. I'm not sure whether I'd call that a feature or a bug 😝 If GetInstance
is able to produce an instance, I'd most definitely expect TryGetInstance
to return true
.
Does that also mean that the following is true?
-
TryGetInstance == false
-
GetInstance != null
-
TryGetInstance == true
We've had some of the same issues with open generic and enumerable services as well 😢 The big issue is that the IServiceProvider
"contract" is to return null (instead of throwing) when you call GetService
and the service doesn't exist.
Maybe we can get around things by always calling GetInstance
, catching potential exceptions and returning null
? That would of course swallow "real" exceptions as well... @jeremydmiller?
"For some reason, StructureMap doesn't "try as hard" when you do"
-- The reason is that the semantics of TryGetInstance
really means "only give me a value here if I explicitly registered this type without any of your StructureMap just figure it out kinda magic". The ASP.Net Core team in their infinite wisdom created different semantic rules here.
You could beat this issue by making StructureMap still use its default concrete type discovery rules with a custom IFamilyPolicy
for concrete types, but set AppliesToHasFamilyChecks = true
so that SM will evaluate that during the TryGetInstance
calls.
And for the record, I griped a lot to the ASP.Net team about exactly this issue.
I have a very similar problem, a particular class cloud not get loaded using serviceProvider.GetService(typeof(MyService));
, the weird thing was, if i declare the same dependency in the constructor it would get build with no problems, and then the same method work.
Let me show with some code:
public ClubsProvider(IServiceProvider serviceProvider, SomeClass someClass)
{
//someClass is initialized correctly!
}
public async Task DoSomething()
{
var otherSomeClass = _serviceProvider.GetService<SomeClass>();
//otherSomeClass is initialized correctly!
}
This works, but if i remove the constructor, then the service provider stops working, i have no idea why:
public ClubsProvider(IServiceProvider serviceProvider)
{
//someClass does not exists in this universe
}
public async Task DoSomething()
{
var otherSomeClass = _serviceProvider.GetService<SomeClass>();
//otherSomeClass is null
}
The solution was really stupid, using GetRequiredService
instead GetService
brings the instance with no problems in both cases.
From what i understand, the only difference between required or now, was that required throws an exception if no implementation is found, so, why this behabiour is presenting?
IServiceProvider.GetService() calls into StructureMap's TryGetInstance(), which is a little bit semantically different in that it will resolve as a null if the concrete type isn't explicitly registered. GetRequiredService() calls to StructureMap's GetInstance(), which in the case of a concrete type with public constructors, will quietly create the registration for you and let you continue on your way.
All of that is functioning as designed in StructureMap. Arguably, the confusion comes in because of the adapter and the different semantic meanings of the methods in the aspnet core abstractions versus the older meaning in StructureMap itself.
Yes, it is confusing, i was confident in this stackoverflow question. I will add this info to future users. Thanks a lot for the quick response.
Just one more question, if the semantics are different, why injecting the object in the constructor causes the instance to be build correctly with the serviceProvider.getService?
I ran across this issue in trying to migrate a large ASPNET Core 1.1 application to 2.1, where our database migrations code rely on DI, but were failing due to being unable to resolve concrete types. Taking @jeremydmiller's suggestion and inspiration from lamar I wrote a ConcreteTypeFamilyPolicy for StructureMap.
It gets the "works on my machine" badge; I wouldn't rely on it for production applications.