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



应用程序是Blazor Server,问题与中间件和Blazor组件中的Scope非常相似,但它的活动时间不长,我已经澄清了一些部分。

我已经编写了一个中间件,可以从当前请求中读取cookie。已经通过InvokeAsync注入了一个作用域服务(singleton不适合,因为它是按用户的(,并使用cookie中的值更新它。在页面组件中注入了相同的服务,但不幸的是,它不是服务的同一实例。

render-mode="Server"render-mode="ServerPrerendered"我都试过了。正如您所期望的那样,它们的行为都不同,但它与中间件中创建的实例不同。在渲染模式Server中,服务会按预期注入一次,在渲染模式ServerPrerendered下,服务会注入两次,一次用于预渲染页面,另一次用于交互式页面。我的目标是在中间件中为请求注入相同作用域的服务,也将其注入页面组件中。这可能吗?

添加中间件的代码(有点简化,但问题仍然相同(。我添加了一些过滤,因为我只对页面请求感兴趣:

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());

添加作用域服务:

public void ConfigureServices(IServiceCollection services)
{
/* all other services added before this */
services.AddScoped<IThemeService, ThemeService>();
}

中间件:


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);
}
}

服务:

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;
}
}

组件(基本上相同的代码也存在于MainLayout.rarzor中(:

@page "/"
@inject IThemeService ThemeService
@code {

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

输出

渲染模式=服务器

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]

渲染模式=服务器预渲染

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]

服务id在预提交的请求中实际上是相同的,但在交互请求中却不同,交互请求是最重要的。关于如何前进有什么想法吗?

我看到过类似的问题,并能够解决如下问题:

  1. 准备一个类似于下面的包装类:
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. 准备一个中间件:
public class TestMiddleware<T>: IMiddleware
{
public virtual async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Wrapper<T>.CurrentValue = /* set references */;
await next(context);
}
}
  1. 您现在可以从Blazor页面或在当前Blazor电路中实例化的作用域/瞬态服务访问Wrapper<T>.CurrentValue

如果我在源代码中没有记错的话,根本原因是Blazor DI作用域是一个新实例,而不是中间件DI作用域。

我为最初的问题选择了另一种解决方案,即读取cookie值,并在组件级别的注入服务中提供它。我更愿意将其作为中间件来处理,但不幸的是,除了使用单例服务之外,我从未找到实现这一点的方法,而且这不是一种选择。

Startup.cs:

services.AddScoped<IThemeService, ThemeService>();

主题服务.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:

@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();
}
}

最新更新