Taritsyn / BundleTransformer

Bundle Transformer - a modular extension for System.Web.Optimization (also known as the Microsoft ASP.NET Web Optimization Framework).
Apache License 2.0
130 stars 19 forks source link

Less handler fails in conjunction with 'Microsoft.Extensions.DependencyInjection' v8 #82

Open julealgon opened 4 months ago

julealgon commented 4 months ago

The Less request handler (BundleTransformer.Less.HttpHandlers.LessAssetHandler) defines 2 public constructors:

https://github.com/Taritsyn/BundleTransformer/blob/82a1beb5edb80f57f5e3746b549ba2889a52602f/src/BundleTransformer.Less/HttpHandlers/LessAssetHandler.cs#L30-L49

This breaks when attempting to resolve the handler through DI when using Microsoft.Extensions.DependencyInjection v8, as a breaking change was introduced that now throws an InvalidOperationException when multiple valid constructors are found.

Section of a stacktrace from our WebForms app which uses Autofac through MEDI: 

Exception information: Exception type: InvalidOperationException Exception message: Multiple constructors accepting all given argument types have been found in type 'BundleTransformer.Less.HttpHandlers.LessAssetHandler'. There should only be one applicable constructor. at bool Microsoft.Extensions.DependencyInjection.ActivatorUtilities.TryFindMatchingConstructor(Type instanceType, Type[] argumentTypes, out ConstructorInfo matchingConstructor, out Nullable[] parameterMap) at void Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type instanceType, Type[] argumentTypes, out ConstructorInfo matchingConstructor, out Nullable[] matchingParameterMap) at object Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, object[] parameters) at object OutProject.DependencyInjection.AutofacWebObjectActivator.GetService(Type serviceType) in ....

And how we are configuring the handler in the web.config:

<system.web>
  <pages>
    <httpHandlers>
      <add path="*.less" verb="GET" type="BundleTransformer.Less.HttpHandlers.LessAssetHandler, BundleTransformer.Less" />
      ...
    </httpHandlers>
    ...
  </pages>
</system.web>

Here is the documentation regarding the breaking change in MEDI v8 that explains the issue in more detail:

This likely breaks with other containers or setups as well since depending on the DI adapter implementation, it could just call directly into a build-up method in the DI library and many DI libraries will choke when multiple public constructors are found.

In our setup, we should be able to workaround this issue by manually registering the handler using a factory registration, but you might still want to reconsider the design here and opt to have just a single constructor (and maybe move the second one into a factory class).

Taritsyn commented 4 months ago

Hello, Juliano!

Bundle Transformer and Microsoft.Extensions.DependencyInjection are things from different worlds. I do not recommend that you even try to create a instances of the Bundle Transformer's types through DI.

I got feeling that you are trying to use the LessAssetHandler separately from the Bundle Transformer's infrastructure. If this is case, then perhaps your best solution would be to use a dotless library.

julealgon commented 4 months ago

Hello, Juliano!

Bundle Transformer and Microsoft.Extensions.DependencyInjection are things from different worlds. I do not recommend that you even try to create a instances of the Bundle Transformer's types through DI.

I would've agreed with you a few years ago, but not anymore. As you can see, we have a WebForms project that leverages your Less handler via standard web.config setup, and then we added dependency injection support to that project once it became available.

WebForms with DI enabled will attempt to resolve every object through the DI container, including ones defined via configuration like this.

I got feeling that you are trying to use the LessAssetHandler separately from the Bundle Transformer's infrastructure. If this is case, then perhaps your best solution would be to use a dotless library.

I'm not sure I understand what you are saying there (to be fair, I don't know the precise history of when/how this handler was added to our project, it was probably a very long time ago), but I've since resolved the problem with a modification to our implementation of the IServiceProvider by manually filtering out types with multiple constructors and skipping those in favor of the standard Activator.CreateInstance instead of relying on the containersActivatorUtilities.CreateInstance`. I wish that wasn't necessary but it was a simple enough fix.

The only reason I decided to create this issue was to make you aware of the problem (I do consider it a problem), but if you disagree, do feel free and close the issue.

Taritsyn commented 4 months ago

WebForms with DI enabled will attempt to resolve every object through the DI container, including ones defined via configuration like this.

Could you please provide a link to the documentation or article that describes this approach in ASP.NET Web Forms.

julealgon commented 4 months ago

WebForms with DI enabled will attempt to resolve every object through the DI container, including ones defined via configuration like this.

Could you please provide a link to the documentation or article that describes this approach in ASP.NET Web Forms.

This is a blog post from back when it was first made available. It uses the Unity container but you can use any other container (we use Autofac on our side, for instance):

And this is the relevant bits that I mentioned:

Areas that Dependency Injection can be used

There are many areas you can use Dependency Injection in WebForms applications now. Here is a complete list.

  • Pages and controls
    • WebForms page
    • User control
    • Custom control
  • IHttpHandler and IHttpHandlerFactory
  • IHttpModule
  • Providers
    • BuildProvider
    • ResourceProviderFactory
    • Health monitoring provider
    • Any ProviderBase based provider created by System.Web.Configuration.ProvidersHelper.InstantiateProvider. e.g. custom sessionstate provider
Taritsyn commented 4 months ago

Thanks for information!