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.22k stars 152 forks source link

How to integrate convention-based middleware #918

Closed sarabu07 closed 3 years ago

sarabu07 commented 3 years ago

I modified the microsoft dependency injection from to use simple injector. Please see below the code and I'm getting error message

'Unable to resolve service for type 'AspNetCoreMiddleware.IGreeter' while attempting to activate 'AspNetCoreMiddleware.MyMiddleware'."'

ASP.NET Core 3.1 Code:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using SimpleInjector;

namespace AspNetCoreMiddleware
{
    public class Greeter : IGreeter
    {
        public string Greet()
        {
            return "Hello from Greeter!\n";
        }
    }

    public interface IGreeter
    {
        string Greet();
    }

    public class MyMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IGreeter _greeter;

        public MyMiddleware(RequestDelegate next, IGreeter greeter)
        {
            _next = next;
            _greeter = greeter;
        }

        public async Task Invoke(HttpContext ctx)
        {
            await ctx.Response.WriteAsync("MyMiddleware class says: " + _greeter.Greet());
            await _next(ctx);
        }
    }

    public class Startup
    {
        /// <summary>
        /// .Net core container object for simpleinjector.<see cref="SimpleInjector"/>
        /// </summary>
        private readonly Container container = new Container();

        public Startup()
        {
            container.Options.ResolveUnregisteredConcreteTypes = false;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            //services.AddTransient<IGreeter, Greeter>();
            _ = services.AddSimpleInjector(container, options =>
            {
                // AddAspNetCore() wraps web requests in a Simple Injector scope and
                // allows request-scoped framework services to be resolved.
                _ = options.AddAspNetCore();

                // Optionally, allow application components to depend on the non-generic
                // ILogger (Microsoft.Extensions.Logging) or IStringLocalizer
                // (Microsoft.Extensions.Localization) abstractions.
                //_ = options.AddLogging();
                //_ = options.AddLocalization();
            });
            InitializeContainer();
        }

        public void Configure(IApplicationBuilder app)
        {
            _ = app.UseSimpleInjector(container);
            // IApplicationBuilder.Use
            app.Use(
                next =>
                {
                    return async ctx =>
                    {
                        await ctx.Response.WriteAsync("Hello from IApplicationBuilder.Use!\n");
                        await next(ctx);
                    };
                });

            // Use extension
            app.Use(async (ctx, next) =>
            {
                await ctx.Response.WriteAsync("Hello from extension method Use!\n");
                await next();
            });

            // Use extension with RequestServices
            app.Use(
                async (ctx, next) =>
                {
                    var greeter = ctx.RequestServices.GetService<IGreeter>();
                    await ctx.Response.WriteAsync(greeter.Greet());
                    await next();
                });

            _ = app.UseMiddleware<MyMiddleware>();

            // Run extension
            app.Run(async context => await context.Response.WriteAsync("Hello from extension method Run!\n"));
            container.Verify();
        }

        private void InitializeContainer()
        {
            container.Register<IGreeter, Greeter>(Lifestyle.Transient);
        }
    }
}
dotnetjunkie commented 3 years ago

See the documentation on hos to wire middleware: https://docs.simpleinjector.org/en/latest/aspnetintegration.html#wiring-custom-middleware.

If you have any questions after following that guidance, please let me know.

sarabu07 commented 3 years ago

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/extensibility?view=aspnetcore-5.0

Could you provide me documentation for using conventional middleware with simple injector?

dotnetjunkie commented 3 years ago

What does that mean, "conventional middleware"?

sarabu07 commented 3 years ago

In the link https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/extensibility?view=aspnetcore-5.0 microsoft has referred middleware which is not using Imiddleware as conventional. That is what i was referring to.

dotnetjunkie commented 3 years ago

Ah, I see. Do you have any specific reason not to implement the IMiddleware interface so you can follow the current guidance?

sarabu07 commented 3 years ago

we have a production app which has middleware without implementing IMiddleware interface. I want to make sure we keep the changes minimal since the app is already in prod. The reason we are trying to switch from microsoft DI to simple injector is our api's are hosted behind istio proxy and there are instances where the container comes up in kubernetes before all DNS values are resolved which makes our apps to crash. We are using simple injector in our .Net framework app successfully for last 6 years and i believe the container.Verify() in simple injector and adding retry logic in the code connecting to third party apps when initializing the object, would solve this problem. Also, per this link https://www.stevejgordon.co.uk/how-is-the-asp-net-core-middleware-pipeline-built we have another middleware in our code which is built using inline middleware (getting the name from above link). Lastly, we are also going to check health check from the docker after deployment.

dotnetjunkie commented 3 years ago

The convention-based middleware design is not compatible with Simple Injector's, which is why there's no support for it. The following design incompatibilities contribute to this:

As a matter of fact, there is no support for this convention-based model within the built-in .NET Core DI Container either. This is why this feature is completely built inside ASP.NET Core MVC. ASP.NET Core MVC, also misses proper extension points to allow so-called non-conforming containers (such as Simple Injector) to integrate with this model. The UseMiddlewareExtensions.UseMiddleware extension methods are strongly coupled to the built-in DI Container, with no option for Simple Injector to interact.

Admittedly, it is technically possible to overcome all these hurdles and build support for convention-based middleware in Simple Injector's integration package. Doing so, however, basically recreating all ASP.NET Core logic surrounding convention-based middleware plus adding the integration with Simple Injector's diagnostic subsystem. This is a lot of work, and I decided this was not worth the trouble, because there is a simple workaround: just implement IMiddleware.

If, unfortunately, there is no way to migrate to using the IMiddleware interface (because perhaps the risks are too high, although a conversion should be rather straightforward), the solution is to new your convention-based middleware type by hand. Using the ConventionalMiddleware example, for instance, can configured as follows:

builder.Use((context, next) =>
{
    var middleware = new ConventionalMiddleware(next);

    return middleware.InvokeAsync(
        context: context,
        db: container.GetInstance<AppDbContext>())
});

As the example above shows, while the ConventionalMiddleware class is created by hand, any required dependency in this case AppDbContext) is requested from Simple Injector. Although this setup requires this setup to be updated when your conventional middle signature changes, on the plus side you get out-of-the-box compile-time support, which is something that conventional middleware otherwise don't have.

Your middleware, however, might have additional constant constructor arguments and additional method dependencies. This might look like this:

var arg0 = ...
var arg1 = ...
builder.Use((context, next) =>
{
    var middleware = new ConventionalMiddleware(next, arg0, arg1);

    return middleware.InvokeAsync(
        context: context,
        db: container.GetInstance<AppDbContext>(),
        dep1: container.GetInstance<IDep1>(),
        dep2: container.GetInstance<IDep2>())
});

In other words, this hand-wiring of ConventionalMiddleware allows it to be integrated with Simple Injector without any actual support from Simple Injector.

I hope this helps.