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 | Azure DevOps Server | Azure DevOps Server 2022
La creación de elementos de trabajo mediante programación es un escenario de automatización común en Azure DevOps Services. En este artículo se muestra cómo crear un error (o cualquier elemento de trabajo) mediante bibliotecas cliente de .NET con métodos de autenticación modernos.
Prerrequisitos
| Categoría | Requisitos |
|---|---|
| Azure DevOps |
-
Una organización - Acceso a un proyecto en el que puede crear elementos de trabajo |
| Autenticación | Elija alguna de las acciones siguientes: - Autenticación de Microsoft Entra ID (recomendado) - Token de acceso personal (PAT) ( para pruebas) |
| Entorno de desarrollo | Un entorno de desarrollo de C#. Puede usar Visual Studio. |
Importante
En el caso de aplicaciones de producción, se recomienda usar autenticación de Microsoft Entra ID en lugar de tokens de acceso personal. Las PAT son adecuadas para escenarios de prueba y desarrollo. Para obtener instrucciones sobre cómo elegir el método de autenticación correcto, consulte Guía de autenticación.
Opciones de autenticación
En este artículo se muestran varios métodos de autenticación para adaptarse a distintos escenarios:
Autenticación de Id. de Microsoft Entra (recomendado para aplicaciones de usuario)
Para las aplicaciones de producción con interacción de usuario, use la autenticación de 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" />
Autenticación de principal 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.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />
Autenticación de identidad administrada (recomendada para aplicaciones hospedadas en Azure)
Para las aplicaciones que se ejecutan en servicios de Azure (Functions, App Service, etc.):
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />
Autenticación de token de acceso personal
Para escenarios de desarrollo y pruebas:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
Ejemplos de código de C#
En los ejemplos siguientes se muestra cómo crear elementos de trabajo mediante distintos métodos de autenticación.
Ejemplo 1: autenticación de Id. de Entra de Microsoft (interactiva)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.VisualStudio.Services.InteractiveClient
// Microsoft.Identity.Client
using System;
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;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class EntraIdBugCreator
{
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 EntraIdBugCreator(string orgName)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
}
/// <summary>
/// Create a bug using Microsoft Entra ID authentication.
/// </summary>
/// <param name="project">The name of your project</param>
/// <param name="title">Bug title</param>
/// <param name="reproSteps">Reproduction steps</param>
/// <param name="priority">Priority level (1-4)</param>
/// <param name="severity">Severity level</param>
/// <returns>The created WorkItem</returns>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
// Use Microsoft Entra ID authentication
var credentials = new VssAadCredential();
var patchDocument = new JsonPatchDocument();
// Add required and optional fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var result = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{result.Id}");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Ejemplo 2: autenticación de entidad de servicio (escenarios automatizados)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.Identity.Client
using System;
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;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class ServicePrincipalBugCreator
{
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 ServicePrincipalBugCreator(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>
/// Create a bug using Service Principal authentication.
/// </summary>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
// 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 patchDocument = new JsonPatchDocument();
// Add work item fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var workItem = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{workItem.Id}");
return workItem;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Ejemplo 3: Autenticación de identidad administrada (aplicaciones hospedadas en Azure)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Azure.Identity
using System;
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;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class ManagedIdentityBugCreator
{
private readonly Uri uri;
/// <summary>
/// Initializes a new instance using Managed Identity authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
public ManagedIdentityBugCreator(string orgName)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
}
/// <summary>
/// Create a bug using Managed Identity authentication.
/// </summary>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
// 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 patchDocument = new JsonPatchDocument();
// Add work item fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var workItem = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{workItem.Id}");
return workItem;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Ejemplo 4: autenticación de token de acceso personal
// NuGet package: Microsoft.TeamFoundationServer.Client
using System;
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;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class PatBugCreator
{
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 PatBugCreator(string orgName, string personalAccessToken)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
this.personalAccessToken = personalAccessToken;
}
/// <summary>
/// Create a bug using Personal Access Token authentication.
/// </summary>
/// <param name="project">The name of your project</param>
/// <param name="title">Bug title</param>
/// <param name="reproSteps">Reproduction steps</param>
/// <param name="priority">Priority level (1-4)</param>
/// <param name="severity">Severity level</param>
/// <returns>The created WorkItem</returns>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
var credentials = new VssBasicCredential(string.Empty, this.personalAccessToken);
var patchDocument = new JsonPatchDocument();
// Add required and optional fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var result = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{result.Id}");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Ejemplos de uso
Uso de la autenticación de Microsoft Entra ID (interactivo)
class Program
{
static async Task Main(string[] args)
{
var bugCreator = new EntraIdBugCreator("your-organization-name");
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Authorization Errors with Microsoft Accounts",
reproSteps: "Our authorization logic needs to allow for users with Microsoft accounts (formerly Live IDs) - https://docs.microsoft.com/library/live/hh826547.aspx",
priority: 1,
severity: "2 - High"
);
Console.WriteLine($"Created bug with ID: {bug.Id}");
}
}
Uso de la autenticación con una 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 bugCreator = new ServicePrincipalBugCreator("your-organization-name", clientId, clientSecret, tenantId);
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Automated Bug Report",
reproSteps: "Issue detected by automated testing...",
priority: 2,
severity: "3 - Medium"
);
Console.WriteLine($"Automated bug created: #{bug.Id}");
}
}
Uso de la autenticación de identidad administrada (Azure Functions/App Service)
public class BugReportFunction
{
[FunctionName("CreateBugReport")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
ILogger log)
{
var bugCreator = new ManagedIdentityBugCreator("your-organization-name");
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Function-detected Issue",
reproSteps: "Issue reported via Azure Function...",
priority: 3,
severity: "4 - Low"
);
return new OkObjectResult($"Bug created: {bug.Id}");
}
}
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 bugCreator = new PatBugCreator("your-organization-name", pat);
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Sample Bug Title",
reproSteps: "Steps to reproduce the issue...",
priority: 2,
severity: "3 - Medium"
);
Console.WriteLine($"Bug created successfully: #{bug.Id}");
}
}
Referencia del campo del elemento de trabajo
Al crear elementos de trabajo, normalmente usará estos campos:
Campos obligatorios
- System.Title: el título del elemento de trabajo (necesario para todos los tipos de elementos de trabajo)
- System.WorkItemType: se establece automáticamente al especificar el tipo en la llamada API.
Campos opcionales comunes
- Microsoft.VSTS.TCM.ReproSteps: Pasos detallados de reproducción
- Microsoft.VSTS.Common.Priority: nivel de prioridad (1=más alto, 4=más bajo)
- Microsoft.VSTS.Common.Severity: clasificación de gravedad
- System.Description: descripción general o detalles adicionales
- System.AssignedTo: persona responsable del elemento de trabajo
- System.AreaPath: clasificación de área
- System.IterationPath: asignación de iteración/sprint
Valores de prioridad
- 1: Prioridad crítica/más alta
- 2: Prioridad alta
- 3: Prioridad media (valor predeterminado)
- 4: Prioridad baja
Valores de gravedad comunes
- 1 - Crítico: Sistema inutilizable, bloqueando el progreso
- 2 - Alto: Funcionalidad principal interrumpida
- 3 - Medio: algunas fallas en la funcionalidad (valor predeterminado)
- 4 - Bajo: Problemas menores o problemas cosméticos
procedimientos recomendados
Autenticación
- Uso de Microsoft Entra ID para aplicaciones interactivas con inicio de sesión de usuario
- Use Service Principal para escenarios automatizados, canalizaciones de CI/CD y aplicaciones de servidor
- Uso de identidad administrada 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 codifique las credenciales de forma rígida en el código fuente; uso de variables de entorno o Azure Key Vault
- Implementación de la rotación de credenciales para aplicaciones de larga duración
- Asegúrese de que haya ámbitos adecuados: la creación de elementos de trabajo requiere permisos apropiados en Azure DevOps
Control de errores
- Implementación del control de excepciones adecuado para errores de autenticación y API
- Validar valores de campo antes de intentar crear elementos de trabajo
- Manejo de errores de validación de campos devueltos por la API
- Uso de patrones asincrónicos o await para mejorar la capacidad de respuesta de la aplicación
Rendimiento
- Operaciones por lotes al crear varios elementos de trabajo
- Almacenar en caché las conexiones al realizar varias llamadas API
- Usar los valores de tiempo de espera adecuados para las operaciones de larga duración
- Implementación de la lógica de reintento con retroceso exponencial para errores transitorios
Validación de datos
- Validación de campos obligatorios antes de las llamadas API
- Comprobar los permisos de campo y las reglas de tipo de elemento de trabajo
- Sanear la entrada del usuario para evitar ataques por inyección
- Seguir los requisitos de campo específicos del proyecto y las convenciones de nomenclatura
Solución de problemas
Problemas de autenticación.
- Errores de autenticación de Microsoft Entra ID: asegúrese de que el usuario tiene los permisos adecuados para crear elementos de trabajo.
- Errores de autenticación de la entidad de servicio principal: compruebe que el identificador de cliente, el secreto y el identificador de inquilino son correctos; compruebe los permisos de la entidad de servicio principal 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 tiene
vso.work_writeámbito y no ha expirado - 403 Errores prohibidos: comprobar los permisos del proyecto y el acceso al tipo de elemento de trabajo
Errores de validación de campos
- Campo obligatorio que falta: asegúrese de que todos los campos obligatorios se incluyen en el documento de revisión.
- Valores de campo no válidos: compruebe que los valores de campo coinciden con el formato esperado y los valores permitidos
- Campo no encontrado: compruebe que los nombres de campo están escrito correctamente y existen para el tipo de elemento de trabajo
- Errores de campos de solo lectura: algunos campos no se pueden establecer durante la creación (por ejemplo, System.CreatedBy)
Excepciones comunes
- VssUnauthorizedException: error de autenticación o permisos insuficientes
- VssServiceException: errores de validación del lado servidor o problemas de API
- ArgumentException: parámetros no válidos o documento de revisión con formato incorrecto
- JsonReaderException: problemas con la serialización o deserialización JSON
Problemas de rendimiento
- Respuestas de API lentas: comprobación de la conectividad de red y estado del servicio Azure DevOps
- Uso de memoria: gestione correctamente las conexiones y los clientes
- Limitación de velocidad: implementación de retrasos adecuados entre llamadas API