Sunday, September 2, 2018

ASP.NET Core: Transient/Scoped Instances Inside Middleware

I have faced this scenario where when requesting a Transient or Scoped instance from a constructor in a Custom Middleware it wasn’t giving me a Transient/Scoped instance, instead, it was giving me a singleton.

Let’s examine the problem in detail. Say I have the following interface, it’s implementation and a custom middleware.

IMyService.cs
public interface IMyService
{
    Guid GetGuid();
}
MyService.cs
public class MyService : IMyService
{
    private readonly Guid guid;
 
    public MyService()
    {
        guid = Guid.NewGuid();
    }
 
    public Guid GetGuid()
    {
        return guid;
    }
}
HelloWorldMiddleware.cs
public class HelloWorldMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMyService _myService;
 
    public HelloWorldMiddleware(RequestDelegate next,
        IMyService myService)
    {
        _next = next;
        _myService = myService;
    }
 
    public async Task Invoke(HttpContext httpContext)
    {
        await httpContext.Response.WriteAsync($"Hello World with {_myService.GetGuid()}");
    }
}
Now in my Startup.cs, I am registering IMyService and MyService as Transient and plugging in the HelloWorldMiddleware to the application pipeline.

Startup.cs
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
 
    public IConfiguration Configuration { get; }
 
    public void ConfigureServices(IServiceCollection services)
    {
        // Rest of the code ignored for brevity
 
        // Registering IMyService as Transient
        // Meaning a new instance is created each time it’s requested
        services.AddTransient<IMyService, MyService>();
    }
 
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // Rest of the code ignored for brevity
 
        // Setting the custom middleware to the pipeline
        app.Map("/api/helloworld", configuration =>
        {
            configuration.UseMiddleware<HelloWorldMiddleware>();
        });
    }
}
So the idea is, each time I get navigated to “http://something/api/helloworld”, I get a response “Hello World” and that is with a new Guid. Because MyService constructor always creates a new Guid.

But no matter how many times I sent the request to “../api/helloworld”, I will get the same Guid. The reason is even though I have registered MyService as a Transient, the Middleware constructor will only get triggered once, and that is when the application is starting up. So here the constructor injection doesn’t work.

The workaround would be removing the constructor injection and request the service directly from context’s services.
public class HelloWorldMiddleware
{
    private readonly RequestDelegate _next;
 
    public HelloWorldMiddleware(RequestDelegate next)
    {
        _next = next;
    }
 
    public async Task Invoke(HttpContext httpContext)
    {
        IMyService _myService = httpContext.RequestServices.GetService<IMyService>();
        await httpContext.Response.WriteAsync($"Hello World with {_myService.GetGuid()}");
    }
}
Now when I navigated to “../api/helloworld”, I will get an instance of MyService, if it was registered as Transient, I will get a Transient and if it was registered as Scoped or Singleton, I will get a Scoped or Singleton instance respectfully.

Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment