Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Azure DevOps Services
La captura de elementos de trabajo mediante consultas es un escenario común en Azure DevOps Services. En este artículo se explica cómo implementar este escenario mediante programación mediante API REST o bibliotecas cliente de .NET.
Sugerencia
Puede usar la inteligencia artificial para ayudar con esta tarea más adelante en este artículo, o consulte Enable AI assistance with Azure DevOps MCP Server para empezar.
Requisitos previos
| Categoría | Requisitos |
|---|---|
| Azure DevOps |
-
Una organización - Acceso a un proyecto con elementos de trabajo |
| Autenticación | Elija uno de los métodos siguientes: - Microsoft Entra ID autenticación (recomendado para aplicaciones interactivas) - Autenticación de Principal de Servicio (recomendada para la automatización) - Autenticación de identidad administrada (recomendado para aplicaciones hospedadas Azure) - Token de acceso personal (para pruebas) |
| Entorno de desarrollo | Un entorno de desarrollo de C#. Puede usar Visual Studio |
Importante
Considere usar los más seguros tokens de Microsoft Entra en lugar de los tokens de acceso personal. Para obtener más información, consulte Reducir el uso de PAT. Revise las instrucciones de autenticación para elegir el mecanismo de autenticación adecuado para sus necesidades.
Opciones de autenticación
En este artículo se muestran varios métodos de autenticación para adaptarse a distintos escenarios:
autenticación Microsoft Entra ID (recomendada para aplicaciones interactivas)
Para las aplicaciones de producción con interacción con el usuario, use la autenticación de Microsoft Entra ID.
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="19.232.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.2" />
Autenticación de entidad de servicio (recomendada para la automatización)
Para escenarios automatizados, canalizaciones de CI/CD y aplicaciones de servidor:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.2" />
Autenticación de identidad administrada (recomendada para aplicaciones hospedadas Azure)
Para las aplicaciones que se ejecutan en servicios de Azure (Functions, App Service, etc.):
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Azure.Identity" Version="1.13.1" />
Autenticación de token de acceso personal
Para escenarios de desarrollo y pruebas:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
Ejemplos de código de C#
En los ejemplos siguientes se muestra cómo obtener elementos de trabajo mediante distintos métodos de autenticación.
Ejemplo 1: autenticación Microsoft Entra ID (interactiva)
Nota:
La clase VssAadCredential utilizada en este ejemplo requiere el paquete Microsoft.VisualStudio.Services.InteractiveClient y está dirigida al .NET Framework. Para aplicaciones .NET Core/.NET 5+, use el enfoque basado en MSAL que se muestra en Ejemplo 2 (Entidad de servicio) o Ejemplo 3 (Identidad administrada) con VssOAuthAccessTokenCredential.
// 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, project).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"]}");
}
}
}
Ejemplo 2: autenticación de entidad de servicio (escenarios 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">Microsoft Entra 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, project).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>();
}
}
}
}
Ejemplo 3: Autenticación de identidad administrada (aplicaciones hospedadas 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, project).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>();
}
}
}
}
Ejemplo 4: Autenticación de token de acceso personal
// 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, project).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>();
}
}
}
}
Ejemplos de uso
En los ejemplos siguientes se muestra cómo llamar a cada clase de autenticación.
Uso de la autenticación de Microsoft Entra ID (interactiva)
class Program
{
static async Task Main(string[] args)
{
var executor = new EntraIdQueryExecutor("your-organization-name");
await executor.PrintOpenBugsAsync("your-project-name");
}
}
Uso de la autenticación de entidad de servicio (escenarios 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"]}");
}
}
}
Uso de la autenticación de identidad administrada (Azure Functions/App Service)
public class WorkItemQueryFunction
{
private readonly ILogger<WorkItemQueryFunction> _logger;
public WorkItemQueryFunction(ILogger<WorkItemQueryFunction> logger)
{
_logger = logger;
}
[Function("QueryOpenBugs")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req)
{
var executor = new ManagedIdentityQueryExecutor("your-organization-name");
var workItems = await executor.QueryOpenBugsAsync("your-project-name");
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
await response.WriteAsJsonAsync(new {
Count = workItems.Count,
Items = workItems.Select(wi => new {
Id = wi.Id,
Title = wi.Fields["System.Title"],
State = wi.Fields["System.State"]
})
});
return response;
}
}
Uso de la autenticación de token de acceso personal (desarrollo/pruebas)
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"]}");
}
}
}
procedimientos recomendados
Autenticación
- Use Microsoft Entra ID para aplicaciones interactivas con inicio de sesión de usuario
- Uso de entidades de servicio para escenarios automatizados, canalizaciones de CI/CD y aplicaciones de servidor
- Uso de identidades administradas para aplicaciones que se ejecutan en servicios de Azure (Functions, App Service, VM)
- Evitar tokens de acceso personal en producción; usar solo para desarrollo y pruebas
- Nunca codificar credenciales en el código fuente; debes usar variables de entorno o Azure Key Vault
- Implementación de la rotación de credenciales para aplicaciones de larga duración
- Ensure los ámbitos adecuados: las consultas de elementos de trabajo requieren los permisos de lectura adecuados en Azure DevOps
Control de errores
- Implementación de la lógica de reintento con retroceso exponencial para errores transitorios
- Registrar errores adecuadamente para la depuración y la supervisión
- Control de excepciones específicas , como errores de autenticación y tiempos de espera de red
- Uso de tokens de cancelación para operaciones de larga duración
Rendimiento
- Recuperaciones de elementos de trabajo por lotes al consultar varios elementos
- Limitar los resultados de la consulta mediante la cláusula TOP para conjuntos de datos grandes
- Almacenar en caché los datos a los que se accede con frecuencia para reducir las llamadas API
- Uso de campos adecuados para minimizar la transferencia de datos
Optimización de las consultas
- Usar nombres de campo específicos en lugar de SELECT * para mejorar el rendimiento
- Adición de cláusulas WHERE adecuadas para filtrar los resultados en el servidor
- Ordenar los resultados adecuadamente para su caso de uso
- Considere los límites de consulta y la paginación para conjuntos de resultados grandes
Solución de problemas
Problemas de autenticación.
- Microsoft Entra ID errores de autenticación: asegúrese de que el usuario tiene los permisos adecuados y ha iniciado sesión en Azure DevOps
- Errores de autenticación de la entidad de servicio : compruebe que el identificador de cliente, el secreto y el identificador de inquilino son correctos; comprobación de los permisos de entidad de servicio en Azure DevOps
- Errores de autenticación de identidad administrada: asegúrese de que el recurso de Azure tiene habilitada una identidad administrada y los permisos adecuados.
-
Errores de autenticación de PAT : compruebe que el token es válido y tiene ámbitos adecuados (
vso.workpara el acceso a elementos de trabajo) - Expiración del token : compruebe si el PAT ha expirado y genere uno nuevo si es necesario.
Problemas de consultas
- Sintaxis WIQL no válida : asegúrese de que la sintaxis del lenguaje de consulta del elemento de trabajo es correcta
- Errores de nombre de proyecto: compruebe que el nombre del proyecto existe y se ortografía correctamente
- Errores en nombres de campo: use los nombres de campo del sistema correctos (por ejemplo, ,
System.Id)
Excepciones comunes
- VssUnauthorizedException : comprobación de las credenciales y permisos de autenticación
- ArgumentException : compruebe que se proporcionan todos los parámetros necesarios y son válidos.
- HttpRequestException : comprobación de la conectividad de red y la disponibilidad del servicio
Problemas de rendimiento
- Consultas lentas : agregar cláusulas WHERE adecuadas y limitar conjuntos de resultados
- Uso de memoria : procesamiento de grandes conjuntos de resultados en lotes
- Limitación de velocidad : implementación de la lógica de reintento con retroceso exponencial
Uso de IA para consultar elementos de trabajo de forma programática
Si tiene el servidor MCP de Azure DevOps conectado a su agente de IA en modo agente, puede usar indicaciones en lenguaje natural para generar código para consultar elementos de trabajo.
| tarea | Mensaje de ejemplo |
|---|---|
| Generación de código de consulta | Write C# code to query all active bugs assigned to me in Azure DevOps using the .NET client libraries with Microsoft Entra authentication |
| Consulta de API REST | Create a REST API call to fetch work items from Azure DevOps using a WIQL query with a personal access token |
| Ejecución de una consulta guardada | Show me how to use the Azure DevOps .NET client to run a saved query and retrieve work item details including custom fields |
| Exportar a CSV | Build a .NET app that fetches work items from Azure DevOps and exports them to CSV using managed identity authentication |
| Filtrar por ruta de acceso del área | Write C# code to query work items under area path <Contoso\Backend> that were modified in the last 7 days |
| Paginar resultados grandes | Show me how to query Azure DevOps work items in batches of 200 using the .NET client libraries with proper pagination |
Nota:
El modo de agente y el servidor MCP usan lenguaje natural, por lo que puede ajustar estas indicaciones o formular preguntas de seguimiento para refinar los resultados.