Including files created by Node.js into .Net project 🛠
Tags: node, dotnet, csharp, msbuild
Published: 2020-02-21 12:52:25

This is the continuation of a previous post of mine

{% link https://dev.to/kapantzak/automating-boilerplate-code-generation-with-node-js-and-handlebars-2c09 %}

In that post we had described the process of creating a set of new files, that contain boilerplate code, using the [Handlebars](https://handlebarsjs.com/) library. Those files are created within the folder structure of a .Net project and they, somehow, have to be included in the project.

We can find them one by one and include them manually, but it would be better if they could automatically get included into the project.

# The project file

Each .Net project is built by the Microsoft build engine (MSBuild). In order for the engine to know what files to include into the build process, there is a special XML file called the project file that has a language soecific extension. For C# is `.csproj`.

The project file is created automatically by Visual Studio, or it can be created manually if we want to build a project without using Visual Studio. Here is a sample project file for a .Net project written in C#.

{% gist https://gist.github.com/kapantzak/ef9230eda2d8958b9968b8920a515918 file=eStudio.csproj %}

Each time we create a new file, Visual Studio includes it to the project, by adding a new entry to the XML document like the following:

```xml
<Content Include="file1.ascx"/>
```

In our case, the new files are created by a Node.js app. They are at the right place but they are not yet part of the build process. We have to add a new entry to the project file for each one of them. We can do it manually, by right clicking on each file and selecting *Include In Project*, or we can somehow automate this process.

# Attempt #1 (xml-js)

As we said at the beginning, this post describes the process of including files generated from a Node.js cli to specific .Net project. We are now going to describe the first attempt of creating an automated process of including those files to the desired project file.

The first idea that came to my mind was to read the `.csproj` file in javascript, add the desired entries and recreate it. So, I found [xml-js](https://www.npmjs.com/package/xml-js), a powerfull tool that lets you convert xml to js/JSON and vice versa.

The flow was something like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1ixawogf4lqjh7r094tv.png)

After the new files are created, we read the `.csproj` file

```js
const convert = require("xml-js");

const xml = fs.readFileSync(path, "utf8");
const js = convert.xml2js(xml);
```

Then, we manipulate the `js` object accordingly and we recreate a new xml structure:

```js
const xml = convert.js2xml(js, {
    compact: true,
    ignoreComment: true,
    spaces: 2
});
```

Finally, we replace the contents of the `.csproj` file with the new xml structure:

```js
fs.writeFileSync(filePath, xml);
```

After the creation of the new `.cproj` file, I could not build the project at all (oops!)

![oops](https://media.giphy.com/media/Dx8lG9BlDnPTa/giphy.gif)

It looks that the project file is broken, and, eventually it seems logical since we are trying to manipulate a specific type of XML file in a way that is not recommended. I have never used xml-js before and I might could have achieved a better result if I tried different conifgurations.

# Attempt #2 (`Project class`)

After the previous failed attempt, I searched about how to programmatically include files in .net projects and found an answer to this question on stackoverflow:

{% stackoverflow 18544354 %}

People suggested to use the class `Microsoft.Build.Evaluation.Project` from the .Net framework and its `AddItem` method that does exactly what we are looking for.

The problem is that **we have to use C#** (or VB or whatever .Net compatible language you are working with) in order to use the `Project` class. So, we have to write a console application that uses the `Project` class and its methods in order to add new entries to the project file.

As soon as we write the console appication, we must find a way to **execute it from Node.js**, since our new files are created from a Node.js cli.

## Console application

Lets now build a simple (C#) console application that will load the project file, add the new items and save the changes. We are not going to cover the whole process in detail, instead we are going to highlight the major points.

If you want to view the whole code you can check the following repo:

{% github kapantzak/csproj-include %}

Inside our `Main` method, we check if the desired project is already loaded and if not, we load it.

```csharp
var p = ProjectCollection
          .GlobalProjectCollection
          .LoadedProjects
          .FirstOrDefault(
              x => x.FullPath == projectFullPath
          );
if (p == null)
    p = new Project(projectFullPath);
```

As soon as we have a loaded project file, we can iterate over a collection of items that represents the new entries and add them one by one to the loaded file:

```csharp
items.ForEach(x =>
{                                
    p.AddItemFast(x.itemType, x.unevaluatedInclude, x.metadata);    
});
p.Save();
```

We assume that `items` is a collection of objects of type `Item` which is defined bellow:

```csharp
class Item
{
    public string itemType { get; set; }
    public string unevaluatedInclude { get; set; }
    public Dictionary<string, string> metadata { get; set; }
}
```

### Inputs

The program needs some input in order to work properly, like:

- The project file to load
- The entries collection to be inserted

We expect the first argument to be the project file and the second argument to be a JSON string that is going to be deserialised into a list of `Item` objects.

### Execution

We can now call the executable like this:

```bash
> app.exe file.csproj [{"itemType": "Content", "unevaluatedInclude": "myFile.ts"}, {"metadata": null}]
```
But when I tested, I got the following error.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6xxo4cjo5ldlda1sweif.jpg)

It seems that there is a missing file or directory! After a lot of searching, I found another stackoverflow question:

{% stackoverflow 48771116 %}

I finally found out that `$(MSBuildExtensionsPath32)` and `$(VisualStudioVersion)` variables of the project file was not correctly set, and that I could apply the desired settings, using an overload of the `Project` class constructor that accepts this kind of settings.

So I added the following lines:

```csharp
var glob = new Dictionary<string, string>();
glob.Add("VisualStudioVersion", "16.0");
glob.Add("MSBuildExtensionsPath32", @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild");

p = new Project(projectFullPath, glob, null);
```
And it worked just fine!

![hooray](https://media.giphy.com/media/D6WuLOKOpR2fK/giphy.gif)

## Execute from Node.js

Our final step is to call the console application's executable from inside our Node.js cli. We can achieve that using the `execFile()` method of the `child_process` module like that:

```js
const exec = require("child_process").execFile;
const child = exec(projectFile, args, (err, data) => {
    // ...
});
```

## Workflow

Now lets overview the workflow after our second attempt

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zj99vv31hdtpcj23esrq.png)

As we described earlier, the new files are created by a Node.js cli. As soon as the files have been created, we use `execFile` to call the console app we have created in order to add the new items to the desired project file.

# Conclusion

During the development process of this Node.js app and the console application, I came accross various problems that I had never faced before. I had to search a lot for the details so, I thought I could write a post about my experience in order to help other people who may face the same or similar problems. I hope you enjoyed reading! 😄

# Resources

- [Understanding the project file](https://docs.microsoft.com/en-us/aspnet/web-forms/overview/deployment/web-deployment-in-the-enterprise/understanding-the-project-file)
- [https://nodejs.org/api/child_process.html#child_process_child_process_execfile_file_args_options_callback](https://nodejs.org/api/child_process.html#child_process_child_process_execfile_file_args_options_callback)
- [https://stackoverflow.com/questions/18544354/how-to-programmatically-include-a-file-in-my-project](https://stackoverflow.com/questions/18544354/how-to-programmatically-include-a-file-in-my-project)
- [https://stackoverflow.com/questions/47077150/msbuild-15-webapplication-targets-is-missing](https://stackoverflow.com/questions/47077150/msbuild-15-webapplication-targets-is-missing)
- [https://stackoverflow.com/questions/48771116/cant-use-microsoft-build-evaluation-in-vs2017](https://stackoverflow.com/questions/48771116/cant-use-microsoft-build-evaluation-in-vs2017)
- [https://xamarin.github.io/bugzilla-archives/18/18892/bug.html](https://xamarin.github.io/bugzilla-archives/18/18892/bug.html)