48

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.

Community
  • 1
  • 1
user1306322
  • 8,561
  • 18
  • 61
  • 122
  • 1
    `Microsoft Build.Evaluation` namespace is available in .NET 4.0 and up. What are you building to? – crthompson Aug 31 '13 at 04:08
  • Do you mean like NuGet does? – Steve Wellens Aug 31 '13 at 04:10
  • @paqogomez I know, right? Try using that in a new project, you'll have the "Build namespace not found" error. That's what I get. – user1306322 Aug 31 '13 at 04:10
  • @user1306322 - Try Goggling NuGet. – Steve Wellens Aug 31 '13 at 04:11
  • @SteveWellens Do you happen to know a non-third-party dependent solution? – user1306322 Aug 31 '13 at 04:12
  • I just created a project building to 4.0 and added `Microsoft.Build`. Worked just fine. – crthompson Aug 31 '13 at 04:13
  • @paqogomez what kind of project? I'm trying to make this work with WinForms and it fails. – user1306322 Aug 31 '13 at 04:15
  • I just did a console app. But if you can use NuGet, do it. Its the way to add great stuff to your projects. – crthompson Aug 31 '13 at 04:16
  • @paqogomez no luck either. And there is no such thing as `Microsoft.Build` in the list of references I could add. – user1306322 Aug 31 '13 at 04:17
  • Please explain what “it fails” and “no luck” mean. Are you using VS 2010 Express? – Dour High Arch Sep 01 '13 at 17:32
  • 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. – user1306322 Sep 01 '13 at 19:09
  • @user1306322 did you try to search your hard drive for `Microsoft.Build.dll`? I found it in `C:\Windows\Microsoft.NET\Framework\v4.0.30319` – psubsee2003 Sep 01 '13 at 20:03
  • 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. – user1306322 Sep 01 '13 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? – psubsee2003 Sep 01 '13 at 20:15
  • 1
    @psubsee2003 of course! No error messages. Now I have to figure out how to use that `AddItem` method. – user1306322 Sep 01 '13 at 20:20
  • possible duplicate of [Include in project programmatically](http://stackoverflow.com/questions/15180527/include-in-project-programmatically) – psubsee2003 Sep 02 '13 at 00:00
  • None of this works. When I try to load a project, it just throws an error saying "the imported project "Microsoft.CSharp.targets" was not found". But the path is relative to my currently running app, which makes no sense. Obviously Microsoft.CSharp.targets is not something my app defines. Why is this class incapable of loading a project by default with basic constructors like "new Project(projectFileName)"? – Triynko Mar 17 '20 at 05:58

6 Answers6

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();
Boot750
  • 891
  • 1
  • 7
  • 17
  • tnx for code but how can i check if file is exist dont add it?? – AminM Jun 11 '15 at 04:11
  • 8
    @AminM `if (p.Items.FirstOrDefault(i => i.EvaluatedInclude == newItemPath) == null) { p.AddItem("Build", newItemPath); p.Save(); }` – benkevich Aug 28 '15 at 07:12
  • See my answer below for an addendum consideration to this approach – Caius Jard Jun 29 '17 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. – AbuDawood Nov 30 '19 at 21:18
15

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);
Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • 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. – user1306322 Jun 29 '17 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 Jun 30 '17 at 08: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. – user1306322 Jun 30 '17 at 08: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 Jun 30 '17 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. – user1306322 Jun 30 '17 at 19:41
  • I was looking for this. This is perfect! – Sachin Pakale Sep 06 '18 at 08:51
8

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();
Aetbmo
  • 123
  • 1
  • 8
7

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" />

Dave Yarwood
  • 2,866
  • 1
  • 17
  • 29
rerun
  • 25,014
  • 6
  • 48
  • 78
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

AbuDawood
  • 745
  • 7
  • 22
-1

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

tlango
  • 33
  • 3
    Even if the link is complete answer your answer should stay for itself. Please add at least a short explanation. – IvanH Sep 10 '13 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 May 22 '16 at 20:49