简体   繁体   English

Blazor 服务器是否可以在中间件中注入作用域服务并在组件中注入相同的实例?

[英]Is it possible in Blazor Server to inject scoped service in middleware and inject that same instance in component?

The application is Blazor Server and the question is very similar to Scope in Middleware and Blazor Component but it has not been active for long and I've clarified a few parts.该应用程序是 Blazor 服务器,问题与中间件中的Scope 和 Blazor 组件非常相似,但它已经很久没有活动了,我已经澄清了。

I've written a middleware that reads a cookie from the current request.我编写了一个从当前请求中读取 cookie 的中间件。 A scoped service has been injected (singleton is not suitable since it's per user) via InvokeAsync and it gets updated with a value from the cookie.已通过InvokeAsync注入了作用域服务(单例不适合,因为它是针对每个用户的),并使用 cookie 中的值进行更新。 The same service is injected in a page component but unfortunately it's not the same instance of the service.相同的服务被注入到一个页面组件中,但不幸的是它不是同一个服务实例。

I've tried both render-mode="Server" and render-mode="ServerPrerendered" .我已经尝试过render-mode="Server"render-mode="ServerPrerendered" They both behave differently as you would expect but nevertheless it is not the same instance as the one created in the middleware.它们的行为都与您预期的不同,但它与在中间件中创建的实例不同。 In render-mode Server the service is injected once as you expected and in render-mode ServerPrerendered the service is injected twice, once for the prerendered page and once for the interactive page.在渲染模式服务器中,服务按预期注入一次,在渲染模式ServerPrerendered中,服务注入两次,一次用于预渲染页面,一次用于交互式页面。 My goal is to have the same scoped service injected for the request in the middleware to also be injected in the page component.我的目标是为中间件中的请求注入相同范围的服务,以便也注入页面组件。 Is this possible?这可能吗?

Code for adding middleware (a bit simplified but still same problem).添加中间件的代码(有点简化但仍然是同样的问题)。 I've added some filtering since I'm only interested in the page request:我添加了一些过滤,因为我只对页面请求感兴趣:

app.UseWhen(
    context =>
        {
            return (context.Request.Path.StartsWithSegments("/_content") ||
                    context.Request.Path.StartsWithSegments("/_framework") ||
                    context.Request.Path.StartsWithSegments("/_blazor") ||
                    context.Request.Path.StartsWithSegments("/images") ||
                    context.Request.Path.StartsWithSegments("/favicon.ico") ||
                    context.Request.Path.StartsWithSegments("/css")) == false;                     
        }
    , builder => builder.UseSettingsMiddleware());

Adding the scoped service:添加范围服务:

public void ConfigureServices(IServiceCollection services)
{
   /* all other services added before this */

   services.AddScoped<IThemeService, ThemeService>();
}

The middleware:中间件:


public class ThemeMiddleware
{
    private readonly RequestDelegate _next;
    private string _id;

    public ThemeMiddleware(RequestDelegate next)
    {
        _next = next;
        _id = Guid.NewGuid().ToString()[^4..];
    }

    public async Task InvokeAsync(HttpContext httpContext, IThemeService themeService)
    {            
        var request = httpContext.Request;
        string path = request.Path;

        string theme = request.Cookies["App.Theme"];
            
        Debug.WriteLine($"Middleware [{_id}]: Service [{themeService.GetId()}] | Request Path={path} | Theme={theme}");

        if(string.IsNullOrEmpty(theme) == false)
        {
            themeService.SetTheme(theme);
        }                                              

        await _next(httpContext);
    }
}

The service:服务:

public class ThemeService : IThemeService, IDisposable
{
    string _theme = "default";
    string _id;
    string dateTimeFormat = "ss.fffffff";

    public ThemeService()
    {
        _id = Guid.NewGuid().ToString()[^4..];
    }

    public void Dispose() { }

    public string GetId() { return _id; }
            
    public string GetTheme()
    {            
        Debug.WriteLine($"ThemeService [{_id}]: GetTheme={DateTime.Now.ToString(dateTimeFormat)}");
        return _theme;
    }

    public void SetTheme(string theme)
    {
        Debug.WriteLine($"ThemeService [{_id}]: SetTheme={DateTime.Now.ToString(dateTimeFormat)}");
        _theme = theme;
    }
}

The component (basically same code also exists in MainLayout.razor):组件(MainLayout.razor 中也存在基本相同的代码):

@page "/"
@inject IThemeService ThemeService

@code {
    
    protected override async Task OnInitializedAsync()
    {        
        System.Diagnostics.Debug.WriteLine($"Index.razor: Service [{ThemeService.GetId()}]");
    }
}

Output Output

render-mode=Server渲染模式=服务器

Middleware [399d]: Service [1f37] | Request Path=/ | Theme=dark
ThemeService [1f37]: SetTheme=00.5996142
MainLayout.razor: Service [4e96]
ThemeService [4e96]: GetTheme=01.0375910
Index.razor: Service [4e96]

render-mode=ServerPrerendered渲染模式=服务器预渲染

Middleware [982d]: Service [5fa8] | Request Path=/ | Theme=dark
ThemeService [5fa8]: SetTheme=03.2477461
MainLayout.razor: Service [5fa8]
ThemeService [5fa8]: GetTheme=03.3576799
Index.razor: Service [5fa8]
MainLayout.razor: Service [d27c]
ThemeService [d27c]: GetTheme=03.9510551
Index.razor: Service [d27c]

The service id is actually the same in the prerendered request but not in the interactive one which is the one that counts.服务 id 在预渲染请求中实际上是相同的,但在交互式请求中是不同的,这是一个重要的请求。 Any ideas on how to move forward?关于如何前进的任何想法?

I have seen a similar problem and was able to solve it as follows:我已经看到了类似的问题,并且能够按如下方式解决它:

  1. Prepare a wrapper class similar to the one below:准备一个类似于下面的包装器 class:
public static class Wrapper<T>
{
    private static AsyncLocal<T> _value = new AsyncLocal<T>();

    public static T CurrentValue
    {
        get
        {
            return _value.Value;
        }
        set
        {
            _value.Value = value;
        }
    }
}
  1. prepare a middleware:准备一个中间件:
public class TestMiddleware<T>: IMiddleware
{
    public virtual async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        Wrapper<T>.CurrentValue = /* set references */;

        await next(context);
    }
}
  1. You may now access the Wrapper<T>.CurrentValue from a Blazor page or a scoped / transient service which was instantiated in the current Blazor circuit.您现在可以从 Blazor 页面或在当前 Blazor 电路中实例化的范围/瞬态服务访问Wrapper<T>.CurrentValue

I opted for another solution to the initial problem, ie read a cookie-value and have it available in an injected service on the component-level.对于最初的问题,我选择了另一种解决方案,即读取一个 cookie 值并将其用于组件级别的注入服务中。 I would have preferred to handle this as a middleware, but unfortunately never found a way to do this except when using a singleton service and that is not an option.我更愿意将其作为中间件处理,但不幸的是,除非使用 singleton 服务,否则从未找到这样做的方法,这不是一个选项。

Startup.cs:启动.cs:

services.AddScoped<IThemeService, ThemeService>();

ThemeService.cs:主题服务.cs:

public class ThemeService : IThemeService, IDisposable
{
    string _id;
    string _theme = "default";
    string _themeCookieName;

    public ThemeService(IHttpContextAccessor contextAccessor, IOptions<MyConfiguration> myConfig)
    {
        _id = Guid.NewGuid().ToString()[^4..];
        _themeCookieName = myConfig.Value.ThemeCookieName;
        RetrieveTheme(contextAccessor);
    }

    void RetrieveTheme(IHttpContextAccessor contextAccessor)
    {
        var request = contextAccessor.HttpContext.Request;
        string theme = request.Cookies[_themeCookieName];
        if (string.IsNullOrEmpty(theme) == false)
        {
            _theme = theme;
        }
    }

    public void Dispose() { }

    public string GetId() { return _id; }

    public string GetTheme() { return _theme; }

    public void SetTheme(string theme) { _theme = theme; }
}

MainLayout.razor: MainLayout.razor:

@inject IThemeService ThemeService

/* markup */

@code {
    bool _darkTheme = false;
    MudTheme _theme = new DefaultTheme();

    protected override async Task OnInitializedAsync()
    {
        var currentTheme = ThemeService.GetTheme();
        _darkTheme = currentTheme == "dark";
        _theme = _darkTheme ? new DarkTheme() : new DefaultTheme();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 Blazor 服务器应用程序中,是否可以将 GraphServiceClient 注入范围服务? - In a Blazor Server app, is it possible to inject a GraphServiceClient into a scoped service? 如何将服务注入 Blazor? - How to inject a service into Blazor? 将配置的选项实例注入到作用域服务中的最佳方法是什么? - What is best way to inject configured options instance into a scoped service? 如何在 Blazor 服务器应用程序的服务注册中向 HttpClient 注入访问令牌? - How to inject access token to HttpClient in Service registration in Blazor server app? 如何将范围服务注入DbContext? 网络核心 - How to inject a scoped service into DbContext? Net Core 在自身内部注入相同的作用域依赖 - Inject same scoped dependency inside itself 如何在服务和存储库之间注入相同的dbcontext实例? - How to inject same dbcontext instance between Service and Repository? 如何在 Blazor 服务器应用程序中注入 ClaimsPrincipal - How to inject a ClaimsPrincipal in a Blazor Server application 如何将服务注入 Blazor Server 中的实体框架 AddDbContextFactory lambda 函数? - How can I inject a service into an Entity Framework AddDbContextFactory lambda function in Blazor Server? 使用Prism,如何将作用域RegionManager注入服务? - With Prism, how can I inject a scoped RegionManager into a service?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM