Consulta de la API del proyecto
La API de consulta del proyecto VisualStudio.Extensibility permite consultar información desde el sistema de proyecto. Los sistemas de proyectos forman parte de los componentes de Visual Studio para ayudar a los usuarios a trabajar con proyectos y mantenerlos, ejecutar compilaciones para producir resultados y probar la salida.
El objetivo de la API de consulta del proyecto es:
- Trabajar con sistemas de proyectos
- Recuperar datos de proyectos
- Realizar cambios en los proyectos
Entre algunos ejemplos se incluyen el reconocimiento de los archivos incluidos en un proyecto, los paquetes NuGet a los que hace referencia un proyecto, la adición de nuevos archivos a un proyecto o el cambio de las propiedades de un proyecto.
Puede obtener más información sobre los sistemas de proyectos aquí. Puede encontrar documentación conceptual sobre lo que es el sistema de proyecto, sus usos y sus distintos términos aquí.
Trabajar con la API de consulta del proyecto
En esta introducción se describen los principales escenarios para trabajar con la API de consulta del proyecto:
- Acceso al espacio de consulta del proyecto
- Consulta del sistema de proyecto de un proyecto
- Especificación de los parámetros del proyecto que se van a incluir en el resultado de la consulta
- Filtrado de los resultados de la consulta
- Uso de las consultas anidadas para especificar las propiedades deseadas
- Recuperación de una colección secundaria mediante el método Get
- Consulta de información adicional de un artículo devuelto anteriormente
- Modificación de un proyecto
- Consulta de propiedades del proyecto
- Consulta de soluciones
- Consulta de carpetas de soluciones
- Enumeración de archivos de origen con información adicional en un proyecto
- Consulta de proyectos que poseen un archivo de origen específico
- Consulta de las configuraciones del proyecto y sus propiedades
- Consulta de las referencias entre proyectos
- Consulta de referencias de paquetes
- Consulta de grupos de salida del proyecto
Acceso al espacio de consulta del proyecto
Para poder consultar el sistema de proyecto, debe obtener una instancia del objeto del espacio de consulta del proyecto, que tiene varios métodos asíncronos que consultan o actualizan el sistema de proyecto. El término espacio de consulta del proyecto y el término área de trabajo significan lo mismo: el objeto que proporciona acceso a todos los datos de un proyecto.
Acceso al espacio de consulta del proyecto en una extensión fuera de proceso
Si va a crear una extensión fuera de proceso, use el código siguiente:
WorkspacesExtensibility workSpace = this.Extensibility.Workspaces();
Acceso al espacio de consulta del proyecto en una extensión dentro de proceso
Si va a crear una extensión dentro de proceso, en su lugar tiene acceso al espacio de consulta del proyecto, como se muestra en el ejemplo de código siguiente. A menos que haya creado específicamente una extensión dentro de proceso, utilice el fragmento de la sección anterior para obtener una instancia del objeto de espacio de consulta del proyecto.
En el fragmento de código siguiente, package
representa una instancia de AsyncPackage, una clase utilizada en el desarrollo de extensiones de Visual Studio. El método GetServiceAsync
se emplea para adquirir de forma asíncrona el servicio de consultas desde el contenedor de servicio de Visual Studio.
IProjectSystemQueryService queryService = await package.GetServiceAsync<IProjectSystemQueryService, IProjectSystemQueryService>();
ProjectQueryableSpace workSpace = queryService.QueryableSpace;
Consulta del sistema de proyecto de un proyecto
El objeto WorkspacesExtensibility
le permite consultar un proyecto individual, si tiene el GUID del proyecto. Normalmente, hay dos GUID asociados a un proyecto, uno representa el tipo de proyecto y otro representa de forma única el proyecto. Puede encontrar el GUID único del proyecto en el archivo de solución o desde una extensión. También puede consultar la propiedad Guid
como se muestra en la sección siguiente.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projectList = workspace
.ProjectsByProjectGuid(knownGuid)
.QueryAsync(cancellationToken);
Especificación de los parámetros del proyecto que se van a incluir en el resultado de la consulta
Al consultar el sistema de proyecto, puede utilizar cláusulas With
para controlar qué parámetros o metadatos se incluyen en los resultados de la consulta. Hay varias maneras válidas de especificar qué parámetros se deben incluir.
Ejemplo de uso de una cláusula independiente With
para cada parámetro
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
.Projects
.With(p => p.Path)
.With(p => p.Guid)
.With(p => p.Kind) // DTE.Project.Kind
.With(p => p.Type) // VSHPROPID_ProjectType
.With(p => p.TypeGuid) // VSHPROPID_TypeGuid
.With(p => p.Capabilities)
.QueryAsync(cancellationToken);
await foreach (IQueryResultItem<IProjectSnapshot> project in allProjects)
{
var projectGuid = project.Value.Guid;
// Checking whether 'Capabilities' property has been retrieved.
// Otherwise, it can throw for projects which do not support it. (Like SQL projects)
bool capabilities = project.Value.PropertiesAvailableStatus.Capabilities;
}
Ejemplo de uso de una cláusula única With
para especificar varios parámetros
También puede especificar varios parámetros deseados en una sola cláusula With
.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
.Projects
.With(p => new { p.Path, p.Guid, p.Capabilities })
.QueryAsync(cancellationToken);
Ejemplo de uso de una cláusula WithRequired
Cuando se usa WithRequired
, solo se devuelven proyectos con las propiedades requeridas.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projectWithFiles = workSpace
.Projects
.With(p => new { p.Path, p.Guid })
.WithRequired(p => p.Files.Where(f => f.FileName == "information.txt"))
.QueryAsync(cancellationToken);
Ejemplo de cuando no se especifica ninguna propiedad
Cuando no se especifica ninguna propiedad, se devuelve el conjunto predeterminado de propiedades.
IAsyncEnumerable<IQueryResultItem<IPropertySnapshot>> properties = myproject
.PropertiesByName("RootNamespace", "AssemblyVersion")
.QueryAsync(cancellationToken);
Filtrado de los resultados de la consulta
Para limitar los resultados de la consulta, hay dos maneras de aplicar el filtrado condicional: instrucciones Where
y métodos de consulta con filtrado integrado.
Ejemplo de uso de una cláusula Where
Los distintos tipos de proyecto admiten diferentes conjuntos de funcionalidades. Con una cláusula Where
, puede filtrar por proyectos que admitan funcionalidades específicas. Las consultas pueden generar un error si no filtra por proyectos que admiten las funcionalidades pertinentes.
El código siguiente devuelve Path
y Guid
de todos los proyectos web de .NET Core del área de trabajo:
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
.Projects
.Where(p => p.Capabilities.Contains("DotNetCoreWeb"))
.With(p => new { p.Path, p.Guid })
.QueryAsync(cancellationToken);
Ejemplo de uso del filtrado integrado
También puede utilizar métodos de consulta como ProjectsByCapabilities
, que tienen filtrado integrado en la consulta.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
.ProjectsByCapabilities("DotNetCoreWeb | DotNetCoreRazor")
.With(p => new { p.Path, p.Guid })
.QueryAsync(cancellationToken);
Uso de consultas anidadas para especificar las propiedades deseadas
Algunos parámetros son colecciones en sí mismos y puede utilizar consultas anidadas para realizar especificaciones y filtros similares en esas colecciones secundarias.
Ejemplo
En el ejemplo siguiente, una consulta anidada permite filtrar y especificar la colección de archivos que se incluirán con cada proyecto devuelto por la consulta externa.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
.ProjectsByCapabilities("CPS")
.With(p => new { p.Path, p.IsProjectFileSearchable })
.With(p => p.PropertiesByName("ApplicationIcon")) // Retrieve a single property, if it exists
.With(p => p.Files // Without any condition, retrieve all files in the project, but filter them
.Where(f => f.Extension == ".ico")
.With(f => new { f.Path, f.IsHidden }))
.QueryAsync(cancellationToken);
await foreach (IQueryResultItem<IProjectSnapshot> project in projects)
{
IPropertySnapshot property = project.Value.Properties.FirstOrDefault();
string? applicationIcon = (string?)property?.Value;
foreach (var iconFile in project.Value.Files)
{
string filePath = iconFile.Path;
bool isHidden = iconFile.IsHidden;
}
}
Recuperación de una colección secundaria a través del método Get
El modelo de proyecto de Visual Studio tiene colecciones para proyectos, así como colecciones secundarias, por ejemplo, para archivos o funcionalidades de proyectos dentro de proyectos. Para recuperar una colección secundaria en sí misma, puede usar una cláusula Get
. Al igual que otros tipos de consulta, la cláusula Get
permite utilizar otras cláusulas como la cláusula With
para dar forma o limitar los resultados.
IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files = workspace
.Projects
.Where(p => p.Guid == knownGuid)
.Get(p => p.Files
.With(f => new { f.Path, f.IsHidden, f.IsSearchable }))
.QueryAsync(cancellationToken);
await foreach (var file in files)
{
string filePath = file.Value.Path;
}
Consulta de información adicional de un artículo devuelto anteriormente
Puede utilizar los resultados de una consulta anterior como base para consultas adicionales.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
.Projects
.With(p => p.Path)
.With(p => p.Guid)
.QueryAsync(cancellationToken);
await foreach (IQueryResultItem<IProjectSnapshot> project in allProjects)
{
// Gets child collections
IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files = project.Value
.Files
.With(f => new { f.Path, f.ItemType })
.QueryAsync(cancellationToken);
}
Modificación de un proyecto
Normalmente, los resultados de la consulta son inmutables. También puede utilizar la API de consulta para realizar cambios mediante la cláusula AsUpdatable
para tener acceso a versiones mutables de los resultados de la consulta, de modo que pueda realizar cambios en los proyectos y en los elementos del proyecto.
Ejemplo de adición de un archivo a un proyecto en un resultado de consulta
IQueryResult<IProjectSnapshot> updatedProjects = workSpace
.ProjectsByProjectGuid(knownGuid)
.AsUpdatable()
.CreateFile("AdditionalInformation.txt", textContent)
.ExecuteAsync(cancellationToken);
Ejemplo de adición de un archivo a un proyecto devuelto anteriormente
IQueryResult<IProjectSnapshot> updatedProjects = myproject
.AsUpdatable()
.CreateFile("AdditionalInformation2.txt", textContent)
.ExecuteAsync(cancellationToken);
Consulta de propiedades del proyecto
Puede utilizar una cláusula Get
para consultar las propiedades del proyecto. La consulta siguiente devuelve una colección de IPropertySnapshot
que contiene entradas para las dos propiedades solicitadas. IPropertySnapshot
contiene el nombre de propiedad, el nombre para mostrar y el valor en un momento dado.
// We assume that we can find the "RootNamespace" property in the result.
// However it isn't true from query API point of view.
// The query tries to retrieve items based on the condition, and if there is no such item, it will run successfully, only without returning items.
IAsyncEnumerable<IQueryResultItem<IPropertySnapshot>> properties = myProject
.AsQueryable()
.Get(p => p.PropertiesByName("RootNamespace", "AssemblyVersion"))
.QueryAsync(cancellationToken);
Consulta de soluciones
Además de trabajar con proyectos como se mostró anteriormente, puede utilizar técnicas similares para trabajar con soluciones.
IAsyncEnumerable<IQueryResultItem<ISolutionSnapshot>> solutions = workSpace
.Solutions
.With(s => new { s.Path, s.Guid, s.ActiveConfiguration, s.ActivePlatform })
.QueryAsync(cancellationToken);
Consulta de carpetas de soluciones
Del mismo modo, puede utilizar una cláusula Get
para consultar las carpetas de la solución. La propiedad IsNested
le permite incluir o excluir carpetas anidadas de los resultados. El Explorador de soluciones puede tener carpetas anidadas, como para la configuración o los recursos.
IAsyncEnumerable<IQueryResultItem<ISolutionFolderSnapshot>> solutionFolders = workSpace
.Solutions
.Get(s => s.SolutionFolders)
.With(folder => folder.Name)
.With(folder => folder.IsNested)
.With(folder => folder.VisualPath) // it's a relative (virtual) path to represent how the folder is nested.
.QueryAsync(cancellationToken);
Aquí se obtienen todas las carpetas de soluciones anidadas, proyectos y archivos dentro de una carpeta de solución (no anidada de forma recursiva):
IAsyncEnumerable<IQueryResultItem<ISolutionSnapshot>> solutionFoldersWithExtraInformation = mySolutionFolder
.AsQueryable()
.With(folder => folder.Files
.With(f => f.Path))
.With(folder => folder.Projects
.With(p => new { p.Name, p.Guid }))
.With(folder => folder.SolutionFolders
.With(nested => nested.Name))
.QueryAsync(cancellationToken);
Aquí se obtienen todas las carpetas de soluciones anidadas de forma recursiva. VisualPath
es la ruta de acceso tal como se muestra en el Explorador de soluciones.
string visualPath = mySolutionFolder.VisualPath;
IAsyncEnumerable<IQueryResultItem<ISolutionFolderSnapshot>> recursivelyNestedFolders = await workSpace
.Solutions
.Get(s => s.SolutionFolders)
.Where(f => f.VisualPath.StartsWith(visualPath) && f.VisualPath != visualPath)
.With(f => f.Name)
.QueryAsync(cancellationToken);
Enumeración de archivos de origen con información adicional en un proyecto
Este es un ejemplo que enumera todos los archivos .xaml de un proyecto y su generador de código:
IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files =
workSpace.ProjectsByProjectGuid(knownGuid)
.Get(p => p.Files)
.Where(file => file.Extension == ".xaml")
.With(file => file.Path)
.With(file => file.PropertiesByName("Generator"))
.QueryAsync(cancellationToken);
Otro ejemplo consiste en empezar con un proyecto devuelto por la consulta anterior:
IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files = myProject
.FilesEndingWith(".xaml") // use built-in filter instead of 'Where' condition
.With(file => file.Path)
.With(file => file.PropertiesByName("Generator"))
.QueryAsync(cancellationToken);
O para obtener todos los archivos de contenido, que son archivos no compilados necesarios en el tiempo de ejecución, como archivos HTML y CSS.
IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files =
myProject.FilesWithItemTypes("Content")
.With(file => file.Path)
.QueryAsync(cancellationToken);
O para enumerar todos los archivos con una extensión determinada, como archivos de esquema XML (archivos .xsd
) en todos los proyectos:
IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> schemaFiles =
workSpace.Projects
.Get(proj => proj.FilesEndingWith(".xsd"))
.With(file => file.Path)
.QueryAsync(cancellationToken);
await foreach (IQueryResultItem<IFileSnapshot> fileResult in schemaFiles)
{
DoSomething(fileResult.Value.Path);
}
Consulta de proyectos que poseen un archivo de origen específico
Los proyectos y carpetas tienen información sobre los archivos que poseen o contienen, por lo que puede utilizar una cláusula WithRequired
para consultar proyectos que incluyan determinados archivos.
Ejemplo de búsqueda de proyectos que poseen un archivo determinado
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
.Projects
.WithRequired(proj => proj.FilesByPath(myFilePath))
.With(proj => proj.Guid)
.QueryAsync(cancellationToken);
Ejemplo de búsqueda de carpetas de soluciones que contienen un archivo determinado
IAsyncEnumerable<IQueryResultItem<ISolutionFolderSnapshot>> solutionFolders = workspace
.Solutions
.Get(s => s.SolutionFolders)
.WithRequired(folder => folder.FilesByPath(myFilePath))
.With(folder => folder.Name)
.With(folder => folder.Guid)
.QueryAsync(cancellationToken);
Consulta de las configuraciones del proyecto y sus propiedades
Los proyectos tienen una propiedad ConfigurationDimension
que puede utilizar para buscar información sobre la configuración del proyecto. La información sobre la configuración del proyecto se relaciona con las configuraciones de compilación del proyecto (por ejemplo, Debug
y Release
).
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
.Projects
.With(p => new { p.Guid, p.Name })
.With(p => p.Configurations
.With(c => c.Name)
.With(c => c.PropertiesByName("OutputPath"))
.With(c => c.ConfigurationDimensions)) // ConfigurationDimension is essentially Name, Value pairs, both are default properties.
.QueryAsync(cancellationToken);
await foreach (IQueryResultItem<IProjectSnapshot> project in projects)
{
foreach (var configuration in project.Value.Configuration)
{
// ...
}
}
Consulta de las referencias entre proyectos
También puede realizar una consulta para buscar proyectos que hagan referencia a un proyecto determinado.
Ejemplo de búsqueda de todos los proyectos a los que hace referencia el proyecto actual
IAsyncEnumerable<IQueryResultItem<IProjectReferenceSnapshot>> projectReferences = myProject
.ProjectReferences
.With(r => r.ProjectGuid)
.With(r => r.ReferencedProjectId)
.QueryAsync(cancellationToken);
Ejemplo de búsqueda de todos los proyectos que hacen referencia al proyecto actual
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workSpace
.Projects
.With(p => p.Guid)
.WithRequired(p => p.ProjectReferences
.Where(r => r.ProjectGuid == knownGuid))
.QueryAsync(cancellationToken);
Consulta de referencias de paquetes
Del mismo modo, puede consultar las referencias del paquete NuGet.
Ejemplo de búsqueda de todos los paquetes a los que hace referencia el proyecto actual
IAsyncEnumerable<IQueryResultItem<IProjectConfigurationSnapshot>> configurationsWithPackageReferences = myProject
.ActiveConfigurations
.With(c => c.Name)
.With(c => c.PackageReferences
.With(p => new { p.Name, p.Version }))
.QueryAsync(cancellationToken);
Ejemplo de búsqueda de todos los proyectos que hacen referencia a un paquete NuGet específico
string packageName = "Newtonsoft.Json";
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workSpace
.Projects
.With(p => p.Guid)
.WithRequired(p => p.ActiveConfigurations
.WithRequired(c => c.PackageReferences
.Where(package => package.Name == packageName)))
.QueryAsync(cancellationToken);
Consulta de grupos de salida del proyecto
Las configuraciones del proyecto contienen información sobre los grupos de salida del proyecto.
// From our list of active configurations, we need to get the first one in the list
IAsyncEnumerable<IQueryResultItem<IProjectConfigurationSnapshot>> configurations = myProject
.ActiveConfigurations
.QueryAsync(cancellationToken);
IProjectConfigurationSnapshot myConfiguration = null;
await foreach (IQueryResultItem<IProjectConfigurationSnapshot> config in configurations)
{
myConfiguration = config.Value;
break;
}
// A multi-target project may have multiple active configurations
IAsyncEnumerable<IQueryResultItem<IOutputGroupSnapshot>> outputGroups = myConfiguration
.OutputGroupsByName("Built", "Symbols")
.With(g => g.Name)
.With(g => g.Outputs)
.QueryAsync(cancellationToken);
Pasos siguientes
Revise el código de una extensión que utilice la API de consulta del proyecto en VSProjectQueryAPISample.