Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have Visual Studio 2017 accept modifications to csproj file

I've developed a code generator for internal use where code assets (POCOs) are generated based off of C# interfaces. The code generation process programmatically adds/removes items to csproj file. The workflow is as follows: a developer adds a new C# interface, or removes an existing C# interface in Visual Studio 2017. If the developer saves the project file first then runs the code generator, then everything works as expected. Code-generated assets are either added to the project (or removed) and Visual Studio reflects those changes accordingly. However, if the developer fails to save the csproj file before running code generator, and has deleted a C# interface, then the code-generated assets are not being removed from the project because Visual Studio is not accepting the csproj file modifications.

Inside the code generator, I'm physically removing the references to the code-generated files that are deleted and am saving the csproj file. I verify that the referenced files are removed from the csproj file by opening the csproj up in notepad. However, as soon as I bring Visual Studio into focus, Visual Studio recognizes that the csproj file has changed and asks if I want to discard, overwrite, save as, etc and the changes made to csproj file from my code generation process are lost. Visual Studio adds the references to the deleted files back into the csproj file. I've tried discard, overwrite, save as, etc and I'm not getting Visual Studio to accept the newly modified csproj file (which has the references to deleted files removed).

Here's my code for removing code-generated assets:

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);
        }
    }

Is it possible to have Visual Studio just accept the new csproj file? Is there some setting I need to make to csproj file when removing assets? Having Visual Studio balk at the modified csproj file is hindering the usefulness of the code generator for removing code-generated assets no longer needed (stemming from physically deleting a C# interface file).

EDIT

Here's a video showing the process of running the T4 generator inside Visual Studio generating C# assets based on a C# interface. I delete the source C# interface, re-run code generator and the project file is updated accordingly causing the project to be reloaded.

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

The problem isn't that the project gets reloaded. The problem is the code generator updates and saves the csproj file outside of Visual Studio, which causes Visual Studio to be confused because the csproj file changed. How do you get Visual Studio to 'silently' accept the changes saved to csproj file?

Thanks for your help.

like image 840
Tom Schreck Avatar asked Sep 29 '17 05:09

Tom Schreck


People also ask

Can we edit Csproj file?

CSPROJ files are are meant to be opened and edited in Microsoft Visual Studio (Windows, Mac) as part of Visual Studio projects. However, because CSPROJ files are XML files, you can open and edit them in any text or source code editor.

How do I open a .csproj file in Visual Studio 2017?

For Visual Studio-version: 8.1. 5, Right click on the project folder. Click "Tools", then "Edit File".


1 Answers

Modifying the project file by yourself while it's loaded into Visual Studio isn't a great idea. Even if you find a way to force it to reload the project, it will still need to reload it, which is a major nuisance.

It's much better to access the EnvDTE from a T4 template, and modify the project file through that. This object gives you access to Visual Studio's project model.

Note that the user will still need to save the modified project file by default, as it will be seen as dirty by VS, but this behavior is consistent with every other project file modification you can do through VS. You can force VS to save the project if you really need it, though.

Here's what you need to do to access VS, as documented here:

Set the hostspecific attribute to true:

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

Import EnvDTE:

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

Get the dte object:

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

And now you have full access to VS's project APIs. Beware that with this approach you lose the ability to execute the template outside of Visual Studio.


Here's a full example of how to add a file beside your template:

<#@ 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();
    }
#>

Here's the result in the solution explorer:

result

like image 105
Lucas Trzesniewski Avatar answered Oct 14 '22 05:10

Lucas Trzesniewski