2022年4月

本来最简单的做法,就是将语言选择放在cookie中,但出于对SEO的考虑,这样做无法让搜索引擎抓取多语种页面,最好的做法,还是不同语言有不同的路径,例如:

http://www.xxx.com/zh-ch/products
http://www.xxx.com/en-us/products
....

静态类 Langs.cs 中,定义可以用的语言:

    public readonly static string[] LangCodes = new string[] { "vi-vn", "en-us", "zh-cn" };

识别用的路由约束类,其中的 "auto" 是特殊定义的,主要用于用户第一次进入时对用户的 Accept-Languages 进行自动识别:

public class LanguageRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.ContainsKey("culture"))
            return false;

        var culture = values["culture"]?.ToString();
        if (culture == null) return false;
        return culture == "auto" || Langs.LangCodes.Contains(culture);
    }
}

路由约束:

public class LanguageRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.ContainsKey("culture"))
            return false;

        var culture = values["culture"]?.ToString();
        if (culture == null) return false;
        return culture == "auto" || Langs.LangCodes.Contains(culture);
    }
}

然后在 program.cs 中配置:

builder.Services.AddRazorPages().AddRazorPagesOptions(options =>
{
    options.Conventions.Add(new CulturePageRouteConvention());
}).AddRazorRuntimeCompilation();

builder.Services.Configure<RouteOptions>(options =>
{
    options.ConstraintMap.Add("culture", typeof(LanguageRouteConstraint));
});

builder.Services.Configure<RouteOptions>(options =>
{
    options.ConstraintMap.Add("culture", typeof(LanguageRouteConstraint));
});

路由:

app.MapControllerRoute(name: "default", pattern: "{culture:culture=auto}/{controller:slugify=Home}/{action:slugify=Index}/{id?}");

很多人会直接用静态类做内存缓存,包括我自己之前也是这样做,但这样内存的生命周期及GC机制得不到保障,特别是对于多线程的控制,静态类都是无能为力。

另外就是 MemoryCache,很多人都习惯用他的 MemoryCache.TryGetValue 来实现自动缓存,这对于小项目及访问量不大的项目是可行的,但如果对于大项目,这样会导致访问很慢,因为是被动式的去拉取数据,有没有一种更好的方式呢,好在net core 提供了 BackgroundService 及 IHostedService,两者类似,可以完美的解决我们的需求,这里我就举例说一下使用 IHostedService 来实现主动式缓存网站首页的全部数据,以实现最快速的首页访问。

新建逻辑处理服务类:

public class DbServices : IHostedService, IDisposable
{
    CancellationToken cancelToken;
    HomeData? datas;
    Task? mainTask;
    private ILogger logger;

    public DbServices(ILogger logger)
    {
        this.logger=logger;
    }

    public HomeData? GetHomeData()
    {
        return datas;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        cancelToken = cancellationToken;
        mainTask = Task.Factory.StartNew(HomeDataWork, cancellationToken);
        return Task.CompletedTask;
    }
    private async void HomeDataWork()
    {
        DateTime last = DateTime.Now.AddMinutes(-2);
        while (!cancelToken.IsCancellationRequested)
        {
            int sec = 30000 - (int)(DateTime.Now - last).TotalMilliseconds;
            if (sec > 0)
                await Task.Delay(sec);
            last = DateTime.Now;
            HomeData? _data = .....;//实现获取数据
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        mainTask?.Dispose();
    }
}

Program.cs 中:

builder.Services.AddSingleton<DbServices>();
builder.Services.AddHostedService<DbServices>(provider => provider.GetService<DbServices>());

大功造成,该服务会在后台主动更新内存数据,首页可以直接无任何延迟直接获取数据:

DbServices? service = MyHttpContext.Current?.RequestServices.GetService(typeof(DbServices)) as DbServices;
HomeData = service?.GetHomeData(Langs.Instance.LangCode) ?? new();

直接编辑 xxx.csproj 项目文件:

<PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <!--不打包视图文件-->
    <RazorCompileOnBuild>false</RazorCompileOnBuild>
    <RazorCompileOnPublish>false</RazorCompileOnPublish>
    <!--防止生成很多语言资源包-->
    <SatelliteResourceLanguages>zh-Hans;vi</SatelliteResourceLanguages>
</PropertyGroup>

Program.cs 中:

var serviceProvider = builder.Services.BuildServiceProvider();
var logger = serviceProvider.GetService<ILogger<MyServices>>();
builder.Services.AddSingleton(typeof(ILogger), logger);

这样就会将 ILogger 注入到相关类中,可以直接通过构造函数来获取了:

private ILogger logger;
public void MyServices(ILogger logger)
{
    this.logger = logger;
    ...
}