You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题分析与解决

核心问题原因

  1. Startup构造函数注入时机错误:ASP.NET Core中,Startup类的构造函数在服务容器构建之前调用,此时你注册的IStorageService还未加入容器,构造函数注入必然失败。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 03:49:55