Condividi tramite


Creare un bug in Azure DevOps Services usando le librerie client .NET

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

La creazione di elementi di lavoro a livello di codice è uno scenario di automazione comune in Azure DevOps Services. Questo articolo illustra come creare un bug (o qualsiasi elemento di lavoro) usando librerie client .NET con metodi di autenticazione moderni.

Prerequisiti

Categoria Requisiti
Azure DevOps - Un'organizzazione
- Accesso a un progetto in cui è possibile creare elementi di lavoro
Autenticazione Selezionare una delle opzioni seguenti:
- Autenticazione con ID Microsoft Entra (scelta consigliata)
- Token di accesso personale (PAT) ( per i test)
Ambiente di sviluppo Un ambiente di sviluppo C#. È possibile usare Visual Studio

Importante

Per le applicazioni di produzione, è consigliabile usare l'autenticazione con ID Entra Microsoft anziché i token di accesso personale. I PT sono adatti per scenari di test e sviluppo. Per indicazioni sulla scelta del metodo di autenticazione corretto, vedere Linee guida per l'autenticazione.

Opzioni di autenticazione

Questo articolo illustra più metodi di autenticazione in base a scenari diversi:

Per le applicazioni di produzione con l'interazione dell'utente, usare l'autenticazione 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" />

Per scenari automatizzati, pipeline CI/CD e applicazioni server:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />

Per le applicazioni in esecuzione nei servizi di Azure (Funzioni, Servizio app e così via):

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />

Autenticazione del token di accesso personale

Per scenari di sviluppo e test:

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

Esempi di codice C#

Negli esempi seguenti viene illustrato come creare elementi di lavoro usando metodi di autenticazione diversi.

Esempio 1: Autenticazione dell'ID Entra di Microsoft (Interattiva)

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

Esempio 2: Autenticazione dell'entità servizio (scenari automatizzati)

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

Esempio 3: Autenticazione dell'identità gestita (app ospitate in 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;
            }
        }
    }
}

Esempio 4: Autenticazione del token di accesso personale

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

Esempi di utilizzo

Uso dell'autenticazione di Microsoft Entra ID (Interactive)

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 dell'autenticazione tramite Service Principal (scenari 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 dell'autenticazione dell'identità gestita (Funzioni di Azure/Servizio app)

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 dell'autenticazione del token di accesso personale (sviluppo/test)

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

Informazioni di riferimento sul campo dell'elemento di lavoro

Quando si creano elementi di lavoro, si useranno in genere questi campi:

Campi obbligatori

  • System.Title: titolo dell'elemento di lavoro (obbligatorio per tutti i tipi di elemento di lavoro)
  • System.WorkItemType: impostato automaticamente quando si specifica il tipo nella chiamata API

Campi facoltativi comuni

  • Microsoft.VSTS.TCM.ReproSteps: procedura dettagliata per la riproduzione
  • Microsoft.VSTS.Common.Priority: livello di priorità (1=highest, 4=lowest)
  • Microsoft.VSTS.Common.Severity: Classificazione di gravità
  • System.Description: Descrizione generale o dettagli aggiuntivi
  • System.AssignedTo: persona responsabile dell'elemento di lavoro
  • System.AreaPath: Classificazione dell'area
  • System.IterationPath: L'assegnazione di sprint/iterazione

Valori di priorità

  • 1: Priorità critica/più alta
  • 2: Priorità alta
  • 3: Priorità media (impostazione predefinita)
  • 4: Priorità bassa

Valori di gravità comuni

  • 1 - Critico: Inutilizzabile dal sistema, blocco dello stato di avanzamento
  • 2 - Alto: funzionalità principali interrotte
  • 3 - Medio: Alcune funzionalità non funzionano (impostazione predefinita)
  • 4 - Basso: problemi secondari o problemi cosmetici

Procedure consigliate

Autenticazione

  • Usare Microsoft Entra ID per applicazioni interattive con l'accesso utente
  • Usare il Service Principal per scenari di automazione, pipeline CI/CD e applicazioni server
  • Usare l'identità gestita per le applicazioni in esecuzione nei servizi di Azure (Funzioni, Servizio app, macchine virtuali)
  • Evitare token di accesso personali nell'ambiente di produzione; uso solo per lo sviluppo e il test
  • Non impostare mai le credenziali hardcoded nel codice sorgente; usare le variabili di ambiente o Azure Key Vault
  • Implementare la rotazione delle credenziali per le applicazioni a esecuzione prolungata
  • Assicurarsi degli ambiti appropriati: la creazione di elementi di lavoro richiede autorizzazioni adeguate in Azure DevOps

Gestione degli errori

  • Implementare una gestione corretta delle eccezioni per gli errori di autenticazione e API
  • Convalidare i valori dei campi prima di tentare di creare elementi di lavoro
  • Gestire gli errori di convalida dei campi restituiti dall'API
  • Usare modelli asincroni/await per migliorare la velocità di risposta dell'applicazione

Prestazioni

  • Operazioni batch durante la creazione di più elementi di lavoro
  • Memorizzare nella cache le connessioni quando si effettuano più chiamate API
  • Usare i valori di timeout appropriati per le operazioni con esecuzione prolungata
  • Implementare la logica di ripetizione dei tentativi con backoff esponenziale per gli errori temporanei

Convalida dei dati

  • Convalidare i campi obbligatori prima delle chiamate API
  • Controllare le autorizzazioni dei campi e le regole del tipo di elemento di lavoro
  • Sanitizzare l'input dell'utente per evitare attacchi di iniezione
  • Seguire i requisiti di campo e le convenzioni di denominazione specifiche del progetto

Risoluzione dei problemi

Problemi di autenticazione

  • Errori di autenticazione di Microsoft Entra ID: assicurarsi che l'utente disponga delle autorizzazioni appropriate per creare elementi di lavoro
  • Errori di autenticazione del Principal del servizio: verificare che l'ID client, il segreto e l'ID tenant siano corretti; controllare le autorizzazioni del Principal del servizio in Azure DevOps
  • Errori di autenticazione dell'identità gestita: verificare che la risorsa di Azure disponga di un'identità gestita abilitata e delle autorizzazioni appropriate
  • Errori di autenticazione PAT: verificare che il token abbia vso.work_write ambito e non sia scaduto
  • 403 Errori non consentiti: controllare le autorizzazioni del progetto e l'accesso al tipo di elemento di lavoro

Errori di convalida dei campi

  • Campo obbligatorio mancante: verificare che tutti i campi obbligatori siano inclusi nel documento patch
  • Valori di campo non validi: verificare che i valori dei campi corrispondano al formato previsto e ai valori consentiti
  • Campo non trovato: controllare che i nomi dei campi siano digitati correttamente ed esistano per il tipo di elemento di lavoro
  • Errori nei campi di sola lettura: alcuni campi non possono essere impostati durante la creazione (ad esempio, System.CreatedBy)

Eccezioni comuni

  • VssUnauthorizedException: autenticazione non riuscita o autorizzazioni insufficienti
  • VssServiceException: errori di convalida lato server o problemi dell'API
  • ArgumentException: parametri non validi o documento patch non valido
  • JsonReaderException: problemi relativi alla serializzazione/deserializzazione JSON

Problemi di prestazioni

  • Risposte api lente: controllare la connettività di rete e lo stato del servizio Azure DevOps
  • Utilizzo della memoria: eliminare correttamente connessioni e client
  • Limitazione della frequenza: implementare ritardi appropriati tra le chiamate API

Passaggi successivi