Blazor本地化:实现IStringLocalizer以Azure Blob替代.resx文件
实现基于Azure Blob的IStringLocalizer替代Resx文件
整体思路
通过实现ASP.NET Core的IStringLocalizer和IStringLocalizerFactory接口,将Azure Blob存储的JSON格式键值对作为本地化数据源,替代传统的.resx文件。
步骤1:定义Blob存储结构
在Azure Blob容器中创建本地化资源目录(比如localization-resources/),每个语言对应一个JSON文件,命名规则为{资源组}.{文化代码}.json:
- 示例文件:
resources.en-US.json、resources.zh-CN.json、Home.zh-TW.json - JSON格式示例(
resources.zh-CN.json):
{ "Welcome": "欢迎", "HelloWorld": "你好,世界!", "Greeting": "你好,{0}!" }
步骤2:实现IStringLocalizer
创建BlobStringLocalizer类,实现IStringLocalizer接口,负责从Blob加载翻译内容:
using Azure.Storage.Blobs; using Microsoft.Extensions.Localization; using System.Globalization; using System.Text.Json; public class BlobStringLocalizer : IStringLocalizer { private readonly BlobContainerClient _containerClient; private readonly CultureInfo _culture; private readonly string _resourceBaseName; private Dictionary<string, string> _translations = new(); public BlobStringLocalizer(BlobContainerClient containerClient, CultureInfo culture, string resourceBaseName = "resources") { _containerClient = containerClient; _culture = culture; _resourceBaseName = resourceBaseName; _ = LoadTranslationsAsync(); // 异步加载,避免阻塞构造函数 } private async Task LoadTranslationsAsync() { // 优先加载当前文化的资源文件 var blobName = $"localization-resources/{_resourceBaseName}.{_culture.Name}.json"; var blobClient = _containerClient.GetBlobClient(blobName); if (await blobClient.ExistsAsync()) { using var stream = await blobClient.OpenReadAsync(); _translations = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream) ?? new(); return; } // 当前文化不存在时,回退到父文化(比如zh-CN回退到zh) if (!string.IsNullOrEmpty(_culture.Parent.Name)) { var parentBlob = _containerClient.GetBlobClient($"localization-resources/{_resourceBaseName}.{_culture.Parent.Name}.json"); if (await parentBlob.ExistsAsync()) { using var stream = await parentBlob.OpenReadAsync(); _translations = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream) ?? new(); return; } } // 最后回退到默认文化(en-US) var fallbackBlob = _containerClient.GetBlobClient($"localization-resources/{_resourceBaseName}.en-US.json"); if (await fallbackBlob.ExistsAsync()) { using var stream = await fallbackBlob.OpenReadAsync(); _translations = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream) ?? new(); } } public LocalizedString this[string name] { get { var exists = _translations.TryGetValue(name, out var value); return new LocalizedString(name, exists ? value : name, !exists); } } public LocalizedString this[string name, params object[] arguments] { get { var baseString = this[name]; return new LocalizedString(name, string.Format(baseString.Value, arguments), baseString.ResourceNotFound); } } public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) { foreach (var kvp in _translations) { yield return new LocalizedString(kvp.Key, kvp.Value, false); } // 若需要包含父文化资源,递归加载父文化的所有字符串 if (includeParentCultures && !string.IsNullOrEmpty(_culture.Parent.Name)) { var parentLocalizer = new BlobStringLocalizer(_containerClient, _culture.Parent, _resourceBaseName); foreach (var str in parentLocalizer.GetAllStrings(false)) { if (!_translations.ContainsKey(str.Name)) { yield return str; } } } } public IStringLocalizer WithCulture(CultureInfo culture) { return new BlobStringLocalizer(_containerClient, culture, _resourceBaseName); } }
步骤3:实现IStringLocalizerFactory
创建工厂类,负责根据请求的资源组和文化创建对应的BlobStringLocalizer实例:
using Microsoft.Extensions.Localization; using System.Globalization; using Azure.Storage.Blobs; public class BlobStringLocalizerFactory : IStringLocalizerFactory { private readonly BlobContainerClient _containerClient; public BlobStringLocalizerFactory(BlobContainerClient containerClient) { _containerClient = containerClient; } public IStringLocalizer Create(Type resourceSource) { // 以类名作为资源组名称(比如HomeController对应Home资源文件) var resourceBaseName = resourceSource.Name.Replace("Controller", ""); return new BlobStringLocalizer(_containerClient, CultureInfo.CurrentUICulture, resourceBaseName); } public IStringLocalizer Create(string baseName, string location) { // 直接使用传入的baseName作为资源组名称 return new BlobStringLocalizer(_containerClient, CultureInfo.CurrentUICulture, baseName); } }
步骤4:注册服务到ASP.NET Core
在Program.cs中配置Azure Blob服务和本地化服务:
using Azure.Storage.Blobs; using Microsoft.AspNetCore.Localization; using System.Globalization; var builder = WebApplication.CreateBuilder(args); // 注册Azure Blob容器客户端 builder.Services.AddSingleton(sp => { var connectionString = builder.Configuration.GetConnectionString("AzureBlobStorage"); // 替换为你的Blob容器名称 return new BlobContainerClient(connectionString, "localization-container"); }); // 替换默认的本地化器工厂 builder.Services.AddSingleton<IStringLocalizerFactory, BlobStringLocalizerFactory>(); // 配置本地化支持(无需指定ResourcesPath,因为不用resx) builder.Services.AddLocalization(); // 添加MVC并启用视图和数据注解本地化 builder.Services.AddControllersWithViews() .AddViewLocalization() .AddDataAnnotationsLocalization(options => { options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(type); }); // 配置请求本地化规则 builder.Services.Configure<RequestLocalizationOptions>(options => { var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("zh-CN"), new CultureInfo("zh-TW") }; options.DefaultRequestCulture = new RequestCulture("en-US"); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures; }); var app = builder.Build(); // 启用请求本地化中间件(必须在路由之前) app.UseRequestLocalization(); // 其他中间件配置 app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
步骤5:在代码中使用本地化器
和使用默认.resx本地化的方式完全一致,比如在控制器中注入:
public class HomeController : Controller { private readonly IStringLocalizer<HomeController> _localizer; public HomeController(IStringLocalizer<HomeController> localizer) { _localizer = localizer; } public IActionResult Index() { ViewData["WelcomeMessage"] = _localizer["Welcome"]; ViewData["Greeting"] = _localizer["Greeting", "张三"]; return View(); } }
优化建议
- 添加缓存:避免每次请求都加载Blob文件,可使用
IMemoryCache或IDistributedCache缓存已加载的翻译内容,缓存键可以是{资源组}-{文化代码}。 - 异步初始化改进:可以使用
IAsyncDisposable或自定义异步初始化逻辑,确保翻译内容加载完成后再处理请求。 - 资源热更新:监听Blob文件的变化,自动刷新缓存中的翻译内容,无需重启应用。
- 权限控制:如果Blob容器是私有的,建议使用Azure Managed Identity替代连接字符串,提升安全性。
内容的提问来源于stack exchange,提问作者Kasun Jalitha
相关产品推荐
相关产品推荐

