Compartilhar via


Criar um bug no Azure DevOps Services usando bibliotecas de clientes do .NET

Azure DevOps Services | Azure DevOps Server | Azure DevOps Server 2022

Criar itens de trabalho programaticamente é um cenário de automação comum no Azure DevOps Services. Este artigo mostra como criar um bug (ou qualquer item de trabalho) usando bibliotecas de cliente .NET com métodos de autenticação modernos.

Pré-requisitos

Categoria Requisitos
Azure DevOps - Uma organização
– Acesso a um projeto em que você pode criar itens de trabalho
Autenticação Escolha uma destas opções:
- Autenticação da ID do Microsoft Entra (recomendado)
- PAT (Token de Acesso Pessoal) (para teste)
ambiente de desenvolvimento Um ambiente de desenvolvimento em C#. Você pode usar o Visual Studio

Importante

Para aplicativos de produção, recomendamos usar Microsoft Entra ID em vez de Tokens de Acesso Pessoal. Os PATs são adequados para cenários de teste e desenvolvimento. Para obter diretrizes sobre como escolher o método de autenticação correto, consulte as diretrizes de autenticação.

Opções de autenticação

Este artigo demonstra vários métodos de autenticação para atender a cenários diferentes:

Para aplicativos de produção com interação do usuário, use a autenticação da ID do Microsoft Entra:

<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" />

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" />

Para aplicativos em execução 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 criar itens de trabalho usando métodos de autenticação diferentes.

Exemplo 1: Autenticação da ID do Microsoft Entra (Interativa)

// 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;
            }
        }
    }
}

Exemplo 2: Autenticação do Principal de Serviço (cenários 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;
            }
        }
    }
}

Exemplo 3: autenticação de identidade gerenciada (aplicativos hospedados no 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;
            }
        }
    }
}

Exemplo 4: Autenticação de Token de Acesso Pessoal

// 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;
            }
        }
    }
}

Exemplos de uso

Usando a Microsoft Entra ID para autenticação (Interativa)

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}");
    }
}

Usando a autenticação de Service Principal (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 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}");
    }
}

Usando autenticação de Identidade Gerenciada (Azure Functions/Serviço de Aplicativo)

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}");
    }
}

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 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}");
    }
}

Referência de campo de item de trabalho

Ao criar itens de trabalho, você normalmente usará estes campos:

Campos obrigatórios

  • System.Title: O título do item de trabalho (necessário para todos os tipos de item de trabalho)
  • System.WorkItemType: definir automaticamente ao especificar o tipo na chamada à API

Campos opcionais comuns

  • Microsoft.VSTS.TCM.ReproSteps: Etapas detalhadas de reprodução
  • Microsoft.VSTS.Common.Priority: Nível de prioridade (1=mais alto, 4=menor)
  • Microsoft.VSTS.Common.Severity: classificação de severidade
  • System.Description: descrição geral ou detalhes adicionais
  • System.AssignedTo: Pessoa responsável pelo item de trabalho
  • System.AreaPath: Classificação de área
  • System.IterationPath: Atribuição de iteração/sprint

Valores de prioridade

  • 1: Prioridade crítica/mais alta
  • 2: Alta prioridade
  • 3: Prioridade média (padrão)
  • 4: Baixa prioridade

Valores comuns de severidade

  • 1 – Crítico: Sistema inutilizável, bloqueando o progresso
  • 2 – Alta: Função principal quebrada
  • 3 – Médio: algumas funcionalidades interrompidas (padrão)
  • 4 - Baixo: Problemas menores ou problemas cosméticos

Práticas recomendadas

Autenticação

  • Usar a ID do Microsoft Entra para aplicativos interativos com entrada do usuário
  • Use o Service Principal para cenários automatizados, pipelines de CI/CD e aplicações de servidor
  • Usar a Identidade Gerenciada para aplicativos em execução nos serviços do Azure (Funções, Serviço de Aplicativo, VMs)
  • Evite tokens de acesso pessoal em produção; usar somente para desenvolvimento e teste
  • Nunca insira credenciais diretamente no código-fonte. Use variáveis de ambiente ou o Azure Key Vault.
  • Implementar a rotação de credenciais para aplicativos de execução prolongada
  • Garantir escopos adequados: a criação de item de trabalho requer permissões apropriadas no Azure DevOps

Tratamento de erros

  • Implementar o tratamento de exceção adequado para autenticação e falhas de API
  • Validar valores de campo antes de tentar criar itens de trabalho
  • Manipular erros de validação de campo retornados pela API
  • Usar padrões assíncronos/de espera para melhorar a capacidade de resposta do aplicativo

Desempenho

  • Operações em lote ao criar vários itens de trabalho
  • Conexões de cache ao fazer várias chamadas à API
  • Usar valores de tempo limite apropriados para operações de execução prolongada
  • Implementar lógica de repetição com retrocesso exponencial para falhas transitórias

Validação de dados

  • Validar os campos necessários antes das chamadas à API
  • Verifique as permissões de campo e as regras de tipo de item de trabalho
  • Sanitizar a entrada do usuário para evitar ataques de injeção
  • Siga os requisitos de campo específicos do projeto e as convenções de nomenclatura

Resolução de problemas

Problemas de autenticação

  • Falhas de autenticação da ID do Microsoft Entra: verifique se o usuário tem permissões adequadas para criar itens de trabalho
  • Falhas de autenticação da Entidade de Serviço: verificar se a ID do cliente, o segredo e a ID do locatário estão corretos; verificar 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 de PAT: Verifique se o token tem vso.work_write escopo e não expirou
  • Erro 403 - Proibido: verificar permissões do projeto e acesso ao tipo de item de trabalho

Erros de validação de campo

  • Campo necessário ausente: verifique se todos os campos necessários estão incluídos no documento de patch
  • Valores de campo inválidos: verificar se os valores de campo correspondem ao formato esperado e aos valores permitidos
  • Campo não encontrado: verifique se os nomes de campo são escritos corretamente e existem para o tipo de item de trabalho
  • Erros de campo somente leitura: alguns campos não podem ser definidos na criação (por exemplo, System.CreatedBy)

Exceções comuns

  • VssUnauthorizedException: autenticação com falha ou permissões insuficientes
  • VssServiceException: erros de validação do lado do servidor ou problemas de API
  • ArgumentException: parâmetros inválidos ou documento de patch malformado
  • JsonReaderException: Problemas com serialização/desserialização JSON

Problemas de desempenho

  • Respostas lentas da API: verificar a conectividade de rede e o status do serviço do Azure DevOps
  • Uso de memória: descarte conexões e clientes corretamente
  • Limitação de taxa: implementar atrasos apropriados entre chamadas à API

Próximas etapas