Eseguire query sull'API del progetto

L'API di query del progetto VisualStudio.Extensibility consente di eseguire query sulle informazioni dal sistema di progetto. I sistemi di progetto fanno parte dei componenti di Visual Studio per aiutare gli utenti a lavorare con e gestire progetti, eseguire compilazioni per produrre risultati e testare l'output.

L'obiettivo dell'API Project Query è:

  1. Usare i sistemi di progetto
  2. Recuperare dati dai progetti
  3. Apportare modifiche ai progetti

Alcuni esempi includono la comprensione dei file inclusi in un progetto, i pacchetti NuGet a cui fa riferimento un progetto, l'aggiunta di nuovi file a un progetto o la modifica delle proprietà del progetto.

Altre informazioni sui sistemi di progetto sono disponibili qui. Trovare la documentazione concettuale sul sistema di progetto, sull'utilizzo e sui vari termini qui.

Usare l'API di query del progetto

Questa panoramica illustra gli scenari principali per l'uso dell'API di query del progetto:

Accedere allo spazio di query del progetto

Prima di poter eseguire query sul sistema del progetto, è necessario ottenere un'istanza dell'oggetto spazio query del progetto, che include diversi metodi asincroni che eseguono query o aggiornano il sistema di progetto. Il termine spazio di query del progetto e il termine area di lavoro significano entrambi la stessa cosa, ovvero l'oggetto che fornisce l'accesso a tutti i dati per un progetto.

Accesso allo spazio di query del progetto in un'estensione out-of-process

Se si sta creando un'estensione out-of-process, usare il codice seguente:

WorkspacesExtensibility workSpace = this.Extensibility.Workspaces();

Accesso allo spazio di query del progetto in un'estensione in-process

Se si sta creando un'estensione in-process, si accede invece allo spazio di query del progetto, come illustrato nell'esempio di codice seguente. A meno che non sia stata creata in modo specifico un'estensione in-process, usare il frammento nella sezione precedente per ottenere un'istanza dell'oggetto spazio query del progetto.

Nell'estratto di codice seguente rappresenta package un'istanza di AsyncPackage, una classe utilizzata nello sviluppo di estensioni di Visual Studio. Il metodo GetServiceAsync viene usato per ottenere in modo asincrono il servizio di query dal contenitore del servizio di Visual Studio.

IProjectSystemQueryService queryService = await package.GetServiceAsync<IProjectSystemQueryService, IProjectSystemQueryService>();
ProjectQueryableSpace workSpace = queryService.QueryableSpace;

Eseguire una query sul sistema di progetto per un progetto

L'oggetto WorkspacesExtensibility consente di eseguire una query per un singolo progetto, se si dispone del GUID del progetto. Esistono in genere due GUID associati a un progetto, uno che rappresenta il tipo di progetto e un altro che rappresenta in modo univoco il progetto. È possibile trovare il GUID univoco del progetto nel file della soluzione o da un'estensione, è possibile eseguire una query per la Guid proprietà, come illustrato nella sezione successiva.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projectList = workspace
    .ProjectsByProjectGuid(knownGuid) 
    .QueryAsync(cancellationToken);

Specificare i parametri del progetto da includere nel risultato della query

Quando si esegue una query sul sistema di progetto, è possibile usare With le clausole per controllare quali parametri o metadati sono inclusi nei risultati della query. Esistono diversi modi validi per specificare quali parametri devono essere inclusi.

Esempio di utilizzo di una clausola separata With per ogni parametro

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;
    }

Esempio che usa una singola With clausola per specificare più parametri

È anche possibile specificare più parametri desiderati in una singola With clausola.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
    .Projects
    .With(p => new { p.Path, p.Guid, p.Capabilities })
    .QueryAsync(cancellationToken);

Esempio di utilizzo di una WithRequired clausola

Quando si usa WithRequired, vengono restituiti solo i progetti con le proprietà necessarie.

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);

Esempio quando non vengono specificate proprietà

Quando non vengono specificate proprietà, viene restituito il set predefinito di proprietà restituite.

IAsyncEnumerable<IQueryResultItem<IPropertySnapshot>> properties = myproject
    .PropertiesByName("RootNamespace", "AssemblyVersion")
    .QueryAsync(cancellationToken);

Filtrare il risultato della query

Per limitare i risultati delle query, esistono due modi per applicare il filtro condizionale: Where istruzioni e metodi di query con filtri predefiniti.

Esempio di utilizzo di una Where clausola

Diversi tipi di progetto supportano diversi set di funzionalità. Con una Where clausola è possibile filtrare i progetti che supportano funzionalità specifiche. Le query possono avere esito negativo se non si filtrano i progetti che supportano le funzionalità pertinenti.

Il codice seguente restituisce e PathGuid di tutti i progetti Web .NET Core nell'area di lavoro:

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
    .Projects
    .Where(p => p.Capabilities.Contains("DotNetCoreWeb"))
    .With(p => new { p.Path, p.Guid })
    .QueryAsync(cancellationToken);

Esempio di utilizzo del filtro predefinito

È anche possibile usare metodi di query come ProjectsByCapabilities che dispongono di filtri incorporati nella query.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
        .ProjectsByCapabilities("DotNetCoreWeb | DotNetCoreRazor")
        .With(p => new { p.Path, p.Guid })
        .QueryAsync(cancellationToken);

Usare query annidate per specificare le proprietà desiderate

Alcuni parametri sono raccolte e è possibile usare query annidate per eseguire specifiche e filtri simili per tali raccolte figlio.

Esempio

Nell'esempio seguente una query nidificata consente di filtrare e specificare la raccolta di file da includere in ogni progetto restituito dalla query esterna.

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;
        }
    }

Recuperare una raccolta figlio usando il metodo Get

Il modello di progetto di Visual Studio include raccolte per progetti e raccolte figlio, ad esempio per file o funzionalità di progetto all'interno dei progetti. Per recuperare una raccolta figlio stessa, è possibile utilizzare una Get clausola . Analogamente ad altri tipi di query, la Get clausola consente di usare altre clausole, ad esempio la With clausola per modellare o limitare i risultati.

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;
    }

Eseguire query su informazioni aggiuntive da un elemento restituito in precedenza

È possibile usare i risultati di una query precedente come base per query aggiuntive.

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);
}

Modificare un progetto

I risultati della query sono in genere non modificabili. È anche possibile usare l'API di query per apportare modifiche usando la AsUpdatable clausola per accedere alle versioni modificabili dei risultati della query, in modo da poter apportare modifiche ai progetti e agli elementi del progetto.

Esempio di aggiunta di un file a un progetto in un risultato della query

IQueryResult<IProjectSnapshot> updatedProjects = workSpace
    .ProjectsByProjectGuid(knownGuid)
    .AsUpdatable()
    .CreateFile("AdditionalInformation.txt", textContent)
    .ExecuteAsync(cancellationToken);

Esempio di aggiunta di un file a un progetto restituito in precedenza

IQueryResult<IProjectSnapshot> updatedProjects = myproject
    .AsUpdatable()
    .CreateFile("AdditionalInformation2.txt", textContent)
    .ExecuteAsync(cancellationToken);

Query per le proprietà del progetto

È possibile usare una Get clausola per eseguire una query per le proprietà del progetto. La query seguente restituisce una raccolta di IPropertySnapshot che contiene voci per le due proprietà richieste. IPropertySnapshot contiene il nome della proprietà, il nome visualizzato e il valore in un momento specifico.

// 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);

Query per le soluzioni

Oltre a lavorare con i progetti come illustrato in precedenza, è possibile usare tecniche simili per lavorare con le soluzioni.

IAsyncEnumerable<IQueryResultItem<ISolutionSnapshot>> solutions = workSpace
    .Solutions
    .With(s => new { s.Path, s.Guid, s.ActiveConfiguration, s.ActivePlatform })
    .QueryAsync(cancellationToken);

Query per le cartelle della soluzione

Analogamente, è possibile usare una Get clausola per eseguire query per le cartelle della soluzione. La IsNested proprietà consente di includere o escludere cartelle annidate dai risultati. Esplora soluzioni possono avere cartelle annidate, ad esempio per l'impostazione di configurazione o le risorse.

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);

In questo caso vengono recuperate tutte le cartelle di soluzioni annidate, i progetti, i file all'interno di una cartella della soluzione (non annidati in modo ricorsivo):

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);

In questo caso vengono recuperate tutte le cartelle di soluzioni nidificate in modo ricorsivo. VisualPath è il percorso visualizzato in Esplora soluzioni.

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);

Enumerazione dei file di origine con informazioni aggiuntive in un progetto

Ecco un esempio di enumerazione di tutti i file xaml in un progetto e del relativo generatore di codice:

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);

Un altro esempio consiste nell'iniziare con un progetto restituito dalla query precedente:

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);

Oppure per ottenere tutti i file di contenuto, che sono file non compilati necessari in fase di esecuzione, ad esempio file HTML e CSS.

IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files =
    myProject.FilesWithItemTypes("Content")
        .With(file => file.Path)
        .QueryAsync(cancellationToken);

In alternativa, per enumerare tutti i file con una determinata estensione, ad esempio file XML Schema (.xsd file) in tutti i progetti:

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);
    }

Eseguire una query per i progetti proprietari di un file di origine specifico

I progetti e le cartelle contengono informazioni sui file che possiedono o contengono, quindi è possibile usare una WithRequired clausola per eseguire una query per i progetti che includono determinati file.

Esempio di ricerca di progetti proprietari di un determinato file

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
    .Projects
    .WithRequired(proj => proj.FilesByPath(myFilePath))
    .With(proj => proj.Guid)
    .QueryAsync(cancellationToken);

Esempio di ricerca di cartelle di soluzioni che contengono un determinato file

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);

Eseguire una query per le configurazioni del progetto e le relative proprietà

I progetti hanno una ConfigurationDimension proprietà che è possibile usare per trovare le informazioni di configurazione del progetto. Le informazioni di configurazione del progetto sono correlate alle configurazioni di compilazione del progetto ,ad esempio Debug e 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)
        {
            // ...
        }
    }

Query per riferimenti da progetto a progetto

È anche possibile eseguire query per trovare progetti che fanno riferimento a un determinato progetto.

Esempio di ricerca di tutti i progetti a cui fa riferimento il progetto corrente

IAsyncEnumerable<IQueryResultItem<IProjectReferenceSnapshot>> projectReferences = myProject
    .ProjectReferences
    .With(r => r.ProjectGuid)
    .With(r => r.ReferencedProjectId)
    .QueryAsync(cancellationToken);

Esempio di ricerca di tutti i progetti che fanno riferimento al progetto corrente

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workSpace
    .Projects
    .With(p => p.Guid)
    .WithRequired(p => p.ProjectReferences
        .Where(r => r.ProjectGuid == knownGuid))
    .QueryAsync(cancellationToken);

Query per i riferimenti ai pacchetti

Analogamente, è possibile eseguire query per i riferimenti al pacchetto NuGet.

Esempio di ricerca di tutti i pacchetti a cui fa riferimento il progetto corrente

IAsyncEnumerable<IQueryResultItem<IProjectConfigurationSnapshot>> configurationsWithPackageReferences = myProject
    .ActiveConfigurations
    .With(c => c.Name)
    .With(c => c.PackageReferences
        .With(p => new { p.Name, p.Version }))
    .QueryAsync(cancellationToken);

Esempio di ricerca di tutti i progetti che fanno riferimento a un pacchetto NuGet specifico

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);

Query per i gruppi di output del progetto

Le configurazioni del progetto contengono informazioni sui gruppi di output del progetto.

// 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);

Passaggi successivi

Esaminare il codice per un'estensione che usa l'API Query di progetto in VSProjectQueryAPISample.