How to save Visual Studio project extension properties in a tiered way

Viewed 459

How would I dynamically create/modify/remove extension properties in a Visual Studio 2015 project file, such that the XML maintains the following construct:

<ProjectExtensions>
    <VisualStudio>
        <MyProject>
            <MyProjectProperty1>3</MyProjectProperty>
            <MyProjectProperty2>8</MyProjectProperty>
            <MyProjectProperty3>2016</MyProjectProperty>
        </MyProject>
    </VisualStudio>
</ProjectExtensions>

I already know how to create a variant of that using the EnvDTE.Globals and then persisting the value and optionally saving the project using code similar to:

EnvDTE.Project oProject = dte.Solution.Projects.Item(1);
Globals oGlobals = oProject.Globals;
oGlobals["MyProjectProperty1"] = "3";

The problem here is that this code yields:

<ProjectExtensions>
  <VisualStudio>
    <UserProperties MyProjectProperties1="3" MyProjectProperties2="8" MyProjectProperties3="2016" />
  </VisualStudio>
</ProjectExtensions>

I thought of using IPersistXMLFragment and IVsProjectFlavorCfg combinations, but this approach lacks the key concept of reading, writing, and removing the XML code from the project file. The IPersistXMLFragment load method takes XML code as a string, loads the values into a dictionary, and then manages the values. The save method does the reverse going from a management construct back to a string based version of XML. Missing, as I just said, is the read, write, and remove from the actual project file without manually editing the file and causing a project reload.

I was also playing with the IVsHierarchy and MSBuild VSIX interface/object, but did not get very far.

Does anyone have any ideas?

1 Answers

You may use IVsBuildPropertyStorage. As input I use a EnvDTE.Project object.
SetCsprojProperty will set the property in the first <PropertyGroup> of the csproj file if it does not exist:

private IVsHierarchy GetIVsHierarchyObject(Project project)
{
  string uniqueName = project?.UniqueName;
  int ret = Solution.GetProjectOfUniqueName(uniqueName, out IVsHierarchy hierarchyProject);
  if (ret != VSConstants.S_OK) return null;

  return hierarchyProject;
}

public string GetCsprojProperty(Project project, string name)
{
  IVsHierarchy hierarchyProject = GetIVsHierarchyObject(project);
  if (hierarchyProject is IVsBuildPropertyStorage buildPropertyStorage)
  {
    var result = buildPropertyStorage.GetPropertyValue(name, "", (uint)_PersistStorageType.PST_PROJECT_FILE, out string value);
    if (result == VSConstants.S_OK)
    {
      return value;
    }
  }

  return null;
}

public bool SetCsprojProperty(Project project, string name, string value)
{
  IVsHierarchy hierarchyProject = GetIVsHierarchyObject(project);
  if (hierarchyProject is IVsBuildPropertyStorage buildPropertyStorage)
  {
    var result = buildPropertyStorage.SetPropertyValue(name, "", (uint)_PersistStorageType.PST_PROJECT_FILE, value);
    if (result == VSConstants.S_OK)
    {
      return true;
    }
  }

  return false;
}
Related