Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Servizi di Azure DevOps
Il recupero di elementi di lavoro tramite query è uno scenario comune in Azure DevOps Services. Questo articolo illustra come implementare questo scenario a livello di codice usando le API REST o le librerie client .NET.
Prerequisiti
Categoria | Requisiti |
---|---|
Azure DevOps |
-
Un'organizzazione - Accesso a un progetto con elementi di lavoro |
Autenticazione | Scegliere una delle seguenti modalità: - Autenticazione CON ID Microsoft Entra (scelta consigliata per le app interattive) - Autenticazione dell'entità servizio (consigliata per l'automazione) - Autenticazione dell'identità gestita (consigliata per le app ospitate in Azure) - Token di accesso personale (per i test) |
Ambiente di sviluppo | Un ambiente di sviluppo C#. È possibile usare Visual Studio |
Importante
Per le applicazioni di produzione, è consigliabile usare l'autenticazione con Microsoft Entra ID anziché i token di accesso personale (PAT). I PT sono adatti per scenari di test e sviluppo. Per indicazioni sulla scelta del metodo di autenticazione corretto, vedere Linee guida per l'autenticazione.
Opzioni di autenticazione
Questo articolo illustra più metodi di autenticazione in base a scenari diversi:
Autenticazione CON ID Microsoft Entra (scelta consigliata per le app interattive)
Per le applicazioni di produzione con l'interazione dell'utente, usare l'autenticazione microsoft Entra ID:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />
Autenticazione del Principal di Servizio (scelta consigliata per l'automazione)
Per scenari automatizzati, pipeline CI/CD e applicazioni server:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />
Autenticazione dell'identità gestita (scelta consigliata per le app ospitate in Azure)
Per le applicazioni in esecuzione nei servizi di Azure (Funzioni, Servizio app e così via):
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />
Autenticazione del token di accesso personale
Per scenari di sviluppo e test:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
Esempi di codice C#
Negli esempi seguenti viene illustrato come recuperare elementi di lavoro usando metodi di autenticazione diversi.
Esempio 1: Autenticazione dell'ID Entra di Microsoft (Interattiva)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.VisualStudio.Services.InteractiveClient
// Microsoft.Identity.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
public class EntraIdQueryExecutor
{
private readonly Uri uri;
/// <summary>
/// Initializes a new instance using Microsoft Entra ID authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
public EntraIdQueryExecutor(string orgName)
{
this.uri = new Uri("https://dev.azure.com/" + orgName);
}
/// <summary>
/// Execute a WIQL query using Microsoft Entra ID authentication.
/// </summary>
/// <param name="project">The name of your project within your organization.</param>
/// <returns>A list of WorkItem objects representing all the open bugs.</returns>
public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
{
// Use Microsoft Entra ID authentication
var credentials = new VssAadCredential();
var wiql = new Wiql()
{
Query = "SELECT [System.Id], [System.Title], [System.State] " +
"FROM WorkItems " +
"WHERE [Work Item Type] = 'Bug' " +
"AND [System.TeamProject] = '" + project + "' " +
"AND [System.State] <> 'Closed' " +
"ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
};
using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
{
try
{
var result = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
var ids = result.WorkItems.Select(item => item.Id).ToArray();
if (ids.Length == 0)
{
return Array.Empty<WorkItem>();
}
var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine($"Error querying work items: {ex.Message}");
return Array.Empty<WorkItem>();
}
}
}
/// <summary>
/// Print the results of the work item query.
/// </summary>
public async Task PrintOpenBugsAsync(string project)
{
var workItems = await this.QueryOpenBugsAsync(project).ConfigureAwait(false);
Console.WriteLine($"Query Results: {workItems.Count} items found");
foreach (var workItem in workItems)
{
Console.WriteLine($"{workItem.Id}\t{workItem.Fields["System.Title"]}\t{workItem.Fields["System.State"]}");
}
}
}
Esempio 2: Autenticazione dell'entità servizio (scenari automatizzati)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.Identity.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
public class ServicePrincipalQueryExecutor
{
private readonly Uri uri;
private readonly string clientId;
private readonly string clientSecret;
private readonly string tenantId;
/// <summary>
/// Initializes a new instance using Service Principal authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
/// <param name="clientId">Service principal client ID</param>
/// <param name="clientSecret">Service principal client secret</param>
/// <param name="tenantId">Azure AD tenant ID</param>
public ServicePrincipalQueryExecutor(string orgName, string clientId, string clientSecret, string tenantId)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenantId = tenantId;
}
/// <summary>
/// Execute a WIQL query using Service Principal authentication.
/// </summary>
public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
{
// Acquire token using Service Principal
var app = ConfidentialClientApplicationBuilder
.Create(this.clientId)
.WithClientSecret(this.clientSecret)
.WithAuthority($"https://login.microsoftonline.com/{this.tenantId}")
.Build();
var scopes = new[] { "https://app.vssps.visualstudio.com/.default" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
var wiql = new Wiql()
{
Query = "SELECT [System.Id], [System.Title], [System.State] " +
"FROM WorkItems " +
"WHERE [Work Item Type] = 'Bug' " +
"AND [System.TeamProject] = '" + project + "' " +
"AND [System.State] <> 'Closed' " +
"ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
};
using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
{
try
{
var queryResult = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();
if (ids.Length == 0)
{
return Array.Empty<WorkItem>();
}
var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine($"Error querying work items: {ex.Message}");
return Array.Empty<WorkItem>();
}
}
}
}
Esempio 3: Autenticazione dell'identità gestita (app ospitate in Azure)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Azure.Identity
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
public class ManagedIdentityQueryExecutor
{
private readonly Uri uri;
/// <summary>
/// Initializes a new instance using Managed Identity authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
public ManagedIdentityQueryExecutor(string orgName)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
}
/// <summary>
/// Execute a WIQL query using Managed Identity authentication.
/// </summary>
public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
{
// Use Managed Identity to acquire token
var credential = new DefaultAzureCredential();
var tokenRequestContext = new TokenRequestContext(new[] { "https://app.vssps.visualstudio.com/.default" });
var tokenResult = await credential.GetTokenAsync(tokenRequestContext);
var credentials = new VssOAuthAccessTokenCredential(tokenResult.Token);
var wiql = new Wiql()
{
Query = "SELECT [System.Id], [System.Title], [System.State] " +
"FROM WorkItems " +
"WHERE [Work Item Type] = 'Bug' " +
"AND [System.TeamProject] = '" + project + "' " +
"AND [System.State] <> 'Closed' " +
"ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
};
using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
{
try
{
var queryResult = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();
if (ids.Length == 0)
{
return Array.Empty<WorkItem>();
}
var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine($"Error querying work items: {ex.Message}");
return Array.Empty<WorkItem>();
}
}
}
}
Esempio 4: Autenticazione del token di accesso personale
// NuGet package: Microsoft.TeamFoundationServer.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
public class PatQueryExecutor
{
private readonly Uri uri;
private readonly string personalAccessToken;
/// <summary>
/// Initializes a new instance using Personal Access Token authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
/// <param name="personalAccessToken">Your Personal Access Token</param>
public PatQueryExecutor(string orgName, string personalAccessToken)
{
this.uri = new Uri("https://dev.azure.com/" + orgName);
this.personalAccessToken = personalAccessToken;
}
/// <summary>
/// Execute a WIQL query using Personal Access Token authentication.
/// </summary>
/// <param name="project">The name of your project within your organization.</param>
/// <returns>A list of WorkItem objects representing all the open bugs.</returns>
public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
{
var credentials = new VssBasicCredential(string.Empty, this.personalAccessToken);
var wiql = new Wiql()
{
Query = "SELECT [System.Id], [System.Title], [System.State] " +
"FROM WorkItems " +
"WHERE [Work Item Type] = 'Bug' " +
"AND [System.TeamProject] = '" + project + "' " +
"AND [System.State] <> 'Closed' " +
"ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
};
using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
{
try
{
var result = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
var ids = result.WorkItems.Select(item => item.Id).ToArray();
if (ids.Length == 0)
{
return Array.Empty<WorkItem>();
}
var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine($"Error querying work items: {ex.Message}");
return Array.Empty<WorkItem>();
}
}
}
}
Esempi di utilizzo
Uso dell'autenticazione di Microsoft Entra ID (Interactive)
class Program
{
static async Task Main(string[] args)
{
var executor = new EntraIdQueryExecutor("your-organization-name");
await executor.PrintOpenBugsAsync("your-project-name");
}
}
Uso dell'autenticazione tramite Service Principal (scenari CI/CD)
class Program
{
static async Task Main(string[] args)
{
// These values should come from environment variables or Azure Key Vault
var clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
var executor = new ServicePrincipalQueryExecutor("your-organization-name", clientId, clientSecret, tenantId);
var workItems = await executor.QueryOpenBugsAsync("your-project-name");
Console.WriteLine($"Found {workItems.Count} open bugs via automation");
foreach (var item in workItems)
{
Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
}
}
}
Uso dell'autenticazione dell'identità gestita (Funzioni di Azure/Servizio app)
public class WorkItemQueryFunction
{
[FunctionName("QueryOpenBugs")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
ILogger log)
{
var executor = new ManagedIdentityQueryExecutor("your-organization-name");
var workItems = await executor.QueryOpenBugsAsync("your-project-name");
return new OkObjectResult(new {
Count = workItems.Count,
Items = workItems.Select(wi => new {
Id = wi.Id,
Title = wi.Fields["System.Title"],
State = wi.Fields["System.State"]
})
});
}
}
Uso dell'autenticazione del token di accesso personale (sviluppo/test)
class Program
{
static async Task Main(string[] args)
{
var pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT"); // Never hardcode PATs
var executor = new PatQueryExecutor("your-organization-name", pat);
var workItems = await executor.QueryOpenBugsAsync("your-project-name");
Console.WriteLine($"Found {workItems.Count} open bugs");
foreach (var item in workItems)
{
Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
}
}
}
Procedure consigliate
Autenticazione
- Usare Microsoft Entra ID per applicazioni interattive con l'accesso utente
- Usare Service Principal per scenari automatizzati, pipeline CI/CD e applicazioni server
- Usare l'identità gestita per le applicazioni in esecuzione nei servizi di Azure (Funzioni, Servizio app, macchine virtuali)
- Evitare token di accesso personali nell'ambiente di produzione; uso solo per lo sviluppo e il test
- Non impostare mai le credenziali hardcoded nel codice sorgente; usare le variabili di ambiente o Azure Key Vault
- Implementare la rotazione delle credenziali per le applicazioni a esecuzione prolungata
- Assicurarsi che gli ambiti siano appropriati: le query degli elementi di lavoro richiedono autorizzazioni di lettura appropriate in Azure DevOps
Gestione degli errori
- Implementare la logica di ripetizione dei tentativi con backoff esponenziale per gli errori temporanei
- Registrare gli errori in modo appropriato per il debug e il monitoraggio
- Gestire eccezioni specifiche , ad esempio errori di autenticazione e timeout di rete
- Usare i token di annullamento per le operazioni a esecuzione prolungata
Prestazioni
- Recupero di elementi di lavoro in batch durante l'esecuzione di query su più elementi
- Limitare i risultati delle query usando la clausola TOP per set di dati di grandi dimensioni
- Memorizzare nella cache i dati a cui si accede di frequente per ridurre le chiamate API
- Usare i campi appropriati per ridurre al minimo il trasferimento dei dati
Ottimizzazione query
- Usare nomi di campo specifici anziché SELECT * per ottenere prestazioni migliori
- Aggiungere clausole WHERE appropriate per filtrare i risultati sul server
- Ordinare i risultati in modo appropriato per il caso d'uso
- Considerare i limiti delle query e la paginazione per set di risultati di grandi dimensioni
Risoluzione dei problemi
Problemi di autenticazione
- Errori di autenticazione di Microsoft Entra ID: assicurarsi che l'utente disponga delle autorizzazioni appropriate e che abbia eseguito l'accesso ad Azure DevOps
- Autenticazione del Service Principal non riuscita: verifica che l'ID client, il secret e l'ID tenant siano corretti; controlla le autorizzazioni del Service Principal in Azure DevOps
- Errori di autenticazione dell'identità gestita: verificare che la risorsa di Azure disponga di un'identità gestita abilitata e delle autorizzazioni appropriate
-
Errori di autenticazione PAT: verificare che il token sia valido e abbia ambiti appropriati (
vso.work
per l'accesso agli elementi di lavoro) - Scadenza del token: controllare se il token di accesso personale è scaduto e generarne uno nuovo, se necessario
Problemi di query
- Sintassi WIQL non valida: verificare che la sintassi del linguaggio di query dell'elemento di lavoro sia corretta
- Errori di nome progetto: verificare che il nome del progetto esista e che sia stato digitato correttamente
-
Errori di nome campo: usare i nomi di campo di sistema corretti (ad esempio,
System.Id
,System.Title
)
Eccezioni comuni
- VssUnauthorizedException: controllare le credenziali di autenticazione e le autorizzazioni
- ArgumentException: verificare che tutti i parametri obbligatori siano specificati e validi
- HttpRequestException: Controllare la connettività di rete e la disponibilità del servizio
Problemi di prestazioni
- Query lente: aggiungere clausole WHERE appropriate e limitare i set di risultati
- Utilizzo della memoria: elaborare set di risultati di grandi dimensioni in batch
- Limitazione della frequenza: implementare la logica di ripetizione dei tentativi con backoff esponenziale