工作区

工作区是 Visual Studio 表示打开文件夹中的任何文件集合的方式,它由 IWorkspace 类型表示。 工作区本身不了解与文件夹内的文件相关的内容或功能。 相反,它为功能和扩展提供了一组通用的 API,用于生成和使用其他人可以操作的数据。 生成器是通过使用各种导出属性的 Managed Extensibility Framework (MEF) 组成的。

工作区提供程序和服务

工作区提供程序和服务提供对工作区内容做出反应的数据和功能。 它们可能提供上下文文件信息、源文件中的符号或生成功能。

这两个概念都使用工厂模式,并由工作区通过 MEF 导入。 所有导出属性都会实施 IProviderMetadataBaseIWorkspaceServiceFactoryMetadata,但对于一些具体类型,应对导出的类型使用扩展。

提供程序和服务之间的一个区别在于它们与工作区的关系。 工作区可以有多个特定类型的提供程序,但每个工作区只能创建一个特定类型的服务。 例如,工作区包含许多文件扫描程序提供程序,但每个工作区只有一个索引服务。

另一个关键区别是对来自提供程序和服务的数据的使用。 工作区是出于多种原因从提供程序获取数据的入口点。 首先,提供程序通常具有一些它们创建的窄数据集。 数据可能是 C# 源文件的符号,也可能是 CMakeLists.txt 文件的生成文件上下文。 工作区会将使用者的请求与元数据与请求一致的提供程序相匹配。 其次,某些应用场景允许许多提供者参与请求,而其他应用场景则使用具有最高优先级的提供程序。

相比之下,扩展可以获取工作区服务的实例并直接与工作区服务交互。 可用的 IWorkspace 扩展方法适用于 Visual Studio 提供的服务,例如 GetFileWatcherService。 扩展可以为扩展中的组件或其他扩展提供工作区服务。 使用者应使用 GetServiceAsync 或在 IWorkspace 类型上提供的扩展方法。

警告

不要创作与 Visual Studio 冲突的服务。 这可能会导致意外问题。

工作区关闭时的处理

关闭工作区时,扩展程序可能需要处理但却调用了异步代码。 IAsyncDisposable 接口可用于轻松编写此代码。

工作区设置

工作区具有 IWorkspaceSettingsManager 服务,可以对工作区进行简单但强大的控制。 有关设置的基本概述,请参阅自定义生成和调试任务

大多数 SettingsType 类型的设置是 .json 文件,例如 VSWorkspaceSettings.jsontasks.vs.json

工作区设置的强大功能围绕“作用域”,即工作区中的路径。 当使用者调用 GetAggregatedSettings 时,将聚合包含所请求路径和设置类型的所有作用域。 作用域聚合优先级如下所示:

  1. “本地设置”,通常是工作区根目录 .vs
  2. 所请求路径本身。
  3. 所请求路径的父目录。
  4. 所有更高层级的父目录,直至并包括工作区根目录。
  5. “全局设置”,位于用户目录中。

最后将得到 IWorkspaceSettings 的实例。 此对象保存特定类型的设置,并且可以查询用于设置存储为 string 的密钥名称。 GetProperty 方法和 WorkspaceSettingsExtensions 扩展方法要求调用方知道所请求的设置值的类型。 由于大多数设置文件都保留为 .json 文件,因此许多调用将使用这些类型的 stringboolint 和数组。 此外还支持对象类型。 在这些情况下,可以使用 IWorkspaceSettings 本身作为类型参数。 例如:

{
  "intValue": 1,
  "stringValue": "s",
  "boolValue": true,
  "stringArray": [
    "s1",
    "s2"
  ],
  "nestedIWorkspaceSettings": {
    "nestedString": "ns"
  }
}

假设这些设置位于用户的 VSWorkspaceSettings.json 中,则可以按如下方式访问数据:

using System.Collections.Generic;
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Settings;

private static void ReadSettings(IWorkspace workspace)
{
    IWorkspaceSettingsManager settingsManager = workspace.GetSettingsManager();
    IWorkspaceSettings settings = settingsManager.GetAggregatedSettings(SettingsTypes.Generic);

    // result == WorkspaceSettingsResult.Success
    WorkspaceSettingsResult result = settings.GetProperty("intValue", out int intValue);
    result = settings.GetProperty("stringValue", out string stringValue);
    result = settings.GetProperty("boolValue", out bool boolValue);
    result = settings.GetProperty("stringArray", out string[] stringArray);
    result = settings.GetProperty("nestedIWorkspaceSettings", out IWorkspaceSettings nestedIWorkspaceSettings);
    result = nestedIWorkspaceSettings.GetProperty("nestedString", out string nestedString);

    // Extension method alternative using default values.
    int intValueOrDefault = settings.Property("intValue", /* default */ 42);

    // Missing key. result == WorkspaceSettingsResult.Undefined
    result = settings.GetProperty("missing", out string missing);

    // Wrong type for a key. result == WorkspaceSettingsResult.Error
    result = settings.GetProperty("intValue", out IWorkspaceSettings notSettings);

    // Special ability to union "stringArray" across all scopes.
    IEnumerable<string> allStringArray = settings.UnionPropertyArray<string>("stringArray");
}

注意

这些设置 API 与 Microsoft.VisualStudio.Settings 命名空间中可用的 API 无关。 工作区设置与主机无关,并使用特定于工作区的设置文件或动态设置提供程序。

提供动态设置

扩展可以提供 IWorkspaceSettingsProvider。 这些内存中提供程序允许扩展添加设置或替代其他项。

导出 IWorkspaceSettingsProvider 与其他工作区提供程序不同。 工厂不是 IWorkspaceProviderFactory,没有特殊的属性类型。 相反,它实施 IWorkspaceSettingsProviderFactory 并使用 [Export(typeof(IWorkspaceSettingsProviderFactory))]

// Common workspace provider factory pattern
[ExportFeatureProvider(some, args, to, export)]
internal class MyProviderFactory : IWorkspaceProviderFactory<IFeatureProvider>
{
     IFeatureProvider CreateProvider(IWorkspace workspace) => new Provider(workspace);
}

// IWorkspaceSettingsProvider pattern
[Export(typeof(IWorkspaceSettingsProviderFactory))]
internal class MySettingsProviderFactory : IWorkspaceSettingsProviderFactory
{
    // 100 is typically the value used by built-in settings providers. Lower value is higher priority.
    int Priority => 100;

    IWorkspaceSettingsProvider CreateSettingsProvider(IWorkspace workspace) => new MySettingsProvider(workspace);
}

提示

实施返回 IWorkspaceSettingsSource 的方法(如 IWorkspaceSettingsProvider.GetSingleSettings)时,将返回 IWorkspaceSettings 的一个实例而不是 IWorkspaceSettingsSourceIWorkspaceSettings 提供在某些设置聚合期间有用的详细信息。

工作区建议做法

  • IWorkspaceProviderFactory.CreateProvider 或在创建时记住其 Workspace 上下文的类似 API 返回对象。 编写提供程序接口时,预期此对象在创建时会保留。
  • 将特定于工作区的缓存或设置保存在工作区的“本地设置”路径内。 在 Visual Studio 2017 版本 15.6 或更高版本中使用 Microsoft.VisualStudio.Workspace.WorkspaceHelper.MakeRootedUnderWorkingFolder 为文件创建路径。 对于版本 15.6 之前的版本,请使用以下代码片段:
using System.IO;
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Settings;

private static string MakeRootedUnderWorkingFolder(IWorkspace workspace, string relativePath)
{
    string workingFolder = workspace.GetSettingsManager().GetAggregatedSettings(SettingsTypes.WorkspaceControlSettings).Property<string>("WorkingFolder");
    return Path.Combine(workingFolder, relativePath);
}

解决方案事件和包自动加载

加载的包可以实施 IVsSolutionEvents7 并调用 IVsSolution.AdviseSolutionEvents。 其中包括在 Visual Studio 中打开和关闭文件夹时的事件处理。

UI 上下文可用于自动加载包。 该值为 4646B819-1AE0-4E79-97F4-8A8176FDD664

故障排除

SourceExplorerPackage package 包未正确加载

工作区扩展性严重基于 MEF,组合错误将导致承载“打开文件夹”的包无法加载。 例如,如果扩展导出了一个包含 ExportFileContextProviderAttribute 的类型,但该类型仅实施 IWorkspaceProviderFactory<IFileContextActionProvider>,则尝试在 Visual Studio 中打开文件夹时将发生错误。

有关错误详细信息,请参阅 %LOCALAPPDATA%\Microsoft\VisualStudio\16.0_id\ComponentModelCache\Microsoft.VisualStudio.Default.err。 解决扩展实施的类型的任何错误。

  • 文件上下文 - 文件上下文提供程序为“打开文件夹”工作区带来代码智能。
  • 索引 - 工作区索引可收集并保留有关工作区的信息。