【问题标题】:How to have Visual Studio 2017 accept modifications to csproj file如何让 Visual Studio 2017 接受对 csproj 文件的修改
【发布时间】:2018-03-10 23:16:41
【问题描述】:

我开发了一个供内部使用的代码生成器,其中代码资产 (POCO) 是基于 C# 接口生成的。代码生成过程以编程方式将项目添加/删除到 csproj 文件。工作流程如下:开发人员在 Visual Studio 2017 中添加新的 C# 接口,或删除现有的 C# 接口。如果开发人员先保存项目文件,然后运行代码生成器,则一切正常。将代码生成的资产添加到项目中(或删除),Visual Studio 会相应地反映这些更改。但是,如果开发人员在运行代码生成器之前未能保存 csproj 文件,并且删除了 C# 接口,则代码生成的资产不会从项目中删除,因为 Visual Studio 不接受 csproj 文件修改。

在代码生成器中,我物理上删除了对已删除的代码生成文件的引用,并保存了 csproj 文件。我通过在记事本中打开 csproj 来验证引用的文件是否已从 csproj 文件中删除。但是,只要我将 Visual Studio 放在焦点上,Visual Studio 就会识别出 csproj 文件已更改,并询问我是否要丢弃、覆盖、另存为等,并且在我的代码生成过程中对 csproj 文件所做的更改会丢失。 Visual Studio 将对已删除文件的引用添加回 csproj 文件。我尝试过丢弃、覆盖、另存为等,但我没有让 Visual Studio 接受新修改的 csproj 文件(其中删除了对已删除文件的引用)。

这是我删除代码生成资产的代码:

using Microsoft.Build.Evaluation;
using Microsoft.Build.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

    public static void RemoveGeneratedFilesFromProject(String projectPath)
    {
        UnloadAnyProject();

        var project = ProjectCollection.GlobalProjectCollection.LoadedProjects.FirstOrDefault(pr => pr.FullPath == projectPath);

        //ATTEMPT TO SAVE PROJECT IN CASE DEVELOPER DID NOT...
        project.Save();


        //GET A LIST OF ITEMS CONTAINING PATH TO CODE-GENERATED ASSETS ("Generated\API")
        IList<ProjectItem> generatedItemsList = project.GetItems("Compile").Where(item => item.EvaluatedInclude.Contains(@"Generated\Api")).ToList();

        foreach (var item in generatedItemsList)
        {
            project.RemoveItem(item);
        }

        //SAVE PROJECT TO REFLECT ALL OF THE CODE GENERATED ITEMS REMOVED FROM PROJECT FILE
        project.Save();

        UnloadAnyProject();
    }

    private static void UnloadAnyProject()
    {
        ProjectCollection projcoll = ProjectCollection.GlobalProjectCollection;

        foreach (Project project in projcoll.LoadedProjects)
        {
            ProjectCollection mypcollection = project.ProjectCollection;
            mypcollection.UnloadProject(project);
        }
    }

是否可以让 Visual Studio 只接受新的 csproj 文件?删除资产时,我需要对 csproj 文件进行一些设置吗?让 Visual Studio 对修改后的 csproj 文件犹豫不决,这会妨碍代码生成器在删除不再需要的代码生成资产(源于物理删除 C# 接口文件)方面的有用性。

编辑

这是一个视频,展示了在 Visual Studio 中运行 T4 生成器的过程,该生成器基于 C# 接口生成 C# 资产。我删除了源 C# 接口,重新运行代码生成器,项目文件相应更新,导致项目重新加载。

https://www.screencast.com/t/JWTE0LpkXZGX

问题不在于项目被重新加载。问题是代码生成器更新并将 csproj 文件保存在 Visual Studio 之外,这会导致 Visual Studio 由于 csproj 文件更改而感到困惑。如何让 Visual Studio “静默”地接受保存到 csproj 文件的更改?

感谢您的帮助。

【问题讨论】:

  • 您是在构建期间还是在构建过程之外从 msbuild 目标运行代码生成器作为独立的 exe / 工具?大多数生成方法都集成到 msbuild 中,因此即使在 VS 中的设计时构建期间,它们也可以动态添加代码,而无需编辑项目文件
  • 所以基本上第一次调用 project.Save() 在 VS 中没有效果?
  • 这是一个用 T4 文件编写的独立代码生成器。我不确定如何将 T4 与 MS Build 集成。第一个 project.save() 似乎不影响视觉工作室。第二次保存是在删除过时的代码资产后发生的,并且 VS 会被提示重新加载。但似乎 VS 在内存中有自己的项目版本,这与在代码生成器范围内加载/修改/保存的 csproj 文件不同。
  • 这是一段代码生成器在 VS 中运行的视频。你会看到,当我删除一个 C# 接口而不保存项目时,VS 意识到 DataContracts 项目的 csproj 文件已更改。系统会提示用户丢弃、另存为、覆盖等。选择“另存为”不会从 VS 中删除已删除的资产。引用仍然存在于 csproj 文件中。 screencast.com/t/JWTE0LpkXZGX
  • @MartinUllrich - 我正在通过 TextTransform 生成文件的内容,并使用 System.IO.File 将文件物理写入路径。这就是为什么我必须手动修改 csproj 文件以便它知道新文件的原因。你是说我可以在我的 T4 中引用 Microsoft.Build 程序集并使用 Microsoft.Build 添加新生成的文件?

标签: c# msbuild visual-studio-2017 microsoft.build


【解决方案1】:

在项目文件加载到 Visual Studio 时自行修改项目文件并不是一个好主意。即使你找到了强制它重新加载项目的方法,它仍然需要重新加载它,这是一个很大的麻烦。

最好从 T4 模板访问EnvDTE,并通过它修改项目文件。此对象使您可以访问 Visual Studio 的项目模型。

请注意,默认情况下,用户仍需要保存修改后的项目文件,因为 VS 会认为它是脏的,但这种行为与您可以通过 VS 进行的所有其他项目文件修改是一致的。不过,如果你真的需要,你可以强制 VS 保存项目。

这是访问 VS 所需执行的操作,如 documented here

hostspecific属性设置为true

<#@ template debug="false" hostspecific="true" language="C#" #>

导入EnvDTE:

<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>

获取dte 对象:

<#
    var dte = (DTE)((IServiceProvider)Host).GetService(typeof(DTE));
#>

现在您可以完全访问 VS 的项目 API。请注意,使用这种方法,您将无法在 Visual Studio 之外执行模板。


以下是如何在模板旁边添加文件的完整示例:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ output extension=".txt" #>
<#
    // Get the DTE
    var dte = (DTE)((IServiceProvider)Host).GetService(typeof(DTE));

    // Find the currently running template file in the project structure
    var template = dte.Solution.FindProjectItem(Host.TemplateFile);

    // Write something to a dummy file next to the template
    var filePath = System.IO.Path.ChangeExtension(Host.TemplateFile, "foo");
    System.IO.File.WriteAllText(filePath, "Hello, world!");

    // Add the file as a subitem of the template
    var fileItem = dte.Solution.FindProjectItem(filePath);
    if (fileItem == null)
    {
        template.ProjectItems.AddFromFile(filePath);

        // If you really want to, you can force VS to save the project,
        // though I wouldn't recommend this
        template.ContainingProject.Save();
    }
#>

这是解决方案资源管理器中的结果:

【讨论】:

    猜你喜欢
    • 2017-08-05
    • 2017-08-06
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-08
    • 1970-01-01
    相关资源
    最近更新 更多