Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Serviços de DevOps do Azure
Buscar itens de trabalho usando consultas é um cenário comum nos Serviços de DevOps do Azure. Este artigo explica como implementar esse cenário programaticamente usando APIs REST ou bibliotecas de cliente .NET.
Pré-requisitos
| Categoria | Requerimentos |
|---|---|
| Azure DevOps |
-
Uma organização - Acesso a um projeto com itens de trabalho |
| Autenticação | Escolha um dos métodos seguintes: - Autenticação do Microsoft Entra ID (recomendada para aplicativos interativos) - Autenticação da entidade de serviço ("Service Principal") (recomendada para automação) - Autenticação de Identidade Gerenciada (recomendada para aplicativos hospedados no Azure) - Token de acesso pessoal (para teste) |
| Ambiente de desenvolvimento | Um ambiente de desenvolvimento C#. Você pode usar o Visual Studio |
Importante
Para aplicativos de produção, recomendamos o uso da autenticação Microsoft Entra ID em vez de Tokens de Acesso Pessoal (PATs). Os PATs são adequados para cenários de teste e desenvolvimento. Para obter orientação sobre como escolher o método de autenticação correto, consulte Diretrizes de autenticação.
Opções de autenticação
Este artigo demonstra vários métodos de autenticação para se adequar a diferentes cenários:
Autenticação de ID do Microsoft Entra (recomendada para aplicativos interativos)
Para aplicativos de produção com interação do usuário, use a autenticação 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" />
Autenticação de Service Principal (recomendada para automação)
Para cenários automatizados, pipelines de CI/CD e aplicativos de servidor:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />
Autenticação de identidade gerenciada (recomendada para aplicativos hospedados no Azure)
Para aplicativos executados nos serviços do Azure (Funções, Serviço de Aplicativo, etc.):
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />
Autenticação de Token de Acesso Pessoal
Para cenários de desenvolvimento e teste:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
Exemplos de código C#
Os exemplos a seguir mostram como buscar itens de trabalho usando diferentes métodos de autenticação.
Exemplo 1: Autenticação de ID do Microsoft Entra (Interativo)
// 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"]}");
}
}
}
Exemplo 2: Autenticação do Principal do Serviço (cenários automatizados)
// 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>();
}
}
}
}
Exemplo 3: Autenticação de identidade gerenciada (aplicativos hospedados no 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>();
}
}
}
}
Exemplo 4: Autenticação de Token de Acesso Pessoal
// 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>();
}
}
}
}
Exemplos de utilização
Usando a autenticação interativa do Entra ID do Microsoft
class Program
{
static async Task Main(string[] args)
{
var executor = new EntraIdQueryExecutor("your-organization-name");
await executor.PrintOpenBugsAsync("your-project-name");
}
}
Usando a autenticação do principal de serviço (cenários de 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"]}");
}
}
}
Usando a autenticação de identidade gerida (Azure Functions/App Service)
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"]
})
});
}
}
Usando a autenticação de Token de Acesso Pessoal (Desenvolvimento/Teste)
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"]}");
}
}
}
Melhores práticas
Autenticação
- Usar o Microsoft Entra ID para aplicativos interativos com entrada do usuário
- Use o Principal de Serviço para cenários automatizados, pipelines de CI/CD e aplicações de servidor
- Usar a Identidade Gerenciada para aplicativos executados nos serviços do Azure (Funções, Serviço de Aplicativo, VMs)
- Evite Tokens de Acesso Pessoal em produção; Uso apenas para desenvolvimento e testes
- Nunca codifice credenciais no código-fonte; usar variáveis de ambiente ou o Azure Key Vault
- Implementar rotação de credenciais para aplicativos de longa execução
- Garantir escopos adequados: as consultas de item de trabalho exigem permissões de leitura apropriadas no Azure DevOps
Tratamento de erros
- Implementar lógica de nova tentativa com backoff exponencial para falhas transitórias
- Registrar erros adequadamente para depuração e monitoramento
- Lidar com exceções específicas , como falhas de autenticação e tempos limite de rede
- Use tokens de cancelamento para operações de longa duração
Desempenho
- Recuperações de itens de trabalho em lote ao pesquisar múltiplos itens
- Limitar os resultados da consulta usando a cláusula TOP para grandes conjuntos de dados
- Armazenar em cache dados acessados com frequência para reduzir chamadas de API
- Use campos apropriados para minimizar a transferência de dados
Otimização de consultas
- Use nomes de campos específicos em vez de SELECT * para um melhor desempenho
- Adicione cláusulas WHERE adequadas para filtrar resultados no servidor
- Organize os resultados de forma adequada ao seu caso de uso
- Considere limites de consulta e paginação para grandes conjuntos de resultados
Solução de problemas
Problemas de autenticação
- Falhas de autenticação do Microsoft Entra ID: verifique se o usuário tem as permissões adequadas e está conectado ao Azure DevOps
- Falhas de autenticação da entidade de serviço: verifique se a ID do cliente, o segredo e a ID do locatário estão corretos; verificar as permissões da entidade de serviço no Azure DevOps
- Falhas de autenticação de Identidade Gerenciada: verifique se o recurso do Azure tem uma identidade gerenciada habilitada e permissões adequadas
-
Falhas de autenticação PAT: verifique se o token é válido e tem escopos apropriados (
vso.workpara acesso ao item de trabalho) - Expiração do token: Verifique se a sua PAT expirou e gere uma nova, se necessário
Problemas de consultas
- Sintaxe WIQL inválida: verifique se a sintaxe da Linguagem de Consulta de Item de Trabalho está correta
- Erros de nome do projeto: verifique se o nome do projeto existe e está escrito corretamente
-
Erros de nome de campo: use os nomes de campo corretos do sistema (por exemplo,
System.IdSystem.Title, )
Exceções comuns
- VssUnauthorizedException: Verifique as credenciais e permissões de autenticação
- ArgumentException: Verifique se todos os parâmetros necessários são fornecidos e válidos
- HttpRequestException: Verifique a conectividade de rede e a disponibilidade do serviço
Problemas de desempenho
- Consultas lentas: adicione cláusulas WHERE apropriadas e limite conjuntos de resultados
- Uso de memória: processe grandes conjuntos de resultados em lotes
- Limitação de velocidade: implemente a lógica de tentativas com backoff exponencial