MVVM / PRISM:对话框字符串应该存储在哪里?

MVVM / PRISM: Where should dialog box strings be stored?

我正在使用 PRISM 和 MVVM 构建应用程序。我有一个视图模型需要向用户显示一个 non-modal 对话框,指示操作正在进行中。我本质上使用的是抽象的 IDialogService。

我的问题是:我应该在哪里存储标题和此对话框中显示的消息的字符串?视图模型的逻辑导致显示对话框并确定何时关闭它。因此,我的视图模型中的代码如下所示:

let! closeDlgAction = 
            dialogSvc.ShowDialogModeless (
                "Opening File",
                "Please wait while your selected file is opened.") |>  Async.AwaitTask

我在考虑本地化方案。 WPF 有自己的机制来通过资源字典等提供本地化。这些字符串似乎属于资源字典,但视图模型不应该依赖于 WPF 资源目录——尤其是因为相同的视图模型将被稍后用于 Xamarin Forms 应用程序。

想到的最佳解决方案是使用将资源库抽象出来的服务(例如 IDialogStringService),但我想知道是否有更好或更优选的方法?

您不应使用资源字典 (xaml) 来存储文本。相反,您必须使用资源 (*.resx)。在对比中:

  1. 右键单击项目
  2. 添加 -> 新项目...
  3. 找到“资源文件”模板,键入名称,然后单击添加
  4. 选择。打开此文件(将打开特殊编辑器)并在顶部栏将访问修饰符切换为 Public,如果你想从另一个项目或 XAML 访问文本。添加一些 key\value 个字符串。
  5. 右键单击资源文件并单击运行自定义工具。新 class 将生成静态属性,其名称基于步骤 4 中的密钥。

如何使用(如果文件有名称 Localizations.resx 并且有带键“AppTitle”的字符串)

来自代码:

let! closeDlgAction = 
            dialogSvc.ShowDialogModeless (
                Localizations.AppTitle,
                "Please wait while your selected file is opened.") |>  Async.AwaitTask

来自xaml:

<Window
    x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="{x:Static Localizations.AppTitle}"/>

*.resx 文件和生成的 *.cs 文件都不依赖于任何 WPF 程序集,因此您可以在不同的程序集中使用它们:共享视图模型、wpf 视图和 xamarin 视图。只需将 *.resx 文件放在单独的 netstandard 程序集中,然后在需要的地方引用它

这种方式的缺点:

  1. resx 使用字符串生成 class,每个字符串是 public 属性,因此静态代码分析有效
  2. 您没有添加新的抽象级别
  3. 您可以从代码文件或 XAML
  4. 中引用字符串

我喜欢 Vadim 的回答,我以前也用过这种方法。如果我的视图模型与 WPF 项目位于同一个项目中,那将是最好的解决方案。

但是,我的视图模型位于不同的库(和不同的语言)中,并将在 Prism MVVM WPF 项目和 Prism MVVM Xamarin Forms 项目之间共享。我仍然可以使用视图模型库中的资源,但是本地化问题将分别存在于 WPF 项目(对于视图)和视图模型库中。 IMO 本地化问题应该集中。

因此,我决定将服务背后的资源抽象化。结果证明,实施资源服务比我想象的要简单。为了直观地使用索引器,我定义了一个由 IResourceService return 编辑的“资源容器对象”,如下所示:

public struct ResourceContainer
{
    private readonly Func<string, string> _resourceGetter;

    public string this[string resourceId] => _resourceGetter(resourceId);

    public ResourceContainer(Func<string, string> resourceGetter) => _resourceGetter = resourceGetter;
}

public interface IResourceService
{
     ResourceContainer Resources { get; }
}   

而WPF库中的服务实现如下:

 public class ResourceService : IResourceService
{
    public ResourceService()
    {
        Resources = new ResourceContainer((s) => Application.Current.Resources[s] as string);
    }

    public ResourceContainer Resources { get; }
}

在WPF层的XAML资源目录中:

<s:String x:Key="FileOpenDialogTitle">Opening File</s:String>
<s:String x:Key="FileOpenDialogMessage">Please wait while your selected file is opened.</s:String>

最后,视图模型通过在其构造函数上请求 IResourceService 来使用此服务,使用方式如下:

 let! closeDlgAction = 
            dialogSvc.ShowDialogModeless (
                resourceSvc.Resources.["FileOpenDialogTitle"],
                resourceSvc.Resources.["FileOpenDialogMessage"]) |> Async.AwaitTask

这种方法最终将需要实施两次资源 - 一次用于 WPF 项目,一次用于 XF 项目,但无论如何我必须实施两次视图。至少本地化问题在这两种情况下都是集中的(或者也许可以在两个项目之间使用共享资源库)。

编辑:该技术还可以通过将本地化资源 (.resx) 也放入 WPF 项目中来利用 Vadim 的建议,并且让 XAML 资源目录引用静态资源,或者让 ResourceService return直接资源。拥有 .resx 格式的资源可能会更直接地在多个项目之间共享它们。