simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 155 forks source link

WebAPI integration and unregistered types #890

Closed Ergamon closed 3 years ago

Ergamon commented 3 years ago

I am still struggling with our legacy application.

In a library I have build a more or less generic base composition root. We have different types of applications using this composition root extending it with more specific registrations. Sadly due to the nature of the old code I have sometimes to rely on resolving unregistered types, but regardless of the uglyness it works now for quite a while.

In the process of creating new applications, we want to build an REST API around the old application.

So today I created a new web application following your instructions on using SimpleInjector with Web API.

So basically the only part that is different is the part of registering my own types. There I basically do everything my base composition root does.

Sadly it does not work, cause the unregistered type event handler is now called for a lot of types for which my code is not prepared to handle.

Is my approach wrong? Is there any good way to differ between types queried by the web api code and my application code? (As a quick hack I changed my unregistered type handler to look for System.Web in the full type name and everything seems to work. But this feels more or less wrong)

dotnetjunkie commented 3 years ago

the unregistered type event handler is now called for a lot of types for which my code is not prepared to handle.

Can you elaborate on this? Unregistered type resolution only triggers on unregistered types, so what unregistered types get resolved and why is your code not prepared for this?

Ergamon commented 3 years ago

The old system was build with a self written (very bad) container system. I am more and more migrating everything to SimpleInjector, but as a fallback mechanism I call the old one.

So let´s assume we have a class with 2 methods: bool CanResolve(Type type) and Type Resolve(Type type)

Now I create a new WebAPI project in VS and copy the slightly modified code from your page at the right place in the Global.asax.cs file:

      // Create the container as usual.
      var container = new Container();
      container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

      // Register your types, for instance using the scoped lifestyle:
      container.ResolveUnregisteredType += (s, e) =>
      {
        var type = e.UnregisteredServiceType;

        if (LegacyResolver.CanResolve(type))
        {
          e.Register(() => LegacyResolver.Resolve(type));
        }
      };

      // This is an extension method from the integration package.
      container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

      container.Verify();

      GlobalConfiguration.Configuration.DependencyResolver =
          new SimpleInjectorWebApiDependencyResolver(container);

As you can see there are no registrations at all.

When I start the web site the ResolveUnregisteredType event handler is called with a type named ModelMetadataProvider and there is where my problem lies. The legacy resolver thinks that he can resolve this type, but actually he cant (and from my perspective he shall not).

So my question is how do I suppress this call or if this is not possible how do I extend this CanResolve method to identify such types?

(There are more calls to the event handler, not only the ModelMetadataProvider, but this is the first call already crashing my demo app)

dotnetjunkie commented 3 years ago

What's happening here is that ASP.NET Web API calls the IDependencyResolver abstraction on almost all its own types, in order to let the application container compose them. In case the custom IDependencyResolver returns null, Web API will create the type itself. This means that ModelMetadataProvider is just the first, of many requests Web API will be making to IDependencyResolver.

When a request comes in, the SimpleInjectorWebApiDependencyResolver will detect the ModelMetadataProvider isn't registered in Simple Injector and will return null (by calling Container.GetService(Type)). So far so good, but, even a call to GetService will trigger unregistered type resolution when the registration does not exist.

There doesn't exist a specific mechanism in Simple Injector to configure when ResolveUnregisteredType should go off, and when not. This means that you will have to implement this check inside your `ResolveUnregisteredType event.

Probably a good way to do so if by filtering out types that come from the System.Web.Http namespace; likely all requested types will be defined in System.Web.Http or a sub namespace.

Ergamon commented 3 years ago

Thx for the confirmation, that is basically what I did. I extended my CanResolve method by checking the namespace of the type.

So my question was basically, if there is a better solution.

Obviously there isn´t.

Thx for your time and help

dotnetjunkie commented 3 years ago

So my question was basically, if there is a better solution.

No, I'm afraid there isn't.