如何将 C# 资源文件字符串转换为方法而不仅仅是属性?

How to convert C# Resource File Strings into methods and not just properties?

例如,EntityFramework Microsoft.EntityFrameworkCore.Relational 项目在资源文件中有以下文本:

...
<data name="FromSqlMissingColumn" xml:space="preserve">
  <value>The required column '{column}' was not present in the results of a 'FromSql' operation.</value>
</data>
...

生成以下 C# 代码:

...
/// <summary>
/// The required column '{column}' was not present in the results of a 'FromSql' operation.
/// </summary>
public static string FromSqlMissingColumn([CanBeNull] object column)
{
    return string.Format(CultureInfo.CurrentCulture, GetString("FromSqlMissingColumn", "column"), column);
}
...
private static string GetString(string name, params string[] formatterNames)
{
    var value = _resourceManager.GetString(name);

    Debug.Assert(value != null);

    if (formatterNames != null)
    {
        for (var i = 0; i < formatterNames.Length; i++)
        {
            value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
        }
    }

    return value;
}
...

但是当我在 VS 中编辑文件并保存时,我只生成了简单的属性,例如:

...
/// <summary>
/// The required column '{column}' was not present in the results of a 'FromSql' operation.
/// </summary>
public static string FromSqlMissingColumn
{
    get { return ResourceManager.GetString("FromSqlMissingColumn"); }
}
...

有问题的文件可以在这里找到:

所以问题又来了——他们是怎么做到的,我怎么能得到相同的结果?

How did they do it?

首先很明显,他们不使用标准 ResXFileCodeGenerator,而是使用一些自定义代码生成工具。

目前有 2 种生成代码的标准方法 - 使用类似于 ResXFileCodeGeneratorCustom Tool 的老式方法,或使用 T4 Template 的现代方法。让我们看看。

Microsoft.EntityFrameworkCore.Relational.csproj 文件中的相应条目如下所示:

<ItemGroup> 
    <EmbeddedResource Include="Properties\RelationalStrings.resx">
        <LogicalName>Microsoft.EntityFrameworkCore.Relational.Properties.RelationalStrings.resources</LogicalName> 
    </EmbeddedResource> 
</ItemGroup> 

正如我们所见,他们肯定不会使用Custom Tool

所以应该是T4模板。确实在上面的项目之后我们可以看到:

<ItemGroup> 
    <Content Include="..\..\tools\Resources.tt"> 
        <Link>Properties\Resources.tt</Link> 
            <Generator>TextTemplatingFileGenerator</Generator> 
            <LastGenOutput>Resources.cs</LastGenOutput> 
            <CustomToolNamespace>Microsoft.EntityFrameworkCore.Internal</CustomToolNamespace> 
    </Content> 
    <Content Include="Properties\Microsoft.EntityFrameworkCore.Relational.rd.xml" /> 
</ItemGroup> 

好了!

现在,如果不深入研究实现,我不知道包含的 xml 文件的目的是什么(它可能是生成器使用的东西,比如选项或其他东西),但实际代码生成包含在以下 Resources.tt 文件中。

how could I get the same result?

我猜你是在问你自己的项目。好吧,你可以做类似的事情。 Select 您的 resx 文件,转到 Properties 并清除 Custom Tool。然后将 T4 template 添加到您的项目并编写代码生成(我不确定许可证是否允许您使用他们的代码,所以如果您要这样做,请务必先检查是否允许)。但是原理是一样的

我认为,EF 团队为此目的使用了自己的自定义 Custom Tool。但是 visual studio 使用 PublicResXFileCodeGenerator 作为 .resx 文件的默认自定义工具,此工具没有 PublicResXFileCodeGenerator 等功能,它是基础 class ResXFileCodeGenerator (两者都可以在 Microsoft.VisualStudio.Design 程序集中找到)只是 visual studio 围绕 StronglyTypedResourceBuilder 的包装器。

它们是工具 IVsSingleFileGenerator(位于 Microsoft.VisualStudio.Shell.Interop 程序集)。因此,您可以从这里开始实施自己的 Custom Tool。开始新的 Class Library,添加 Microsoft.VisualStudio.Shell.14.0Microsoft.VisualStudio.Shell.Interop 引用。新建class并实现这个接口。接口 IVsSingleFileGenerator 非常简单。它只包含两个方法:

  • DefaultExtension which returns extension for generated file (with a leading period) as out string pbstrDefaultExtension paratemer and VSConstant.S_OK as return值(当然如果一切都OK)。

  • Generate 这是接受:

    • wszInputFilePath - 输入文件路径,可以为空,请勿使用。
    • bstrInputFileContents - 输入文件内容,应该使用。
    • wszDefaultNamespace - 默认命名空间(现在无法说明为什么 ResXFileCodeGenerator 与 Visual Studio 互操作以获取命名空间而不是使用此参数)。
    • rgbOutputFileContents - 生成文件的字节数组。您必须在 returned 字节数组中包含 UNICODE 或 UTF-8 签名字节,因为这是原始流。必须使用 .NET Framework 调用 Marshal.AllocCoTaskMem 或等效的 Win32 系统调用 CoTaskMemAlloc 为 rgbOutputFileContents 分配内存。项目系统负责释放此内存。
    • pcbOutput - rgbOutputFileContent 数组中的字节数。
    • pGenerateProgress - 对 IVsGeneratorProgress 接口的引用,生成器可以通过该接口向项目系统报告其进度。

    和returns VSConstant.S_OK如果一切正常,或者相应的错误码。

还有small guide about implementation. But this guide does not tells too much. Most useful thing is how to register自己的发电机。

你最好深入研究 ResXFileCodeGenerator code (or simply decompile) for implementation example or to get some hints such as how to interop with visual studio. But I see no reason to interop with VS as everything you need you already provided. .resx file content could be read by ResXResourceReader.FromFileContents

其余的一切都会很简单,因为您有资源名称和值,只需要 return 生成文件的字节数组。我认为,解析资源值以产生无效格式的编译时错误(例如:{{param}}})将是最大的困难。

当为即将到来的方法解析值和参数时,您可以生成代码(再次作为示例,您可以参考 ResXFileCodeGeneratorStronglyTypedResourceBuilder,或者按照您想要的方式自行完成,通过 CodeDom 或手动编写源代码文本)。这也不应该很困难,因为您已经在发布的问题中有了需要生成的方法示例。

编译你自己的生成器,注册它,在你的 .resx 文件的 Custom Tool 属性 中设置它,你将获得资源 classes 与方法而不是属性.

您也可以在 github 上与他人分享。 :)


这是来自 custom tool registration 的说明(因为 msdn link 可能迟早会死):

要使自定义工具在 Visual Studio 中可用,您必须注册它,以便 Visual Studio 可以实例化它并将其与特定项目类型相关联。

  1. 在 Visual Studio 本地注册表或 HKEY_CLASSES_ROOT.

    下的系统注册表中注册自定义工具 DLL

    例如,这里是 Visual Studio 附带的托管 MSDataSetGenerator 自定义工具的注册信息:

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio.0\CLSID\{E76D53CC-3D4F-40A2-BD4D-4F3419755476}]
    @="COM+ class: Microsoft.VSDesigner.CodeGenerator.TypedDataSourceGenerator.DataSourceGeneratorWrapper"
    "InprocServer32"="C:\WINDOWS\system32\mscoree.dll"
    "ThreadingModel"="Both"
    "Class"="Microsoft.VSDesigner.CodeGenerator.TypedDataSourceGenerator.DataSourceGeneratorWrapper"
    "Assembly"="Microsoft.VSDesigner, Version=14.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a"
    
  2. Generators\GUID 下所需的 Visual Studio 配置单元中创建注册表项,其中 GUID 是由特定语言的项目系统或服务定义的 GUID。密钥的名称成为您的自定义工具的编程名称。自定义工具键具有以下值:

    • (Default) - 可选。提供自定义工具的用户友好描述。此参数是可选的,但建议使用。

    • CLSID - 必需。指定实现 IVsSingleFileGenerator 的 COM 组件的 class 库的标识符。

    • GeneratesDesignTimeSource - 必需。指示此自定义工具生成的文件中的类型是否可供视觉设计人员使用。此参数的值需要为(零)0(对于视觉设计者不可用的类型)或(一)1(对于视觉设计者可用的类型)。

    请注意,您必须为您希望自定义工具可用的每种语言单独注册自定义工具。

    例如,MSDataSetGenerator 为每种语言注册一次:

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio.0\Generators\{164b10b9-b200-11d0-8c61-00a0c91e29d5}\MSDataSetGenerator]
    @="Microsoft VB Code Generator for XSD"
    "CLSID"="{E76D53CC-3D4F-40a2-BD4D-4F3419755476}"
    "GeneratesDesignTimeSource"=dword:00000001
    
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio.0\Generators\{fae04ec1-301f-11d3-bf4b-00c04f79efbc}\MSDataSetGenerator]
    @="Microsoft C# Code Generator for XSD"
    "CLSID"="{E76D53CC-3D4F-40a2-BD4D-4F3419755476}"
    "GeneratesDesignTimeSource"=dword:00000001
    
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio.0\Generators\{e6fdf8b0-f3d1-11d4-8576-0002a516ece8}\MSDataSetGenerator]
    @="Microsoft J# Code Generator for XSD"
    "CLSID"="{E76D53CC-3D4F-40a2-BD4D-4F3419755476}"
    "GeneratesDesignTimeSource"=dword:00000001