Blazor应用Startup.cs注入IStorageService报错求解决
问题:Blazor Server Startup.cs中注入IStorageService失败,无法读取localStorage设置语言
我在Blazor Server(MVVM)应用里,想在Startup.cs中注入IStorageService,读取localStorage的值来设置语言,但调用服务方法时出现和服务未注册一样的错误。已经尝试把服务注册放到ConfigureServices顶部,问题仍未解决。
注入代码
public class Startup { private IStorageService storageService; public Startup(IConfiguration configuration, IStorageService storageService) { this.storageService = storageService; Configuration = configuration; } public IConfiguration Configuration { get; } }
服务注册代码
/// <summary> /// This method gets called by the runtime. Use this method to add services to the container. /// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 /// </summary> /// <param name="services"></param> public void ConfigureServices(IServiceCollection services) { services.AddScoped<IStorageService, StorageService>(); var circuitRetentionPeriod = Configuration.GetValue("DisconnectedCircuitRetentionPeriodInMinutes", 3); services.AddRazorPages(); services.AddServerSideBlazor() .AddCircuitOptions(a => a.DisconnectedCircuitRetentionPeriod = System.TimeSpan.FromMinutes(circuitRetentionPeriod)) .AddHubOptions(hub => hub.MaximumReceiveMessageSize = 100 * 128 * 128); // 100 MB // Add basic logging functionality services.AddLogging(loggingBuilder => { // Clear all potential logging providers loggingBuilder.ClearProviders(); // Add NLog which will output the log to a log file loggingBuilder.AddNLog(); // Log output to the Visual Studio Debug output loggingBuilder.AddDebug(); }); services.AddScoped<CircuitHandler>((sp) => new CircuitHandlerService(sp.GetRequiredService<ILogger<CircuitHandlerService>>(), sp.GetRequiredService<CircuitsCounterService>())); services.AddSingleton<CircuitsCounterService>(); services.AddScoped<CorrelationProvider>(); services.AddAppBaseBlazorServices(); services.AddAppBaseApplicationServices(); services.AddApplicationServices(); services.AddBlazorServices(); services.AddForeignServices(); }
服务使用代码
/// <summary> /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// </summary> /// <param name="app"></param> /// <param name="env"></param> public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); var language = storageService.Load<LanguageDTO>(StorageKeys.Language); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); }
StorageService.Load方法实现
public StorageService(ILocalStorageService storage, ILogger<StorageService> logger) { this.logger = logger is null ? new NullLogger<StorageService>() : logger; this.storage = storage; } public async Task<T> Load<T>(string key) where T : class, new() { try { var jsonString = await storage.GetItemAsStringAsync(key); if (string.IsNullOrEmpty(jsonString)) { logger.LogTrace("Local storage item with key '{}' not found. Return new object.", key); return new T(); } if (!jsonString.StartsWith("{")) { // No matter if the data is encrypted try to decrypt the value and swallow if the data was not encrypted try { jsonString = StringCipher.Decrypt(jsonString, PASSPHRASE); } catch (FormatException) { } } if (logger.IsEnabled(LogLevel.Trace)) { var jDoc = JsonDocument.Parse(jsonString, new JsonDocumentOptions { AllowTrailingCommas = true }); jsonString = System.Text.Json.JsonSerializer.Serialize(jDoc, new JsonSerializerOptions { WriteIndented = true }); logger.LogTrace("Load value from local storage with key '{key}': {jsonString}", key, jsonString); } // Deserialize the object var result = JsonConvert.DeserializeObject<T>(jsonString); if (result is null) return new T(); else return result; } catch (JSDisconnectedException) { // This exception es expected when the application try to load something but the Blazor circuit is closed i.e. when the page is refreshed with f5 return new T(); } }
问题分析与解决
核心问题原因
- Startup构造函数注入时机错误:ASP.NET Core中,Startup类的构造函数在服务容器构建之前调用,此时你注册的
IStorageService还未加入容器,构造函数注入必然失败。 - LocalStorage访问限制:你的
IStorageService依赖ILocalStorageService,而Blazor Server中ILocalStorageService需要JS互操作,只能在Blazor Circuit建立后(组件生命周期内)访问。Startup阶段属于服务器端初始化,根本无法触及浏览器的localStorage。
正确解决方案
要实现基于用户localStorage的动态语言设置,需遵循Blazor Server全球化本地化的标准流程:
1. 清理Startup中的错误代码
删除Startup构造函数里的IStorageService参数,以及Configure方法中读取localStorage的逻辑。
2. 自定义RequestCultureProvider读取用户偏好
创建自定义Provider,从cookie中获取语言设置(因为Startup阶段无法直接读localStorage,需先通过组件将localStorage值写入cookie):
public class CustomRequestCultureProvider : RequestCultureProvider { public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext) { // 优先从cookie读取语言 var cultureCookie = httpContext.Request.Cookies["UserCulture"]; if (!string.IsNullOrEmpty(cultureCookie)) { var cultureParts = cultureCookie.Split('|'); return Task.FromResult(new ProviderCultureResult(cultureParts[0], cultureParts[1])); } // 无cookie则返回默认文化 return Task.FromResult(new ProviderCultureResult("en-US", "en-US")); } }
3. 注册全球化服务并添加自定义Provider
在ConfigureServices中配置本地化:
services.AddLocalization(options => options.ResourcesPath = "Resources"); services.Configure<RequestLocalizationOptions>(options => { var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("zh-CN"), // 按需添加其他支持的语言 }; options.DefaultRequestCulture = new RequestCulture("en-US"); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures; // 将自定义Provider放到最前面,优先使用 options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider()); });
4. 启用RequestLocalization
在Configure方法中添加:
app.UseRequestLocalization();
5. 在组件中同步localStorage到cookie
在根组件(如MainLayout.razor)中,初始化时读取localStorage的语言设置,写入cookie并触发页面重载以应用文化:
@inject IStorageService StorageService @inject NavigationManager NavigationManager @inject IJSRuntime JSRuntime @code { protected override async Task OnInitializedAsync() { var language = await StorageService.Load<LanguageDTO>(StorageKeys.Language); if (language != null && !string.IsNullOrEmpty(language.Code)) { // 将语言设置写入cookie,有效期1年 await JSRuntime.InvokeVoidAsync("document.cookie", $"UserCulture={language.Code}|{language.Code}; path=/; max-age=31536000"); // 强制重载页面,应用新的文化设置 NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); } } }
额外注意点
- Blazor Server的服务器端代码无法直接访问浏览器localStorage,必须通过JS互操作,且只能在组件生命周期内执行。
- Startup阶段是应用全局初始化,无用户上下文,不能处理用户专属的localStorage数据。
内容的提问来源于stack exchange,提问作者Aka3D
相关产品推荐
相关产品推荐

