Как конвертировать С# Resource File Strings в методы, а не только свойства? - программирование

Как конвертировать С# Resource File Strings в методы, а не только свойства?

Пример: проект 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>
...

который генерирует следующий код С#:

...
/// <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"); }
}
...

Эти файлы можно найти здесь:

Итак, вопрос снова: как они это сделали, и как я мог получить тот же результат?

4b9b3361

Ответ 1

Как они это сделали?

Во-первых, должно быть очевидно, что они не используют стандартный ResXFileCodeGenerator, а некоторые специальные инструменты генерации кода.

В настоящее время существует 2 стандартных способа генерации кода - старый способ школы с использованием Custom Tool, аналогичный ResXFileCodeGenerator, или современный способ с использованием T4 Шаблон. Поэтому давайте посмотрим.

Соответствующая запись внутри файла 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 файле.

Как я могу получить тот же результат?

Думаю, вы просите свои проекты. Ну, вы можете сделать что-то подобное. Выберите файл resx, перейдите к Properties и снимите Custom Tool. Затем добавьте T4 template в свой проект и напишите генерацию кода (я не уверен, разрешает ли лицензия использовать их код, поэтому, если вы хотите сделать это, убедитесь, что вы сначала проверяете, разрешено ли это). Но принцип будет таким же.

Ответ 2

Я думаю, что для этой цели команда EF использует собственный Custom Tool. Но visual studio использует PublicResXFileCodeGenerator как настраиваемый инструмент по умолчанию для файлов .resx, и этот инструмент не имеет таких функций, как PublicResXFileCodeGenerator, а базовый класс ResXFileCodeGenerator (оба могут быть найдены в Microsoft.VisualStudio.Design assembly) - это всего лишь обертки для визуальной студии вокруг StronglyTypedResourceBuilder.

Они реализуют IVsSingleFileGenerator (находится в сборке Microsoft.VisualStudio.Shell.Interop). Таким образом, это место, где вы можете начать реализацию своего собственного Custom Tool. Начните новые Class Library, добавьте ссылки Microsoft.VisualStudio.Shell.14.0 и Microsoft.VisualStudio.Shell.Interop. Создайте новый класс и реализуйте этот интерфейс. Интерфейс IVsSingleFileGenerator довольно прост. Он содержит только два метода:

  • DefaultExtension, который возвращает расширение для сгенерированного файла (с ведущим периодом) как out string pbstrDefaultExtension паратемер и VSConstant.S_OK в качестве возвращаемого значения (конечно, если все в порядке).

  • Generate, который принимает:

    • wszInputFilePath - путь к входному файлу, может быть нулевым, не использовать его.
    • bstrInputFileContents - содержимое входного файла.
    • wszDefaultNamespace - пространство имен по умолчанию (теперь не могу сказать, почему ResXFileCodeGenerator взаимодействует с Visual Studio, чтобы получить пространство имен вместо этого параметра).
    • rgbOutputFileContents - массив байтов сгенерированного файла. Вы должны включать байты подписи UNICODE или UTF-8 в возвращаемом массиве байтов, так как это необработанный поток. Память для rgbOutputFileContents должна быть назначена с помощью вызова .NET Framework Marshal.AllocCoTaskMem или эквивалентного системного вызова Win32 CoTaskMemAlloc. Система проекта отвечает за освобождение этой памяти.
    • pcbOutput - количество байтов в массиве rgbOutputFileContent.
    • pGenerateProgress - Ссылка на IVsGeneratorProgress интерфейс, через который генератор может сообщить о своем прогрессе в проектную систему.

    И возвращает VSConstant.S_OK, если все в порядке, или соответствующий код ошибки.

Также существует небольшое руководство по реализации. Но это руководство не говорит слишком много. Самое полезное: как зарегистрировать собственный генератор.

Вам лучше погрузиться в ResXFileCodeGenerator code (или просто декомпилировать) для примера реализации или получить некоторые подсказки, например, как взаимодействовать с визуальной студией. Но я не вижу причин вмешиваться в VS, поскольку все, что вам нужно, вы уже предоставили. .resx содержимое файла может быть прочитано ResXResourceReader.FromFileContents.

Остальная часть будет простой, так как у вас есть имена и значения ресурсов и нужно только вернуть массив байтов сгенерированного файла. Я думаю, что разбор значений ресурсов для ошибки времени компиляции недопустимого формата (например: {{param}}}) был бы самой большой проблемой.

Когда значения проанализированы и параметры для вашего предстоящего метода найдены, вы можете сгенерировать код (опять же в качестве примера вы можете обратиться к ResXFileCodeGenerator и StronglyTypedResourceBuilder, или сделать это самим собой, как вы хотите, через CodeDom или создать источник текст кода вручную). Это также не должно быть сложно, поскольку у вас уже есть пример методов, которые необходимо создать в вопросе, который вы опубликовали.

Скомпилируйте свой собственный генератор, зарегистрируйте его, установите в свойстве Custom Tool ваши файлы .resx, и вы получите классы ресурсов со способами вместо свойств.

Также вы можете поделиться им на github с другими.:)


Вот инструкция из настройка пользовательского инструмента (поскольку ссылка msdn может скоро умереть):

Чтобы создать собственный инструмент, доступный в Visual Studio, вы должны зарегистрировать его, чтобы Visual Studio могла его создать и связать с определенным типом проекта.

  • Зарегистрируйте DLL настраиваемого инструмента либо в локальном реестре Visual Studio, либо в системном реестре под HKEY_CLASSES_ROOT.

    Например, здесь регистрационная информация для управляемого MSDataSetGenerator настраиваемого инструмента, который поставляется с Visual Studio:

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.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"
    
  • Создайте раздел реестра в нужном кусте Visual Studio под Generators\GUID, где GUID - это идентификатор GUID, определенный конкретной системой или службой языкового проекта. Имя ключа становится программным именем вашего настраиваемого инструмента. Пользовательский ключ инструмента имеет следующие значения:

    • (Default) - Необязательно. Обеспечивает удобное описание настраиваемого инструмента. Этот параметр является необязательным, но рекомендуется.

    • CLSID - обязательно. Задает идентификатор библиотеки классов COM-компонента, который реализует IVsSingleFileGenerator.

    • GeneratesDesignTimeSource - обязательно. Указывает, доступны ли типы из файлов, созданных этим настраиваемым инструментом, визуальным дизайнерам. Значение этого параметра должно быть (ноль) 0 для типов, недоступных визуальным дизайнерам или (1) для типов, доступных для визуальных дизайнеров.

    Примечание. Вы должны зарегистрировать настраиваемый инструмент отдельно для каждого языка, для которого вы хотите, чтобы пользовательский инструмент был доступен.

    Например, MSDataSetGenerator регистрируется один раз для каждого языка:

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.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\14.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\14.0\Generators\{e6fdf8b0-f3d1-11d4-8576-0002a516ece8}\MSDataSetGenerator]
    @="Microsoft J# Code Generator for XSD"
    "CLSID"="{E76D53CC-3D4F-40a2-BD4D-4F3419755476}"
    "GeneratesDesignTimeSource"=dword:00000001