StructureMap.Microsoft.DependencyInjection icon indicating copy to clipboard operation
StructureMap.Microsoft.DependencyInjection copied to clipboard

Resolving concrete service type does not work in Middleware.Invoke

Open toha73 opened this issue 7 years ago • 10 comments

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.

toha73 avatar Oct 12 '17 11:10 toha73

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.

khellang avatar Oct 12 '17 11:10 khellang

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()

toha73 avatar Oct 12 '17 12:10 toha73

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?

toha73 avatar Oct 12 '17 12:10 toha73

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/

toha73 avatar Oct 12 '17 12:10 toha73

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?

  1. TryGetInstance == false
  2. GetInstance != null
  3. 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?

khellang avatar Oct 12 '17 12:10 khellang

"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.

jeremydmiller avatar Oct 12 '17 13:10 jeremydmiller

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?

antonioortizpola avatar Feb 26 '18 21:02 antonioortizpola

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.

jeremydmiller avatar Feb 27 '18 03:02 jeremydmiller

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?

antonioortizpola avatar Feb 27 '18 21:02 antonioortizpola

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.

jeremyZX avatar Jun 26 '18 16:06 jeremyZX