应用程序是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在预提交的请求中实际上是相同的,但在交互请求中却不同,交互请求是最重要的。关于如何前进有什么想法吗?
我看到过类似的问题,并能够解决如下问题:
- 准备一个类似于下面的包装类:
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;
}
}
}
- 准备一个中间件:
public class TestMiddleware<T>: IMiddleware
{
public virtual async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Wrapper<T>.CurrentValue = /* set references */;
await next(context);
}
}
- 您现在可以从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();
}
}