Scope in Middleware and Blazor Component
Asked Answered
J

1

10

I'm working on a server-side Blazor application and ran into some problems regarding a scoped service. For simplicity's sake I have re-created my issue using the default Blazor template (the one with the counter).

I have a service "CounterService", which initializes a counter to 1 and exposes this counter together with a method to increment it. Really basic:

public class CounterService
    {
        public int Counter { get; private set; }

        public CounterService()
        {
            Counter = 1;
        }

        public void IncrementCounter()
        {
            Counter++;
        }
    }

I have then registered this counter in my Startup.cs as a scoped service: `services.AddScoped()

Then I have a custom ASP.NET middleware, in this case a "CounterInitializerMiddleware".

public class CounterInitializerMiddleware
    {

        public CounterInitializerMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public RequestDelegate _next { get; }

        public async Task Invoke(HttpContext context, CounterService counterService)
        {
            Console.WriteLine($"CounterInitializer invoked from request path: {context.Request.Path.Value}");
            counterService.IncrementCounter();
            counterService.IncrementCounter();
            await _next(context);
        }
    }

    public static class MiddlewareExtensions
    {
        public static IApplicationBuilder UseCounterInitializer(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CounterInitializerMiddleware>();
        }
    }

Basically, its a middle layer to increment the counter so that it starts at 3 rather than 1 when I get the service injected to my component(s). I register it in my Configure-method in Startup.cs: `app.UseCounterInitializer();

This middleware-layer is invoked 4 times when I start up my application (note that it has RenderMode set to ServerPreRendered): At the page-load request and at the _blazor-requests:

CounterInitializer invoked from request path: /counter
CounterInitializer invoked from request path: /_blazor/disconnect
CounterInitializer invoked from request path: /_blazor/negotiate
CounterInitializer invoked from request path: /_blazor

The scoped service is injected, and all seems good.

Then, if I have a component with the CounterService injected, it seems the scopes get messed up. If I look at the OnInitialized-method, this is called twice. Once during the pre-render and once during normal render. At the pre-render execution, the CounterService has Counter set to 3 as expected, since it has been through the CounterInitializerMiddleware. However, during render execution, the CounterService is spawned fresh. So it seems the scope of the normal render and the scope(s) of the requests going through the middleware are different. I thought the scope of the components would be bound to the "_blazor"-signalR connection which is processed my the middleware.

Anyone who can figure out what is going on and help me understand how to accomplish what I'm trying to do?

Best, Mathias

EDIT: Just to clarify. My real use-case is something entirely different, and the Counter-example is just a simplified case showcasing the issue and is more easily reproducible (I hope).

Judges answered 11/2, 2020 at 16:9 Comment(2)
I've run in to a similar issue - a scoped service in the middleware has a new instance when called in App/Host-files. No solution in sight (but I moved the logic to _HostAuthModel_/_Host for now). Have you got any clarity since feb 11, 2020? :)Fiann
Also ran into this issue. Blazor scopes are created per SignalR "circuit", while Asp.Net Core scopes are created per HTTP request. Haven't found a decent solution so far.Altar
G
1

I've hit the same problem and needed a quick workaround.

The workaround is to get the service from the HttpContext, which is an anti-pattern but better than nothing.

class YourClass
{
     private readonly SomeMiddlewareScopedService _service;
     public YourClass(SomeMiddlewareScopedServiceservice)
     {
          _service = service;
     }
}

The workaround:

class YourClass
{
     private readonly SomeMiddlewareScopedService _service;
     public YourClass(IHttpContextAccessor contextAccessor)
     {
          _service= (SomeMiddlewareScopedService)contextAccessor.HttpContext.RequestServices.GetService(typeof(SomeMiddlewareScopedService));
     }
}

Don't forget to add to your builder:

builder.Services.AddHttpContextAccessor();
Gimcrackery answered 31/1, 2023 at 0:39 Comment(1)
This seems to be a useable workaround. Although Microsoft discourages the use of IHttpContextAccessor in Blazor components directly or indirectly.Altar

© 2022 - 2024 — McMap. All rights reserved.