gpt4 book ai didi

c# - 使用 Roslyn API 读取 *.csproj 属性值?

转载 作者:行者123 更新时间:2023-12-03 20:26:53 37 4
gpt4 key购买 nike

我目前正在构建一个支持 ASP.NET Core 项目开发的工具。此工具使用 Roslyn API 和其他方法来验证某些开发要求(例如应用到 API Controller 上的项目特定属性、强制执行命名约定以及为访问使用 ASP.NET 编写的 API 的 JavaScript SPA 生成一些源代码核心 Web API 模板)。

为此,我目前正在使用硬编码路径为 SPA 应用程序生成代码。但是在应用程序的 *.csproj 在文件中,实际上有一个“SpaRoot”属性指定了 SPA 应用程序在项目中的位置:

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
...
</PropertyGroup>
...
</Project>

我的问题是:如何使用 Roslyn API 读取“SpaRoot”属性的值?

我写了一个最小的代码示例来创建一个 Workspace ,打开 Solution ,并检索 Project的引用,类似于以下内容:
static async Task Main(string[] args)
{
string solutionFile = @"C:\Test\my-solution.sln";
using (var workspace = MSBuildWorkspace.Create())
{
var solution = await workspace.OpenSolutionAsync(solutionFile);

string projectName = "some-project";
var project = solution.Projects.Single(p => p.Name == projectName);

// How to extract the value of "SpaRoot" from the Project here?
}

我尝试搜索如何从 Project 中提取“SpaRoot”属性。引用,甚至进行调试,看看我是否可以自己找到方法。不幸的是,我没有想出答案,而且我仍然在原始代码中使用硬编码路径。

甚至可以检索 的值吗? .csproj Project 的属性使用当前的 Roslyn API?

最佳答案

这比你想象的要困难 :) Roslyn apis 只知道编译器知道什么,编译器不会得到关于 SpaRoot 的任何信息。属性(property)。不过,我们可以使用 MSBuild api 来解决这个问题。特别是 Microsoft.Build.Evaluation.Project类(class)。

我所做的一些假设

  • 您只想检查 .NET Core 项目
  • 您将在运行此工具的系统上安装 .NET Core SDK

  • 所以首先我们需要一个看起来像这样的项目文件:

    <Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
    <OutputType>Exe</OutputType>
    <!--NOTE: If the project you are analyzing is .NET Core then the commandline tool must be as well.
    .NET Framework console apps cannot load .NET Core MSBuild assemblies which is required
    for what we want to do.-->
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <LangVersion>Latest</LangVersion>
    </PropertyGroup>

    <ItemGroup>
    <!-- NOTE: We put ExcludeAssets="runtime" on all direct MSBuild references so that we pick up whatever
    version is being used by the .NET SDK instead. This is accomplished with the Microsoft.Build.Locator
    referenced further below. -->
    <PackageReference Include="Microsoft.Build" Version="16.4.0" ExcludeAssets="runtime" />
    <PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.8" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.4.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="3.4.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.4.0" />
    <!-- NOTE: A lot of MSBuild tasks that we are going to load in order to analyze a project file will implicitly
    load build tasks that will require Newtonsoft.Json version 9. Since there is no way for us to ambiently
    pick these dependencies up like with MSBuild assemblies we explicitly reference it here. -->
    <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
    </ItemGroup>

    </Project>


    和一个如下所示的 Program.cs 文件:

    using System;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Xml;
    using Microsoft.Build.Construction;
    using Microsoft.Build.Evaluation;
    using Microsoft.Build.Locator;
    using Microsoft.CodeAnalysis.MSBuild;

    // I use this so I don't get confused with the Roslyn Project type
    using MSBuildProject = Microsoft.Build.Evaluation.Project;

    namespace loadProject {
    class Program {
    static async Task Main(string[] args) {
    MSBuildWorkspaceSetup();
    // NOTE: we need to make sure we call MSBuildLocator.RegisterInstance
    // before we ask the CLR to load any MSBuild types. Therefore we moved
    // the code that uses MSBuild types to its own method (instead of being in
    // Main) so the CLR is not forced to load them on startup.
    await DoAnalysisAsync(args[0]);
    }

    private static async Task DoAnalysisAsync(string solutionPath) {
    using var workspace = MSBuildWorkspace.Create();

    // Print message for WorkspaceFailed event to help diagnosing project load failures.
    workspace.WorkspaceFailed += (o, e) => Console.WriteLine(e.Diagnostic.Message);

    Console.WriteLine($"Loading solution '{solutionPath}'");

    // Attach progress reporter so we print projects as they are loaded.
    var solution = await workspace.OpenSolutionAsync(solutionPath, new ConsoleProgressReporter());
    Console.WriteLine($"Finished loading solution '{solutionPath}'");

    // We just select the first project as a demo
    // you will want to use your own logic here
    var project = solution.Projects.First();

    // Now we use the MSBuild apis to load and evaluate our project file
    using var xmlReader = XmlReader.Create(File.OpenRead(project.FilePath));
    ProjectRootElement root = ProjectRootElement.Create(xmlReader, new ProjectCollection(), preserveFormatting: true);
    MSBuildProject msbuildProject = new MSBuildProject(root);

    // We can now ask any question about the properties or items in our project file
    // and get the correct answer
    string spaRootValue = msbuildProject.GetPropertyValue("SpaRoot");
    }

    private static void MSBuildWorkspaceSetup() {
    // Attempt to set the version of MSBuild.
    var visualStudioInstances = MSBuildLocator.QueryVisualStudioInstances().ToArray();
    var instance = visualStudioInstances.Length == 1
    // If there is only one instance of MSBuild on this machine, set that as the one to use.
    ? visualStudioInstances[0]
    // Handle selecting the version of MSBuild you want to use.
    : SelectVisualStudioInstance(visualStudioInstances);

    Console.WriteLine($"Using MSBuild at '{instance.MSBuildPath}' to load projects.");

    // NOTE: Be sure to register an instance with the MSBuildLocator
    // before calling MSBuildWorkspace.Create()
    // otherwise, MSBuildWorkspace won't MEF compose.
    MSBuildLocator.RegisterInstance(instance);
    }

    private static VisualStudioInstance SelectVisualStudioInstance(VisualStudioInstance[] visualStudioInstances) {
    Console.WriteLine("Multiple installs of MSBuild detected please select one:");
    for (int i = 0; i < visualStudioInstances.Length; i++) {
    Console.WriteLine($"Instance {i + 1}");
    Console.WriteLine($" Name: {visualStudioInstances[i].Name}");
    Console.WriteLine($" Version: {visualStudioInstances[i].Version}");
    Console.WriteLine($" MSBuild Path: {visualStudioInstances[i].MSBuildPath}");
    }

    while (true) {
    var userResponse = Console.ReadLine();
    if (int.TryParse(userResponse, out int instanceNumber) &&
    instanceNumber > 0 &&
    instanceNumber <= visualStudioInstances.Length) {
    return visualStudioInstances[instanceNumber - 1];
    }
    Console.WriteLine("Input not accepted, try again.");
    }
    }

    private class ConsoleProgressReporter : IProgress<ProjectLoadProgress> {
    public void Report(ProjectLoadProgress loadProgress) {
    var projectDisplay = Path.GetFileName(loadProgress.FilePath);
    if (loadProgress.TargetFramework != null) {
    projectDisplay += $" ({loadProgress.TargetFramework})";
    }

    Console.WriteLine($"{loadProgress.Operation,-15} {loadProgress.ElapsedTime,-15:m\\:ss\\.fffffff} {projectDisplay}");
    }
    }
    }
    }

    关于c# - 使用 Roslyn API 读取 *.csproj 属性值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59666864/

    37 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com