1.asp.net core入门到造轮子-目录2.asp.net core之Startup3.asp.net core之依赖注入4.asp.net core之中间件5.asp.net core之Host6.asp.net core之Kestrel7.asp.net core之配置8.asp.net core之Options9.asp.net core之日志10.asp.net core之路由11.asp.net core之异常处理12.asp.net core之HttpClient13.asp.net core之实时应用14.asp.net core之EfCore15.造轮子之自动依赖注入16.造轮子之日志17.造轮子之统一业务异常处理18.造轮子之统一请求响应格式19.造轮子之缓存20.造轮子之ORM集成21.造轮子之asp.net core identity22.造轮子之自定义授权策略23.造轮子之权限管理
24.造轮子之多语言管理
25.造轮子之角色管理26.造轮子之用户管理27.造轮子之菜单管理28.造轮子之属性注入配合懒加载构建服务抽象基类29.造轮子之EventBus30.造轮子之消息实时推送31.造轮子之种子数据32.造轮子之集成GraphQL33.造轮子之设置管理34.造轮子之文件管理多语言也是我们经常能用到的东西,asp.net core中默认支持了多语言,可以使用.resx资源文件来管理多语言配置。
但是在修改资源文件后,我们的应用服务无法及时更新,属实麻烦一些。我们可以通过扩展IStringLocalizer,实现我们想要的多语言配置方式,比如Json配置,PO 文件配置,EF数据库配置等等。
这里我们选用数据库配置的方式,直接查询数据库的多语言配置进行转换。
创建表实体#
多语言管理只需要两个表结构,一个是多语言国家表,一个是多语言资源表。两者是一对多关系。
namespace Wheel.Domain.Localization
{
public class LocalizationCulture : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List Resources { get; set; }
}
}
namespace Wheel.Domain.Localization
{
public class LocalizationResource : IEntity<int>
{
public int Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public virtual int CultureId { get; set; }
public virtual LocalizationCulture Culture { get; set; }
}
}
修改DbContext#
添加DbSet和配置表结构:
#region Localization
public DbSet Cultures { get; set; }
public DbSet Resources { get; set; }
#endregion
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
ConfigureIdentity(builder);
ConfigureLocalization(builder);
ConfigurePermissionGrants(builder);
}
void ConfigureLocalization(ModelBuilder builder)
{
builder.Entity(b =>
{
b.Property(a => a.Id).ValueGeneratedOnAdd();
b.ToTable("LocalizationCulture");
b.Property(a => a.Name).HasMaxLength(32);
b.HasMany(a => a.Resources);
});
builder.Entity(b =>
{
b.Property(a => a.Id).ValueGeneratedOnAdd();
b.ToTable("LocalizationResource");
b.HasOne(a => a.Culture);
b.HasIndex(a => a.CultureId);
b.Property(a => a.Key).HasMaxLength(256);
b.Property(a => a.Value).HasMaxLength(1024);
});
}
实现EF多语言#
这里我们需要实现一下EFStringLocalizerFactory和EFStringLocalizer,使用EFStringLocalizerFactory来创建EFStringLocalizer。
namespace Wheel.Localization
{
public class EFStringLocalizerFactory : IStringLocalizerFactory, ISingletonDependency
{
IServiceProvider _serviceProvider;
public EFStringLocalizerFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IStringLocalizer Create(Type resourceSource)
{
var scope = _serviceProvider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
var cahce = scope.ServiceProvider.GetRequiredService();
return new EFStringLocalizer(db, cahce);
}
public IStringLocalizer Create(string baseName, string location)
{
var scope = _serviceProvider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
var cahce = scope.ServiceProvider.GetRequiredService();
return new EFStringLocalizer(db, cahce);
}
}
}
namespace Wheel.Localization
{
public class EFStringLocalizer : IStringLocalizer
{
private readonly WheelDbContext _db;
private readonly IMemoryCache _memoryCache;
public EFStringLocalizer(WheelDbContext db, IMemoryCache memoryCache)
{
_db = db;
_memoryCache = memoryCache;
}
public LocalizedString this[string name]
{
get
{
var value = GetString(name);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}
public LocalizedString this[string name, params object[] arguments]
{
get
{
var format = GetString(name);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null);
}
}
public IStringLocalizer WithCulture(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentCulture = culture;
return new EFStringLocalizer(_db, _memoryCache);
}
public IEnumerable GetAllStrings(bool includeAncestorCultures)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.Select(r => new LocalizedString(r.Key, r.Value, r.Value == null));
}
private string? GetString(string name)
{
if (_memoryCache.TryGetValue<string>($"{CultureInfo.CurrentCulture.Name}:{name}", out var value))
{
return value;
}
else
{
value = _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.FirstOrDefault(r => r.Key == name)?.Value;
if (!string.IsNullOrWhiteSpace(value))
{
_memoryCache.Set($"{CultureInfo.CurrentCulture.Name}:{name}", value, TimeSpan.FromMinutes(1));
}
return value;
}
}
}
public class EFStringLocalizer<T> : IStringLocalizer<T>
{
private readonly WheelDbContext _db;
private readonly IMemoryCache _memoryCache;
public EFStringLocalizer(WheelDbContext db, IMemoryCache memoryCache)
{
_db = db;
_memoryCache = memoryCache;
}
public LocalizedString this[string name]
{
get
{
var value = GetString(name);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}
public LocalizedString this[string name, params object[] arguments]
{
get
{
var format = GetString(name);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null);
}
}
public IStringLocalizer WithCulture(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentCulture = culture;
return new EFStringLocalizer(_db, _memoryCache);
}
public IEnumerable GetAllStrings(bool includeAncestorCultures)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.Select(r => new LocalizedString(r.Key, r.Value, true));
}
private string? GetString(string name)
{
if (_memoryCache.TryGetValue<string>($"{CultureInfo.CurrentCulture.Name}:{name}", out var value))
{
return value;
}
else
{
value = _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.FirstOrDefault(r => r.Key == name)?.Value;
if (!string.IsNullOrWhiteSpace(value))
{
_memoryCache.Set($"{CultureInfo.CurrentCulture.Name}:{name}", value, TimeSpan.FromMinutes(1));
}
return value;
}
}
}
}
这里的GetString方法,我们先通过缓存查询多语言内容,若查询不到再进数据库查询,减少数据库的并发量。
多语言国家编码直接使用CultureInfo.CurrentCulture.Name获取。无需传参配置。
启用多语言#
再Program中添加多语言代码:
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
app.UseRequestLocalization(new RequestLocalizationOptions
{
ApplyCurrentCultureToResponseHeaders = true,
DefaultRequestCulture = new RequestCulture("zh-CN"),
SupportedCultures = new List
{
new CultureInfo("en"),
new CultureInfo("zh-CN"),
},
SupportedUICultures = new List
{
new CultureInfo("en"),
new CultureInfo("zh-CN"),
}
});
多语言管理API实现#
接下来我们实现LocalizationManage
ILocalizationManageAppService:
namespace Wheel.Services.LocalizationManage
{
public interface ILocalizationManageAppService : ITransientDependency
{
Task> GetLocalizationCultureAsync(int id);
Task> GetLocalizationCulturePageListAsync(PageRequest input);
Task> CreateLocalizationCultureAsync(CreateLocalizationCultureDto input);
Task DeleteLocalizationCultureAsync(int id) ;
Task> CreateLocalizationResourceAsync(CreateLocalizationResourceDto input);
Task UpdateLocalizationResourceAsync(UpdateLocalizationResourceDto input) ;
Task DeleteLocalizationResourceAsync(int id) ;
}
}
LocalizationManageAppService:
namespace Wheel.Services.LocalizationManage
{
///
/// 多语言管理
///
public class LocalizationManageAppService : WheelServiceBase, ILocalizationManageAppService
{
private readonly IBasicRepositoryint> _localizationCultureRepository;
private readonly IBasicRepositoryint> _localizationResourceRepository;
public LocalizationManageAppService(IBasicRepositoryint > localizationCultureRepository, IBasicRepositoryint > localizationResourceRepository)
{
_localizationCultureRepository = localizationCultureRepository;
_localizationResourceRepository = localizationResourceRepository;
}
///
/// 获取地区多语言详情
///
///
///
public async Task> GetLocalizationCultureAsync(int id)
{
var entity = await _localizationCultureRepository.FindAsync(id);
return new R(Mapper.Map(entity));
}
///
/// 分页获取地区多语言列表
///
///
///
public async Task> GetLocalizationCulturePageListAsync(PageRequest input)
{
var (entities, total) = await _localizationCultureRepository
.GetPageListAsync(a => true,
(input.PageIndex - 1) * input.PageSize,
input.PageSize,
propertySelectors: a => a.Resources
);
return new Page(Mapper.Map>(entities), total);
}
///
/// 创建地区多语言
///
///
///
public async Task> CreateLocalizationCultureAsync(CreateLocalizationCultureDto input)
{
var entity = Mapper.Map(input);
entity = await _localizationCultureRepository.InsertAsync(entity);
await UnitOfWork.SaveChangesAsync();
return new R(Mapper.Map(entity));
}
///
/// 删除地区多语言
///
///
///
public async Task DeleteLocalizationCultureAsync(int id)
{
await _localizationCultureRepository.DeleteAsync(id);
await UnitOfWork.SaveChangesAsync();
return new R();
}
///
/// 创建多语言资源
///
///
///
public async Task> CreateLocalizationResourceAsync(CreateLocalizationResourceDto input)
{
var entity = Mapper.Map(input);
entity = await _localizationResourceRepository.InsertAsync(entity);
await UnitOfWork.SaveChangesAsync();
return new R(Mapper.Map(entity));
}
///
/// 修改多语言资源
///
///
///
public async Task UpdateLocalizationResourceAsync(UpdateLocalizationResourceDto input)
{
await _localizationResourceRepository.UpdateAsync(a => a.Id == input.Id,
a => a.SetProperty(b => b.Key, b => input.Key).SetProperty(b => b.Value, b => input.Value));
await UnitOfWork.SaveChangesAsync();
return new R();
}
///
/// 删除多语言资源
///
///
///
public async Task DeleteLocalizationResourceAsync(int id)
{
await _localizationResourceRepository.DeleteAsync(id);
await UnitOfWork.SaveChangesAsync();
return new R();
}
}
}
这里包含了多语言的CURD的实现
LocalizationManageController:
namespace Wheel.Controllers
{
///
/// 多语言管理
///
[Route("api/[controller]")]
[ApiController]
public class LocalizationManageController : WheelControllerBase
{
private readonly ILocalizationManageAppService _localizationManageAppService;
public LocalizationManageController(ILocalizationManageAppService localizationManageAppService)
{
_localizationManageAppService = localizationManageAppService;
}
///
/// 获取地区多语言详情
///
///
///
[HttpGet("Culture/{id}")]
public async Task> GetCulture(int id)
{
return await _localizationManageAppService.GetLocalizationCultureAsync(id);
}
///
/// 创建地区多语言
///
///
///
[HttpPost("Culture")]
public async Task> CreateCulture(CreateLocalizationCultureDto input)
{
return await _localizationManageAppService.CreateLocalizationCultureAsync(input);
}
///
/// 删除地区多语言
///
///
///
[HttpDelete("Culture/{id}")]
public async Task DeleteCulture(int id)
{
return await _localizationManageAppService.DeleteLocalizationCultureAsync(id);
}
///
/// 分页获取地区多语言列表
///
///
///
[HttpGet("Culture")]
public async Task> GetCulturePageList([FromQuery]PageRequest input)
{
return await _localizationManageAppService.GetLocalizationCulturePageListAsync(input);
}
///
/// 创建多语言资源
///
///
///
[HttpPost("Resource")]
public async Task> CreateResource(CreateLocalizationResourceDto input)
{
return await _localizationManageAppService.CreateLocalizationResourceAsync(input);
}
///
/// 修改多语言资源
///
///
///
[HttpPut("Resource")]
public async Task UpdateResource(UpdateLocalizationResourceDto input)
{
return await _localizationManageAppService.UpdateLocalizationResourceAsync(input);
}
///
/// 删除多语言资源
///
///
///
[HttpDelete("Resource/{id}")]
public async Task DeleteResource(int id)
{
return await _localizationManageAppService.DeleteLocalizationResourceAsync(id);
}
///
/// 获取多语言资源列表
///
///
///
[HttpGet("Resources")]
[AllowAnonymous]
public Taskstring, string>>> GetResources()
{
var resources = L.GetAllStrings().ToDictionary(a=>a.Name, a=>a.Value);
return Task.FromResult(new Rstring, string>>(resources));
}
}
}
在控制器额外添加一个匿名访问的API,GetResources()用于客户端集成多语言配置。L是IStringLocalizer实例。
就这样我们完成多语言管理的实现。