Blazor Server项目发布后如何编辑.resx资源文件?
在Blazor Server项目中实现发布后编辑.resx资源文件的功能
要实现发布后编辑.resx文件的需求,核心是避免资源文件被嵌入程序集,同时修改资源读取逻辑为直接读取物理文件,以下是具体步骤:
1. 修改项目配置,确保.resx文件被复制到发布目录
默认情况下,.resx文件会被嵌入程序集,无法在发布后直接编辑。需要修改项目文件(.csproj),将目标.resx文件设置为Content类型并指定发布时复制:
<ItemGroup> <!-- 配置需要编辑的.resx文件,发布时复制到输出目录 --> <Content Include="Resources\*.resx"> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </Content> <!-- 移除默认的嵌入资源配置,避免资源被打包进程序集 --> <EmbeddedResource Remove="Resources\*.resx" /> </ItemGroup>
注意:将
Resources\*.resx替换为你实际的资源文件路径和匹配规则。
2. 实现自定义资源读取服务
创建一个服务类,负责从发布目录的物理文件中读取.resx内容,替代默认的嵌入资源读取逻辑:
using System.Resources; using Microsoft.AspNetCore.Hosting; public class PhysicalResourceReader { private readonly string _resourceDirPath; public PhysicalResourceReader(IWebHostEnvironment env) { // 指向发布目录下的Resources文件夹,可根据实际结构调整 _resourceDirPath = Path.Combine(env.ContentRootPath, "Resources"); } public string GetResourceString(string resourceFileName, string key) { var fullFilePath = Path.Combine(_resourceDirPath, $"{resourceFileName}.resx"); // 文件不存在时返回key本身作为 fallback if (!File.Exists(fullFilePath)) return key; using var reader = new ResXResourceReader(fullFilePath); var resourceDict = reader.Cast<DictionaryEntry>() .ToDictionary(entry => entry.Key.ToString(), entry => entry.Value.ToString()); return resourceDict.TryGetValue(key, out var value) ? value : key; } }
在Program.cs中注册这个服务:
builder.Services.AddScoped<PhysicalResourceReader>();
3. 在Blazor组件中使用自定义资源读取服务
在需要显示资源字符串的Razor组件中,注入服务并调用方法获取内容:
@inject PhysicalResourceReader ResourceReader <div> <h3>@ResourceReader.GetResourceString("AppResources", "WelcomeTitle")</h3> <p>@ResourceReader.GetResourceString("AppResources", "WelcomeContent")</p> </div>
4. 发布后编辑.resx的注意事项
- 发布完成后,
publish目录下会生成Resources文件夹(对应你配置的路径),直接用文本编辑器或专门的.resx编辑器修改文件即可。 - 上述读取逻辑每次都会读取物理文件,修改后无需重启应用即可生效。如果需要优化性能,可以添加内存缓存(如下方示例),但需配套缓存刷新机制。
- 确保应用进程对
publish/Resources目录有读写权限(比如IIS应用池身份需具备该目录的权限)。
可选:添加缓存优化(避免重复读取文件)
如果资源文件不会频繁修改,可添加内存缓存减少IO操作,同时提供手动刷新缓存的方法:
using System.Resources; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Caching.Memory; public class CachedPhysicalResourceReader { private readonly string _resourceDirPath; private readonly IMemoryCache _cache; private readonly MemoryCacheEntryOptions _cacheOptions = new() .SetSlidingExpiration(TimeSpan.FromMinutes(15)); public CachedPhysicalResourceReader(IWebHostEnvironment env, IMemoryCache cache) { _resourceDirPath = Path.Combine(env.ContentRootPath, "Resources"); _cache = cache; } public string GetResourceString(string resourceFileName, string key) { var cacheKey = $"Res_{resourceFileName}_{key}"; // 优先从缓存读取 if (_cache.TryGetValue(cacheKey, out string value)) return value; var fullFilePath = Path.Combine(_resourceDirPath, $"{resourceFileName}.resx"); if (!File.Exists(fullFilePath)) return key; using var reader = new ResXResourceReader(fullFilePath); var resourceDict = reader.Cast<DictionaryEntry>() .ToDictionary(entry => entry.Key.ToString(), entry => entry.Value.ToString()); value = resourceDict.TryGetValue(key, out var val) ? val : key; _cache.Set(cacheKey, value, _cacheOptions); return value; } // 手动刷新指定资源文件的缓存(可在管理页面调用) public void RefreshResourceCache(string resourceFileName) { var cacheKeys = _cache.GetKeys().Where(k => k.ToString().StartsWith($"Res_{resourceFileName}_")); foreach (var key in cacheKeys) { _cache.Remove(key); } } }
注册缓存版服务:
builder.Services.AddScoped<CachedPhysicalResourceReader>(); builder.Services.AddMemoryCache();
内容的提问来源于stack exchange,提问作者Shyam Sunder Agrahari
相关产品推荐
相关产品推荐

