Search code examples
c#visual-studiocode-generationsolution

API to manipulate VS solution and project files (DOM)


I am working in a code generation scenario where an entire solution is generated including projects, folders, *.cs files, the works.

Currently, I generate all the files programmatically as text files without having a structure (DOM). I was wondering if there is such an API within the .NET / VS eco-system that can help gain more control over this.

Essentially be able to programmatically (strongly-typed):

  • Create a solution
  • Create multiple projects within
  • Specify project properties (such as target .NET 8)
  • Create project code files
  • Add project references
  • Manipulate properties for different items such as specifying the Build Action for a file, etc.

Please note that I am not referring to CSCodeProvider, Roslyn, or VS Extensions. However, VS extensions are a good example of how an extension has access to the currently-opened solution. It uses the MS VS SDK but I do not know if this can be used to manipulate solutions as an API instead of within an extension.

Any ideas would be appreciated.


Solution

  • For the projects, I'm using the Microsoft.Build library, especially the Microsoft.Build.Construction namespace.

    A project is represented by the ProjectRootElement class. You create a new one with the Create method, save it with Save. Inspect the other methods to create items, change properties, etc. For example, to create a new item group for e.g., references:

    /// <summary>
    /// Gets the first 'ItemGroup' element in msbuild file that contains an item of specified type.
    /// </summary>
    /// <param name="projRoot"></param>
    /// <param name="itemType">The item type to be searched, for example 'Compile', 'Reference', etc. </param>
    /// <returns></returns>
    /// <remarks>If none such group was found, a new one is created.</remarks>
    private static ProjectItemGroupElement GetOrCreateItemGroupWithItemType(ProjectRootElement projRoot, string itemType)
    {
        foreach (var item in projRoot.Items)
        {
            if (item.ItemType == itemType)
            {
                return item.Parent as ProjectItemGroupElement;
            }
        }
    
        return projRoot.AddItemGroup();
    }
    

    The to add a reference, use a method like this one:

    /// <summary>
    /// Adds the 'Reference' item into msbuild file.
    /// </summary>
    /// <param name="itemGroup"></param>
    /// <param name="referencePath">Reference full path or a file name.</param>
    /// <returns>Created 'Reference' item.</returns>
    /// <remarks></remarks>
    private static ProjectItemElement AddReferenceItem(ProjectItemGroupElement itemGroup, string referencePath)
    {
        string refFileName = Path.GetFileName(referencePath);
        string refName = Path.GetFileNameWithoutExtension(referencePath);
        string refFolder = "";
        if (referencePath.Length == refFileName.Length)
        {
            // referencePath is just a file name without path
            refFolder = QuickDevis.Common.CommonUtility.ApplicationStartupPath();
        }
        else
        {
            refFolder = Path.GetDirectoryName(referencePath);
        }
    
        ProjectItemElement res;
        res = itemGroup.AddItem("Reference", refName);
        res.AddMetadata("HintPath", Path.Combine(refFolder, refFileName));
    
        return res;
    }
    

    One problem is, that the Microsoft.Build cannot create and edit SLN files. It can only parse them. You can use the MvsSln library (I didn't test it). This was discussed at Create solution programmatically.