Wykonywanie zapytań względem interfejsu API projektu
Interfejs API zapytań projektu VisualStudio.Extensibility umożliwia wykonywanie zapytań dotyczących informacji z systemu projektu. Systemy projektów są częścią składników programu Visual Studio, które ułatwiają użytkownikom pracę z projektami i konserwowanie ich, uruchamianie kompilacji w celu generowania wyników i testowania danych wyjściowych.
Celem interfejsu API zapytań projektu jest:
- Praca z systemami projektów
- Pobieranie danych z projektów
- Wprowadzanie zmian w projektach
Niektóre przykłady obejmują opis plików zawartych w projekcie, pakiety NuGet, do których odwołuje się projekt, dodawanie nowych plików do projektu lub zmienianie właściwości projektu.
Więcej informacji na temat systemów projektów można znaleźć tutaj. Znajdź dokumentację koncepcyjną dotyczącą tego, czym jest system projektu, użycie i jego różne terminy tutaj.
Praca z interfejsem API zapytań projektu
To omówienie obejmuje najważniejsze scenariusze pracy z interfejsem API zapytań projektu:
- Uzyskiwanie dostępu do obszaru zapytania projektu
- Wykonywanie zapytań dotyczących systemu projektu dla projektu
- Określanie parametrów projektu, które mają zostać uwzględnione w wyniku zapytania
- Filtrowanie wyniku zapytania
- Użyj zagnieżdżonych zapytań, aby określić żądane właściwości
- Pobieranie kolekcji podrzędnej przy użyciu metody Get
- Wykonywanie zapytań o dodatkowe informacje z wcześniej zwróconego elementu
- Modyfikowanie projektu
- Zapytanie o właściwości projektu
- Wykonywanie zapytań o rozwiązania
- Wykonywanie zapytań dotyczących folderów rozwiązań
- Wyliczanie plików źródłowych z dodatkowymi informacjami w projekcie
- Wykonywanie zapytań dotyczących projektów, które są właścicielem określonego pliku źródłowego
- Wykonywanie zapytań o konfiguracje projektu i ich właściwości
- Wykonywanie zapytań dotyczących odwołań do projektu
- Zapytanie dotyczące odwołań do pakietu
- Wykonywanie zapytań dotyczących grup danych wyjściowych projektu
Uzyskiwanie dostępu do obszaru zapytania projektu
Przed wykonaniem zapytania względem systemu projektu należy uzyskać wystąpienie obiektu obszaru zapytań projektu, które ma kilka metod asynchronicznych, które wysyłają zapytania lub aktualizują system projektu. Termin obszar zapytania projektu i termin obszar roboczy oznaczają to samo, obiekt, który zapewnia dostęp do wszystkich danych projektu.
Dostęp do obszaru zapytań projektu w rozszerzeniu poza procesem
Jeśli tworzysz rozszerzenie poza procesem, użyj następującego kodu:
WorkspacesExtensibility workSpace = this.Extensibility.Workspaces();
Dostęp do obszaru zapytań projektu w rozszerzeniu procesu
Jeśli tworzysz rozszerzenie w procesie, zamiast tego uzyskujesz dostęp do przestrzeni zapytań projektu, jak pokazano w poniższym przykładzie kodu. Jeśli nie utworzono specjalnie rozszerzenia w procesie, użyj fragmentu kodu w poprzedniej sekcji, aby uzyskać wystąpienie obiektu obszaru zapytania projektu.
W poniższym fragcie package
kodu reprezentuje wystąpienie pakietu AsyncPackage, klasę używaną w tworzeniu rozszerzeń programu Visual Studio. Metoda GetServiceAsync
jest stosowana do asynchronicznego pozyskiwania usługi zapytań z kontenera usługi programu Visual Studio.
IProjectSystemQueryService queryService = await package.GetServiceAsync<IProjectSystemQueryService, IProjectSystemQueryService>();
ProjectQueryableSpace workSpace = queryService.QueryableSpace;
Wykonywanie zapytań dotyczących systemu projektu dla projektu
Obiekt WorkspacesExtensibility
umożliwia wykonywanie zapytań dotyczących pojedynczego projektu, jeśli masz identyfikator GUID projektu. Zazwyczaj istnieją dwa identyfikatory GUID skojarzone z projektem, jeden reprezentujący typ projektu, a drugi, który jednoznacznie reprezentuje projekt. Unikatowy identyfikator GUID projektu można znaleźć w pliku rozwiązania lub w rozszerzeniu, które można wykonać w zapytaniu o Guid
właściwość, jak pokazano w następnej sekcji.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projectList = workspace
.ProjectsByProjectGuid(knownGuid)
.QueryAsync(cancellationToken);
Określanie parametrów projektu, które mają zostać uwzględnione w wyniku zapytania
Podczas wykonywania zapytań dotyczących systemu projektu można użyć With
klauzul w celu kontrolowania parametrów lub metadanych uwzględnionych w wynikach zapytania. Istnieje kilka prawidłowych sposobów określania, które parametry powinny być uwzględnione.
Przykład użycia oddzielnej With
klauzuli dla każdego parametru
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;
}
Przykład użycia pojedynczej With
klauzuli w celu określenia wielu parametrów
Można również określić wiele żądanych parametrów w jednej With
klauzuli.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
.Projects
.With(p => new { p.Path, p.Guid, p.Capabilities })
.QueryAsync(cancellationToken);
Przykład użycia klauzuli WithRequired
W przypadku korzystania z programu WithRequired
zwracane są tylko projekty z wymaganymi właściwościami.
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);
Przykład, gdy nie określono żadnych właściwości
Jeśli nie określono żadnych właściwości, zwracany jest domyślny zestaw właściwości.
IAsyncEnumerable<IQueryResultItem<IPropertySnapshot>> properties = myproject
.PropertiesByName("RootNamespace", "AssemblyVersion")
.QueryAsync(cancellationToken);
Filtrowanie wyniku zapytania
Aby ograniczyć wyniki zapytania, istnieją dwa sposoby stosowania filtrowania warunkowego: Where
instrukcje i metody zapytań z wbudowanym filtrowaniem.
Przykład użycia klauzuli Where
Różne typy projektów obsługują różne zestawy możliwości. Za pomocą klauzuli Where
można filtrować projekty, które obsługują określone możliwości. Zapytania mogą zakończyć się niepowodzeniem, jeśli nie filtrujesz projektów, które obsługują odpowiednie możliwości.
Poniższy kod zwraca wartości Path
i Guid
wszystkich projektów internetowych platformy .NET Core w obszarze roboczym:
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
.Projects
.Where(p => p.Capabilities.Contains("DotNetCoreWeb"))
.With(p => new { p.Path, p.Guid })
.QueryAsync(cancellationToken);
Przykład użycia wbudowanego filtrowania
Możesz również użyć metod zapytań, takich jak ProjectsByCapabilities
filtrowanie wbudowane w zapytanie.
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
.ProjectsByCapabilities("DotNetCoreWeb | DotNetCoreRazor")
.With(p => new { p.Path, p.Guid })
.QueryAsync(cancellationToken);
Użyj zagnieżdżonych zapytań, aby określić żądane właściwości
Niektóre parametry są kolekcjami i można użyć zapytań zagnieżdżonych do podobnej specyfikacji i filtrowania dla tych kolekcji podrzędnych.
Przykład
W poniższym przykładzie zagnieżdżone zapytanie umożliwia filtrowanie i określanie kolekcji plików, które mają zostać dołączone do każdego projektu zwróconego przez zapytanie zewnętrzne.
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;
}
}
Pobieranie kolekcji podrzędnej przy użyciu metody Get
Model projektu programu Visual Studio zawiera kolekcje projektów i kolekcji podrzędnych, takich jak pliki lub możliwości projektu w projektach. Aby pobrać samą kolekcję podrzędną, możesz użyć klauzuli Get
. Podobnie jak w przypadku innych typów zapytań, klauzula Get
umożliwia używanie innych klauzul, takich jak klauzula With
do kształtowania lub ograniczania wyników.
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;
}
Wykonywanie zapytań o dodatkowe informacje z wcześniej zwróconego elementu
Możesz użyć wyników z poprzedniego zapytania jako podstawy dla dodatkowych zapytań.
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);
}
Modyfikowanie projektu
Wyniki zapytania są zwykle niezmienne. Interfejs API zapytań umożliwia również wprowadzanie zmian przy użyciu klauzuli w AsUpdatable
celu uzyskania dostępu do modyfikowalnych wersji wyników zapytania, dzięki czemu można wprowadzać zmiany w projektach i elementach projektu.
Przykład dodawania pliku do projektu w wyniku zapytania
IQueryResult<IProjectSnapshot> updatedProjects = workSpace
.ProjectsByProjectGuid(knownGuid)
.AsUpdatable()
.CreateFile("AdditionalInformation.txt", textContent)
.ExecuteAsync(cancellationToken);
Przykład dodawania pliku do wcześniej zwróconego projektu
IQueryResult<IProjectSnapshot> updatedProjects = myproject
.AsUpdatable()
.CreateFile("AdditionalInformation2.txt", textContent)
.ExecuteAsync(cancellationToken);
Zapytanie o właściwości projektu
Klauzulę Get
można użyć do wykonywania zapytań dotyczących właściwości projektu. Poniższe zapytanie zwraca kolekcję IPropertySnapshot
zawierającą wpisy dla dwóch żądanych właściwości. IPropertySnapshot
zawiera nazwę właściwości, nazwę wyświetlaną i wartość w danym momencie.
// 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);
Wykonywanie zapytań o rozwiązania
Oprócz pracy z projektami, jak pokazano wcześniej, można użyć podobnych technik do pracy z rozwiązaniami.
IAsyncEnumerable<IQueryResultItem<ISolutionSnapshot>> solutions = workSpace
.Solutions
.With(s => new { s.Path, s.Guid, s.ActiveConfiguration, s.ActivePlatform })
.QueryAsync(cancellationToken);
Wykonywanie zapytań dotyczących folderów rozwiązań
Podobnie można użyć klauzuli Get
do wykonywania zapytań dotyczących folderów rozwiązań. Właściwość IsNested
umożliwia dołączanie lub wykluczanie folderów zagnieżdżonych z wyników. Eksplorator rozwiązań mogą mieć zagnieżdżone foldery, takie jak ustawienie konfiguracji lub zasoby.
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);
W tym miejscu uzyskujemy wszystkie zagnieżdżone foldery rozwiązań, projekty, pliki wewnątrz folderu rozwiązania (nie są rekursywnie zagnieżdżone):
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);
W tym miejscu otrzymujemy wszystkie cyklicznie zagnieżdżone foldery rozwiązań. Jest VisualPath
to ścieżka wyświetlana w Eksplorator rozwiązań.
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);
Wyliczanie plików źródłowych z dodatkowymi informacjami w projekcie
Oto przykład wyliczania wszystkich plików xaml w projekcie i jego generatorze kodu:
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);
Innym przykładem jest rozpoczęcie od projektu zwróconego z poprzedniego zapytania:
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);
Możesz też pobrać wszystkie pliki zawartości, które nie są skompilowane, które są wymagane w czasie wykonywania, takie jak pliki HTML i CSS.
IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files =
myProject.FilesWithItemTypes("Content")
.With(file => file.Path)
.QueryAsync(cancellationToken);
Lub wyliczyć wszystkie pliki z określonym rozszerzeniem, takie jak pliki schematu XML (.xsd
pliki) we wszystkich projektach:
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);
}
Wykonywanie zapytań dotyczących projektów, które są właścicielem określonego pliku źródłowego
Projekty i foldery zawierają informacje o plikach, które są ich właścicielami lub zawierają, dzięki czemu można użyć WithRequired
klauzuli do wykonywania zapytań dotyczących projektów zawierających określone pliki.
Przykład znajdowania projektów, które są właścicielem danego pliku
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
.Projects
.WithRequired(proj => proj.FilesByPath(myFilePath))
.With(proj => proj.Guid)
.QueryAsync(cancellationToken);
Przykład znajdowania folderów rozwiązań zawierających dany plik
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);
Wykonywanie zapytań o konfiguracje projektu i ich właściwości
Projekty mają ConfigurationDimension
właściwość , której można użyć do znajdowania informacji o konfiguracji projektu. Informacje o konfiguracji projektu odnoszą się do konfiguracji kompilacji projektu (na przykład Debug
i 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)
{
// ...
}
}
Wykonywanie zapytań dotyczących odwołań do projektu
Możesz również wykonać zapytanie w celu znalezienia projektów odwołujących się do danego projektu.
Przykład znajdowania wszystkich projektów, do których odwołuje się bieżący projekt
IAsyncEnumerable<IQueryResultItem<IProjectReferenceSnapshot>> projectReferences = myProject
.ProjectReferences
.With(r => r.ProjectGuid)
.With(r => r.ReferencedProjectId)
.QueryAsync(cancellationToken);
Przykład znajdowania wszystkich projektów odwołującej się do bieżącego projektu
IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workSpace
.Projects
.With(p => p.Guid)
.WithRequired(p => p.ProjectReferences
.Where(r => r.ProjectGuid == knownGuid))
.QueryAsync(cancellationToken);
Zapytanie dotyczące odwołań do pakietu
Podobnie możesz wykonywać zapytania dotyczące odwołań pakietów NuGet.
Przykład znajdowania wszystkich pakietów, do których odwołuje się bieżący projekt
IAsyncEnumerable<IQueryResultItem<IProjectConfigurationSnapshot>> configurationsWithPackageReferences = myProject
.ActiveConfigurations
.With(c => c.Name)
.With(c => c.PackageReferences
.With(p => new { p.Name, p.Version }))
.QueryAsync(cancellationToken);
Przykład znajdowania wszystkich projektów odwołującej się do określonego pakietu NuGet
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);
Wykonywanie zapytań dotyczących grup danych wyjściowych projektu
Konfiguracje projektu zawierają informacje o grupach danych wyjściowych projektu.
// 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);
Następne kroki
Przejrzyj kod rozszerzenia korzystającego z interfejsu API zapytań projektu w artykule VSProjectQueryAPISample.