VsixCommunity / Community.VisualStudio.Toolkit

Making it easier to write Visual Studio extensions
Other
261 stars 44 forks source link

Reading properties from project-user files not reliable and it is impossible to remove properties stored in user files #519

Open rezanid opened 2 months ago

rezanid commented 2 months ago

I discovered a weird behavior in project storage that looks like a bug and perhaps a bug in Visual Studio's SDK too.

If you use the following lines to store a setting in project file and a project user file:

var proj = await VS.Solutions.GetActiveProjectAsync();
var result1 = await proj.TrySetAttributeAsync("MySetting", "A", ProjectStorageType.UserFile);
var result2 = await proj.TrySetAttributeAsync("MySetting", "B", ProjectStorageType.ProjectFile);

the both work fine and you will have something like the following in the respective files.

Project File:

<?xml version="1.0" encoding="utf-16"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- removed for brevity -->
  <PropertyGroup>
    <MySetting>A</MySetting>
  </PropertyGroup>
  <!-- removed for brevity -->
</Project>

Project User File:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <!-- Removed for brevity -->
    <MySetting>B</MySetting>
  </PropertyGroup>
</Project>

Now, if you try to read the value with any of the following two lines, they will return the value only from the project user file!

var projValue = await proj.GetAttributeAsync(key, ProjectStorageType.ProjectFile);
var projUserValue = await proj.GetAttributeAsync(key, ProjectStorageType.UserFile);
// Both above variables will contain "A"

This means no matter what you ask for, the value of user file will override the value of the project file. Perhaps we could work with that, even though we are specifically asking for the value in the project file and not the user file. But, to make the matters worse, if we try to remove the values stored in the user file the API will simply set its value to null and it still overrides the value of the project file.

var result = await proj.RemoveAttributeAsync(key, ProjectStorageType.UserFile);

The above code will make the user file look like the following.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <!-- Removed for brevity -->
    <MySetting />
  </PropertyGroup>
</Project>

That means if you ever store the value in the user file you will have to live with it forever! or resort to a hacky direct access to the file. If you do that you will have to go all the way and read, and write values yourself. I wonder how any other developer overcome this issue or maybe just never store values in user files!

I looked at the source code of these methods and they seem to be implemented correctly based on my limited knowledge of the VS SDK and its secrets.

RobertvanderHulst commented 2 months ago

We only use the User file for things like "Show All Files", because user files are not added to Source Code Control

rezanid commented 2 months ago

We only use the User file for things like "Show All Files", because user files are not added to Source Code Control

Indeed. In the extension I am working on there is a special setting that the developer can choose to store it at any of the following levels:

The first two would mean this setting will be shared by the development team but the last two would be specific to a single developer. I have to do this because there are different ways different teams prefer to work and I don't want to force them to change their way of working. For the time being I have to remove the last option. They can manually edit the user file if they need to override the setting.