Partilhar via


Criar um bug nos Serviços Azure DevOps usando bibliotecas cliente .NET

Azure DevOps Serviços | Azure DevOps Server | Azure DevOps Server 2022

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

Sugestão

Pode usar IA para ajudar nesta tarefa mais adiante neste artigo, ou consultar Enable AI assistance with Azure DevOps MCP Server para começar.

Pré-requisitos

Categoria Requerimentos
Azure DevOps Uma organização
- Acesso a um projeto onde você pode criar itens de trabalho
Autenticação Escolha uma das seguintes opções:
- Autenticação do Microsoft Entra ID (recomendado)
Token de Acesso Pessoal (PAT) (para teste)
Ambiente de desenvolvimento Um ambiente de desenvolvimento C#. Podes usar Visual Studio

Importante

Considere usar tokens mais seguros Microsoft Entra em vez de tokens de acesso pessoal de maior risco. Para mais informações, consulte Reduzir o uso do PAT. Consulte as orientações de autenticação para escolher o mecanismo de autenticação adequado às suas necessidades.

Opções de autenticação

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

Para aplicações de produção com interação do utilizador, utilize a autenticação 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" />

Para cenários automatizados, pipelines de CI/CD e aplicativos de servidor:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.2" />

Para aplicações a correr em serviços Azure (Functions, App Service, etc.):

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Azure.Identity" Version="1.13.1" />

Autenticação de Token de Acesso Pessoal

Para cenários de desenvolvimento e teste:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />

Exemplos de código C#

Os exemplos a seguir mostram como criar itens de trabalho usando diferentes métodos de autenticação.

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

Observação

A classe VssAadCredential usada neste exemplo requer o pacote Microsoft.VisualStudio.Services.InteractiveClient e tem como alvo .NET Framework. Para aplicações .NET Core/.NET 5+, utilize a abordagem baseada em MSAL apresentada no Exemplo 2 (Principal de Serviço) ou no Exemplo 3 (Identidade Gerida) com VssOAuthAccessTokenCredential.

// 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">Microsoft Entra 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 por Identidade Gerida (aplicações alojadas 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 utilização

Utilizar a autenticação Interativa do Microsoft Entra ID

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://learn.microsoft.com/entra/identity-platform/",
            priority: 1,
            severity: "2 - High"
        );
        
        Console.WriteLine($"Created bug with ID: {bug.Id}");
    }
}

Usando a autenticação da entidade 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 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}");
    }
}

Utilização de autenticação por Identidade Gerida (Funções do Azure/App Service)

public class BugReportFunction
{
    private readonly ILogger<BugReportFunction> _logger;

    public BugReportFunction(ILogger<BugReportFunction> logger)
    {
        _logger = logger;
    }

    [Function("CreateBugReport")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
    {
        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"
        );

        var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
        await response.WriteStringAsync($"Bug created: {bug.Id}");
        return response;
    }
}

Utilizar autenticação por 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 tarefa

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: Definido automaticamente ao especificar o tipo na chamada de 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=mais baixo)
  • Microsoft.VSTS.Common.Severity: Classificação de gravidade
  • 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/máxima
  • 2: Alta prioridade
  • 3: Prioridade média (padrão)
  • 4: Baixa prioridade

Valores comuns de gravidade

  • 1 - Crítico: Sistema inutilizável, bloqueando o progresso
  • 2 - Alta: Funcionalidade principal quebrada
  • 3 - Médio: Algumas funcionalidades quebradas (padrão)
  • 4 - Baixo: Questões menores ou problemas estéticos

Melhores práticas

Autenticação

  • Use Microsoft Entra ID para aplicações interativas com login do utilizador
  • Use o Principal de Serviço para cenários automatizados, pipelines de CI/CD e aplicações de servidor
  • Use Identidade Gerida para aplicações a correr em serviços "Azure" (Funções, Serviço de Aplicações, Máquinas Virtuais).
  • Evite Tokens de Acesso Pessoal em produção; Uso apenas para desenvolvimento e testes
  • Nunca codificar credenciais fixas no código-fonte; use variáveis de ambiente ou Azure Key Vault
  • Implementar rotação de credenciais para aplicativos de longa execução
  • Garantir os escopos adequados: A criação de itens de trabalho requer permissões apropriadas no Azure DevOps

Tratamento de erros

  • Implementar o tratamento adequado de exceções para falhas de autenticação e API
  • Validar valores de campo antes de tentar criar itens de trabalho
  • Manipular erros de validação de campo retornados pela API
  • Use padrões async/await 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 de API
  • Use valores de tempo limite apropriados para operações de longa duração
  • Implementar lógica de nova tentativa com backoff exponencial para falhas transitórias

Validação de dados

  • Validar campos obrigatórios antes de chamadas de API
  • Verificar permissões de campo e regras de tipo de item de trabalho
  • Sanear a entrada do utilizador para evitar ataques de injeção
  • Siga os requisitos de campo específicos do projeto e as convenções de nomenclatura

Solução de problemas

Problemas de autenticação

  • Microsoft Entra ID falhas de autenticação: Garantir que o utilizador tem as permissões adequadas para criar itens de trabalho
  • Falhas de autenticação do Principal de Serviço: Verificar que o ID do cliente, o segredo e o ID do tenant estão corretos; verificar permissões do principal de serviço no Azure DevOps
  • Falhas de autenticação de identidade gerida: Garantir que o recurso Azure tem uma identidade gerida ativada e as permissões adequadas
  • Falhas de autenticação PAT: verifique se o token tem escopo e não expirou
  • 403 Erros proibidos: Verifique as permissões do projeto e o acesso ao tipo de item de trabalho

Erros de validação de campo

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

Exceções comuns

  • VssUnauthorizedException: Falha na autenticação 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: Verifique a conectividade da rede e o estado do serviço Azure DevOps
  • Uso de memória: descarte conexões e clientes corretamente
  • Rate limiting: implemente os atrasos apropriados entre chamadas de API

Usa IA para criar itens de trabalho programaticamente

Se tiveres o Azure DevOps MCP Server ligado ao teu agente de IA em modo agente, podes usar prompts em linguagem natural para gerar código para criar itens de trabalho.

Tarefa Exemplo de prompt
Gerar código de criação de bugs Write a C# console app that creates a bug in Azure DevOps project <Contoso> using Microsoft Entra ID authentication and the .NET client libraries
Criar com campos personalizados Write code to create a work item with priority, severity, and repro steps fields in Azure DevOps using a managed identity
Criar itens de trabalho em lote Show me how to create multiple bugs in Azure DevOps from a CSV file using the .NET client libraries with service principal authentication
Criar a partir de Azure Function Generate an Azure Function that creates bugs in Azure DevOps project <Contoso> using a system-assigned managed identity
Adicionar anexos Write C# code to create a bug in Azure DevOps and attach a log file using the .NET client libraries
Criar itens de trabalho ligados Write code to create a bug in Azure DevOps and link it to an existing user story using the .NET client libraries

Observação

O modo Agente e o Servidor MCP usam linguagem natural, por isso pode ajustar estes prompts ou fazer perguntas de seguimento para refinar os resultados.

Próximos passos

  • Obter itens de trabalho com consultas
  • Saiba mais sobre as opções de autenticação
  • Explore mais exemplos de integração
  • Noções básicas sobre campos de item de trabalho