47

Background

I'm making a helper application that reformats some code files and creates new code files, which are to be added to my other project, so I could use the new code right away, but I'm having serious trouble adding that new code file into my project automatically. By the way it's in c# and the helper app is WinForms.

Failed attempts

This question's only answer has two ways of doing that, but I couldn't make any of them work. With the first I can't find a Microsoft.Build assembly to reference, and in the other there are clearly not enough arguments for a command line.

Question

How do I programmatically include a file into a project without the use of third-party applications?

Basically, I'm looking for the equivalent of this:

...But done using code.

Requirements

These are the features I suppose the solution should offer:

  • Select the solution which has the project we're adding the file to
  • Select project into which the file is to be added
  • Select directory within the project
  • And, of course, the file which we're adding

Progress

With user @psubsee2003's help I was able to find the Microsoft.Build.dll file in C:\Windows\Microsoft.NET\Framework\v4.0.30319 folder on my computer and successfully import it by changing my project's target framework to version 4 Full profile, not the default Client profile.

And I found how to use the AddItem method:

var p = new Microsoft.Build.Evaluation.Project(@"C:\projects\MyProject.csproj");
p.AddItem("Compile", @"C:\folder\file.cs");
p.Save();

The file will appear in project's root folder unless the project already had a folder called folder, in which case the file will be placed there. So basically the file will be placed in the deepest folder chain found in the original file's path going towards the root folder.

17
  • 1
    Microsoft Build.Evaluation namespace is available in .NET 4.0 and up. What are you building to?
    – crthompson
    Commented Aug 31, 2013 at 4:08
  • 1
    @DourHighArch When I type Microsoft.Build my Visual Studio 2010 Ultimate says The type or namespace name 'Build' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?). When I look in the "add reference" list, there is no such entry as Microsoft.Build anywhere to be found. Commented Sep 1, 2013 at 19:09
  • 1
    @psubsee2003 when I added all of those Microsoft.Build dlls, all of them had a yellow warning sign icon and the error list contained Could not resolve assembly "Microsoft.Build". The assembly is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client" for all of them. Commented Sep 1, 2013 at 20:10
  • 1
    @user1306322 MS.Build is not part of the client profile. Do you need to use the client profile? If not, then just change the target framework to the full framework? Commented Sep 1, 2013 at 20:15
  • 1
    @psubsee2003 of course! No error messages. Now I have to figure out how to use that AddItem method. Commented Sep 1, 2013 at 20:20

6 Answers 6

33

It worked for my just adding the it to the ProjectFolder, and also add the folder programmatically like this.

var p = new Microsoft.Build.Evaluation.Project(@"C:\projects\BabDb\test\test.csproj");
        p.AddItem("Folder", @"C:\projects\BabDb\test\test2");
        p.AddItem("Compile", @"C:\projects\BabDb\test\test2\Class1.cs");
        p.Save();
4
  • tnx for code but how can i check if file is exist dont add it??
    – AminM
    Commented Jun 11, 2015 at 4:11
  • 8
    @AminM if (p.Items.FirstOrDefault(i => i.EvaluatedInclude == newItemPath) == null) { p.AddItem("Build", newItemPath); p.Save(); }
    – benkevich
    Commented Aug 28, 2015 at 7:12
  • See my answer below for an addendum consideration to this approach
    – Caius Jard
    Commented Jun 29, 2017 at 17:07
  • This method will explicitly update csproj file, and one of its cons is showing a notification that the project has been edited and request project's reload. check below my answer where the include operation will be performed implicitly and developed an VSIX for that purpose. Commented Nov 30, 2019 at 21:18
16

As a supplement to Boot750's answer (first suggested as an edit to his answer but re-posted as a standalone)

You should note that the call to new Microsoft.Build.Evaluation.Project(path) actually causes the project to be loaded into a global cache maintained by the Microsoft.Build.Evaluation assembly. Calling new Project(path) again with the same path, without unloading it from the global collection first/restarting your app will cause an exception like:

An equivalent project (a project with the same global properties and tools version) is already present in the project collection, with the path "YOUR_PATH". To load an equivalent into this project collection, unload this project first.

even if the variable p has gone out of scope.

You might hence need to adopt a pattern more like this, if you plan to use the Load/AddItem/Save pattern repeatedly:

 var p =
Microsoft.Build.Evaluation
  .ProjectCollection.GlobalProjectCollection
    .LoadedProjects.FirstOrDefault(pr => pr.FullPath == projFilePath);

if (p == null)
   p = new Microsoft.Build.Evaluation.Project(projFilePath);
6
  • Thank you for your contribution. Next time don't hesitate to post your own answer instead of editing someone else's. You could comment under an answer with an important oversight to see your answer for a fix. Commented Jun 29, 2017 at 18:33
  • There's a bit too much info in my post for a comment, hence why it originally proposed it as an edit because it seemed to be legitimate and helpful extra info - stuff i had to research and find out myself while trying to implement boot750's answer (and failing)
    – Caius Jard
    Commented Jun 30, 2017 at 8:47
  • Generally, if you can't fit your additional useful info into a comment, that means it's time to post an answer, so you did the correct thing in the end. Commented Jun 30, 2017 at 8:49
  • Is that true even if it merely builds on someone else's answer? How does that fit with the no-plagiarism rule? Should the other person's answer be included in mine with credit to them?
    – Caius Jard
    Commented Jun 30, 2017 at 16:00
  • Absolutely. You should always post an answer when you have useful information that another answer does not contain. You can preface your answer that it's a pretty important addition to one of the existing answers (as you already did), but don't be shy to take credit for your own contribution by posting your own answer. Commented Jun 30, 2017 at 19:41
9

Just to add to the response from @caius-jard

Once the project is in the GlobalProjectCollection it's held in memory and does not reflect any manual changes made to the project. This includes updating the .csproj file and using VS to remove the generated files.

If you remove the generated file and run the code again the project will update to contain 2 of the generated file. Use the ReevaluateIfNecessary() function to update the instance of the project before use like this:

var p = Microsoft.Build.Evaluation
        .ProjectCollection.GlobalProjectCollection
        .LoadedProjects.FirstOrDefault(pr => pr.FullPath == projFilePath);

if (p == null)
    p = new Microsoft.Build.Evaluation.Project(projFilePath);

// Update instance of project
p.ReevaluateIfNecessary();

// Check folder is not already in the project
var folderLoc = @"C:\projects\BabDb\test\test2";
if(p.Items.FirstOrDefault(i => i.EvaluatedInclude == folderLoc) == null)
    p.AddItem("Folder", folderLoc);

// Check file is not already in the project
var fileLoc = @"C:\projects\BabDb\test\test2\Class1.cs";
if(p.Items.FirstOrDefault(i => i.EvaluatedInclude == fileLoc) == null)
    p.AddItem("Compile", fileLoc);

p.Save();
1
  • Aren't you supposed to have relative paths for folderLoc and fileLoc?
    – drizin
    Commented Sep 15, 2019 at 23:08
8

The simplest way to do this is to modify the Project file. As it is just an MSBUILD file, VS will pick up the change and prompt up to reload the project. Load the project file as an XML and find the first <Compile Include="Name of File.cs" />. Insert a new <Compile Include="NewFile.CS" /> and your done.

As another option you can remove all the <Compile> tags and replace them with <Compile Include="*.cs" />

1
  • This works sort off but it doesn't get added as a new file in source control. Commented Jan 3, 2021 at 17:37
1

I know that this is an old post. However, I was in need of this option and didn't find the suitable one as I wished. So, I developed a VS extension with extended features. You can set the file types, and then, select the list of projects' directory that you want their files to be loaded into the project once they get generated.

Market (free) | Source-Code

-1

You could use T4 Text Templates. See http://msdn.microsoft.com/en-us/library/vstudio/bb126445(v=vs.100).aspx for more information.

2
  • 3
    Even if the link is complete answer your answer should stay for itself. Please add at least a short explanation.
    – IvanH
    Commented Sep 10, 2013 at 18:30
  • @tlango This is a very vague answer, T4 (Text Translate Transformation Toolkit) is a generic toolkit which can be used for a wide range of purposes. The link you pasted did not even answer the question directly.
    – Dio Phung
    Commented May 22, 2016 at 20:49

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.