Compartir a través de


Componentes de una extensión de VisualStudio.Extensibility

Una extensión que utiliza VisualStudio.Extensibility suele tener varios componentes que interactúan entre sí y también con Visual Studio.

Instancia de extensión

Las extensiones deben tener una clase que derive de Extension. Para ver un ejemplo de implementación, consulte MarkdownLinter.

Una instancia de la clase Extension es el punto de partida para la ejecución de la extensión. Esta instancia contiene los métodos necesarios para que Visual Studio consulte los servicios proporcionados por la extensión. También proporciona métodos virtuales para que la extensión proporcione recursos localizados y servicios locales propiedad de la extensión que se compartirán entre los componentes de la extensión.

La configuración de la clase Extension también contiene los metadatos de la extensión que se muestra en la ventana Administrar extensiones de Visual Studio y, para las extensiones publicadas, en Visual Studio Marketplace.

[VisualStudioContribution]
public class MarkdownLinterExtension : Extension
{
    /// <inheritdoc/>
    public override ExtensionConfiguration ExtensionConfiguration => new()
    {
        Metadata = new(
                id: "MarkdownLinter.0cf26ba2-edd5-4419-8646-a55d0a83f7d8",
                version: this.ExtensionAssemblyVersion,
                publisherName: "Microsoft",
                displayName: "Markdown Linter Sample Extension",
                description: "Sample markdown linter extension"),
    };
    ...

Para los desarrolladores de extensiones que están familiarizados con las APIs existentes de VSSDK, el Metadata contenido en ExtensionConfiguration se utiliza para generar el archivo .vsixmanifest. Además, la clase Extension es similar a la clase AsyncPackage que se utiliza en el modelo de extensibilidad de VSSDK.

Objeto VisualStudioExtensibility

El objeto VisualStudioExtensibility actúa como punto de entrada para las características de extensibilidad expuestas por Visual Studio. Esta clase tiene varios métodos de extensión y propiedades para enumerar rápidamente las características disponibles en el SDK de extensibilidad. Consulte la documentación de la API para conocer los métodos disponibles.

Partes de extensión

Para las funciones en las que una extensión aporta componentes a Visual Studio, como comandos o escuchas del editor, las extensiones utilizarán clases atribuidas. El proceso de compilación generará los metadatos correctos para garantizar que Visual Studio pueda detectar estos componentes.

Para las funciones en las que una extensión aporta componentes a Visual Studio, como comandos, escuchas del editor, ventanas de herramientas, etc., las extensiones utilizan clases marcadas con el atributo VisualStudioContribution. El proceso de compilación genera los metadatos correctos para garantizar que Visual Studio pueda detectar estos componentes.

Actualmente, el SDK es compatible con un conjunto limitado de componentes que se pueden aportar:

Las instancias de estas clases se crean como parte del marco de extensibilidad proporcionado por el SDK mediante una biblioteca de inyección de dependencias, y los constructores pueden utilizarse para recuperar instancias de servicios proporcionados por el SDK o por la propia extensión para compartir el estado entre los componentes.

Vida útil de las partes de la extensión

El tiempo de vida de cada parte es gestionado por el componente respectivo que carga esas partes dentro del proceso IDE de Visual Studio.

  • Los controladores de comandos se inicializan cuando se activa el conjunto de comandos correspondiente, lo que puede ocurrir durante la primera ejecución del comando. Una vez activados, los controladores de comandos solo deben desecharse cuando se apaga el IDE.

  • De forma similar, los escuchadores de eventos de vista de texto se inicializan cuando se carga en el IDE la primera vista de texto que coincide con el tipo de contenido especificado. Actualmente, estos escuchadores están activos hasta que se cierra el IDE, pero este comportamiento puede cambiar en el futuro.

En general, para extensiones complejas recomendamos que las extensiones proporcionen servicios locales que las partes puedan importar en su constructor y usar esos servicios para compartir estado entre partes y entre instancias de la misma parte. Esta práctica garantiza que el estado de la extensión no se vea afectado por los cambios de vida de las partes de la extensión.

Servicios de inyección proporcionados por el SDK

El SDK proporciona los siguientes servicios que pueden utilizarse en el constructor de cualquier parte de extensión:

  • VisualStudioExtensibility: Cada parte de extensión puede inyectar una instancia de VisualStudioExtensibility para interactuar con Visual Studio IDE.

  • Extension: Las partes pueden inyectar un tipo Microsoft.VisualStudio.Extensibility.Extension o el propio tipo de la extensión heredando de él a las partes de la extensión.

  • TraceSource: Se crea una instancia de origen de seguimiento bajo demanda para cada extensión que se puede utilizar para registrar información de diagnóstico. Estas instancias se registran con el proveedor de diagnósticos de Visual Studio que puede utilizarse para fusionar registros de múltiples servicios y utilizar futuras herramientas para acceder al registro en tiempo real. Consulte Registro.

  • Servicios locales: Cualquier servicio local proporcionado por la propia extensión también estará disponible para la inyección de dependencias.

  • MefInjection<TService> y AsyncServiceProviderInjection<TService, TInterface>: Las extensiones In-proc pueden inyectar servicios del SDK de Visual Studio que tradicionalmente se consumirían a través de MEF o del AsyncServiceProvider.

Servicios de extensión locales

En ciertos escenarios, una extensión puede querer compartir estado entre diferentes componentes, como un gestor de comandos y un receptor de cambios de vista de texto, como puede verse en el ejemplo MarkdownLinter. Estos servicios pueden añadirse a la colección de servicios en proceso anulando el método Extension.InitializeServices y cuando se crean instancias de partes de la extensión, los servicios se inyectan basándose en los argumentos del constructor.

Hay tres opciones para añadir un servicio:

  • AddTransient: Se crea una nueva instancia del servicio para cada parte que lo ingiere.
  • AddScoped: Se crea una nueva instancia del servicio dentro de un ámbito determinado. En el contexto de la extensibilidad de Visual Studio, el ámbito se refiere a una única parte de extensión.
  • AddSingleton: Hay una única instancia compartida del servicio que se crea en la primera ingesta.

Debido a que el tiempo de vida del objeto VisualStudioExtensibility está ligado al ámbito de una única parte de extensión, cualquier servicio local que lo ingiera tiene que ser un servicio de ámbito o transitorio. Intentar crear un servicio singleton que lo inyecte VisualStudioExtensibility resultará en un fallo.

Para ver un ejemplo de cómo se utilizan los servicios locales, consulte la extensión MarkdownLinter.

Contexto de cliente

Dado que todas las extensiones en el nuevo SDK se ejecutan fuera de proceso, introducimos el concepto de contexto de cliente para varias partes de la extensión para representar el estado del IDE en el momento en que se invoca el evento o método. Este contexto está representado por la instancia IClientContext en el SDK y se pasa a varias operaciones como los controladores de ejecución de comandos. El SDK proporciona métodos de extensión IClientContext que pueden utilizarse para recuperar objetos del contexto. Por ejemplo, las extensiones pueden obtener la vista de texto activa o el URI de los elementos seleccionados en el momento de la ejecución del comando utilizando la instancia IClientContext.

Algunos componentes, como los comandos, también permiten declarar en qué contextos están interesados. Esto se hace para optimizar la cantidad de datos transferidos en cada ejecución remota ya que el contexto del cliente puede llegar a ser grande en el futuro. En la vista previa inicial, solo hay dos contextos disponibles, Shell y Editor, y ambos se incluyen por defecto al declarar un comando utilizando CommandAttribute.