使用DI框架进行本地化 - 好主意?

Dav*_*all 5 language-agnostic dependency-injection localization

我正在开发一个我需要本地化和国际化的Web应用程序.在我看来,我可以使用依赖注入框架来做到这一点.假设我声明了一个接口ILocalResources(在这个例子中使用C#,但这并不重要):

interface ILocalResources {
    public string OkString { get; }
    public string CancelString { get; }
    public string WrongPasswordString { get; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

并创建此接口的实现,每个语言需要支持一个.然后,我将设置我的DI框架,以静态或动态地实例化正确的实现(例如,基于请求的浏览器首选语言).

有什么理由我不应该使用DI框架来做这种事情吗?我能找到的唯一反对意见是它可能有点矫枉过正,但如果我在我的网络应用程序中使用DI框架,我不妨用它进行国际化?

Ste*_*ven 6

构建DI框架以进行依赖注入,本地化可能只是您的服务之一,因此在这种情况下没有理由使用DI框架IMO.也许我们应该开始讨论提供的ILocalResources接口.虽然我支持编译时支持,但我不确定所提供的接口是否会对您有所帮助,因为该接口可能是您系统中将发生最大变化的类型.并使用该接口实现它的类型/类型.也许你应该采用不同的设计.

当我们查看大多数本地化框架/提供者/工厂(或其他)时,它们都是基于字符串的.因此,请考虑以下设计:

public interface ILocalResources
{
    string GetStringResource(string key);
    string GetStringResource(string key, CultureInfo culture);
}
Run Code Online (Sandbox Code Playgroud)

这将允许您将键和文化添加到基础消息数据存储,而无需更改接口.下行当然是你永远不应该换钥匙,因为这可能是一个地狱.

另一种方法可以是抽象基类型:

public abstract class LocalResources
{
    public string OkMessage { get { return this.GetString("OK"); } }
    public string CancelMessage { get { return this.GetString("Cancel"); } }
    ...

    protected abstract string GetStringResource(string key, 
        CultureInfo culture);

    private string GetString(string key)
    {
        Culture culture = CultureInfo.CurrentCulture;

        string resource = GetStringResource(key, culture);

        // When the resource is not found, fall back to the neutral culture.
        while (resource == null && culture != CultureInfo.InvariantCulture)
        {
            culture = culture.Parent;
            resource = this.GetStringResource(key, culture);
        }

        if (resource == null) throw new KeyNotFoundException(key);

        return resource;
    }
}
Run Code Online (Sandbox Code Playgroud)

此类型的实现可能如下所示:

public sealed class SqlLocalResources : LocalResources
{
    protected override string GetStringResource(string key, 
        CultureInfo culture)
    {
        using (var db = new LocalResourcesContext())
        {
            return (
                from resource in db.StringResources
                where resource.Culture == culture.Name
                where resource.Key == key
                select resource.Value).FirstOrDefault();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法在两个方面都是最好的,因为密钥不会分散在应用程序中,添加新属性只需要在一个地方完成.使用您喜欢的 DI库,您可以注册这样的实现:

container.RegisterSingleton<LocalResources>(new SqlLocalResources());
Run Code Online (Sandbox Code Playgroud)

由于LocalResources类型只有一个抽象方法可以完成所有工作,因此很容易创建一个添加缓存的装饰器,以防止从数据库中请求相同的数据:

public sealed class CachedLocalResources : LocalResources
{
    private readonly Dictionary<CultureInfo, Dictionary<string, string>> cache =
        new Dictionary<CultureInfo, Dictionary<string, string>>();
    private readonly LocalResources decoratee;

    public CachedLocalResources(LocalResources decoratee) { this.decoratee = decoratee; }

    protected override string GetStringResource(string key, CultureInfo culture) {
        lock (this.cache) {
            string res;
            var cultureCache = this.GetCultureCache(culture);
            if (!cultureCache.TryGetValue(key, out res)) {
                cultureCache[key] = res= this.decoratee.GetStringResource(key, culture);
            }                
            return res;
        }
    }

    private Dictionary<string, string> GetCultureCache(CultureInfo culture) {
        Dictionary<string, string> cultureCache;
        if (!this.cache.TryGetValue(culture, out cultureCache)) {
            this.cache[culture] = cultureCache = new Dictionary<string, string>();
        }
        return cultureCache;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以按如下方式应用装饰器:

container.RegisterSingleton<LocalResources>(
    new CachedLocalResources(new SqlLocalResources()));
Run Code Online (Sandbox Code Playgroud)

请注意,此装饰器无限期地缓存字符串资源,这可能会导致内存泄漏,因此您希望将字符串包装在WeakReference实例中或对其进行某种过期超时.但这个想法是你可以在不改变任何现有实现的情况下应用缓存.

我希望这有帮助.


Pet*_*old 2

如果您无法使用现有的资源框架(例如 ASP.Net 中内置的资源框架)并且必须构建自己的资源框架,我将假设您在某些时候需要公开提供本地化资源的服务。

DI 框架用于处理服务实例化。您的本地化框架将公开提供本地化的服务。为什么该服务不应该由框架提供?

此处不使用 DI 就像说:“我正在构建 CRM 应用程序,但不能使用 DI,因为 DI 不是为客户关系管理而构建的”。

所以,是的,如果您已经在应用程序的其余部分中使用 DI,那么在我看来,不将其用于处理本地化的服务是错误的。