Freigeben über


Erstellen eines Fehlers in Azure DevOps Services mithilfe von .NET-Clientbibliotheken

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

Das programmgesteuerte Erstellen von Arbeitsaufgaben ist ein gängiges Automatisierungsszenario in Azure DevOps Services. In diesem Artikel wird gezeigt, wie Sie einen Fehler (oder eine beliebige Arbeitsaufgabe) mithilfe von .NET-Clientbibliotheken mit modernen Authentifizierungsmethoden erstellen.

Voraussetzungen

Kategorie Anforderungen
Azure DevOps - Eine Organisation
– Zugriff auf ein Projekt, in dem Sie Arbeitsaufgaben erstellen können
Authentifizierung Wählen Sie eine der folgenden Optionen aus:
- Microsoft Entra ID-Authentifizierung (empfohlen)
- Persönliches Zugriffstoken (PAT) ( zu Testzwecken)
Entwicklungsumgebung Eine C#-Entwicklungsumgebung. Sie können Visual Studio verwenden

Von Bedeutung

Für Produktionsanwendungen empfehlen wir die Verwendung der Microsoft Entra-ID-Authentifizierung anstelle von Persönlichen Zugriffstoken. PATs eignen sich für Test- und Entwicklungsszenarien. Anleitungen zum Auswählen der richtigen Authentifizierungsmethode finden Sie unter Authentifizierungsleitfaden.

Authentifizierungsoptionen

In diesem Artikel werden mehrere Authentifizierungsmethoden für verschiedene Szenarien veranschaulicht:

Verwenden Sie für Produktionsanwendungen mit Benutzerinteraktion die Microsoft Entra ID-Authentifizierung:

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

Für automatisierte Szenarien, CI/CD-Pipelines und Serveranwendungen:

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

Für Anwendungen, die auf Azure-Diensten ausgeführt werden (Funktionen, App Service usw.):

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

Authentifizierung des persönlichen Zugriffstokens

Für Entwicklungs- und Testszenarien:

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

C#-Codebeispiele

Die folgenden Beispiele zeigen, wie Arbeitsaufgaben mit unterschiedlichen Authentifizierungsmethoden erstellt werden.

Beispiel 1: Microsoft Entra ID-Authentifizierung (Interaktiv)

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

Beispiel 2: Dienstprinzipalauthentifizierung (automatisierte Szenarien)

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

Beispiel 3: Verwaltete Identitätsauthentifizierung (von Azure gehostete Apps)

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

Beispiel 4: Authentifizierung des persönlichen Zugriffstokens

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

Verwendungsbeispiele

Verwenden der Microsoft Entra ID-Authentifizierung (Interaktiv)

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

Verwendung der Dienstprinzipal-Authentifizierung (CI/CD-Szenarien)

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

Verwenden der verwalteten Identitätsauthentifizierung (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}");
    }
}

Verwendung der Authentifizierung mit persönlichem Zugriffstoken (Entwicklung/Tests)

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

Arbeitselement Feldreferenz

Beim Erstellen von Arbeitsaufgaben verwenden Sie häufig die folgenden Felder:

Pflichtfelder

  • System.Title: Der Arbeitsaufgabentitel (erforderlich für alle Arbeitsaufgabentypen)
  • System.WorkItemType: Beim Angeben des Typs im API-Aufruf automatisch festgelegt

Allgemeine optionale Felder

  • Microsoft.VSTS.TCM.ReproSteps: Detaillierte Wiedergabeschritte
  • Microsoft.VSTS.Common.Priority: Prioritätsebene (1=höchste, 4=niedrigste)
  • Microsoft.VSTS.Common.Severity: Schweregradklassifizierung
  • System.Description: Allgemeine Beschreibung oder zusätzliche Details
  • System.AssignedTo: Person, die für die Arbeitsaufgabe zuständig ist
  • System.AreaPath: Bereichsklassifizierung
  • System.IterationPath: Iterations-/Sprintzuweisung

Prioritätswerte

  • 1: Kritische/höchste Priorität
  • 2: Hohe Priorität
  • 3: Mittlere Priorität (Standard)
  • 4: Niedrige Priorität

Allgemeine Schweregradwerte

  • 1 – Kritisch: Nicht verwendbares System, Blockieren des Fortschritts
  • 2 – Hoch: Wesentliche Funktionalität gestört
  • 3 - Mittel: Einige Funktionen sind fehlerhaft (Standard)
  • 4 - Niedrig: Kleinere Probleme oder kosmetische Probleme

Bewährte Methoden

Authentifizierung

  • Verwenden der Microsoft Entra-ID für interaktive Anwendungen mit Benutzeranmeldung
  • Verwenden Sie den Dienstprinzipal für automatisierte Szenarien, CI/CD-Pipelines und Serveranwendungen
  • Verwenden der verwalteten Identität für Anwendungen, die auf Azure-Diensten ausgeführt werden (Funktionen, App Service, VMs)
  • Vermeiden Sie persönliche Zugriffstoken in der Produktion; Nur für Entwicklung und Tests verwendet
  • Niemals Anmeldeinformationen hartkodieren im Quellcode; Verwenden Sie Umgebungsvariablen oder Azure Key Vault
  • Implementierung der Rotation von Anmeldeinformationen für langzeitlaufende Anwendungen
  • Sicherstellen der richtigen Bereiche: Die Erstellung von Arbeitselementen erfordert entsprechende Berechtigungen in Azure DevOps

Fehlerbehandlung

  • Implementieren der richtigen Ausnahmebehandlung für Authentifizierungs- und API-Fehler
  • Überprüfen von Feldwerten vor dem Versuch, Arbeitsaufgaben zu erstellen
  • Behandeln von Feldüberprüfungsfehlern , die von der API zurückgegeben werden
  • Verwenden von asynchronen/await-Mustern für eine bessere Reaktionsfähigkeit der Anwendung

Leistung

  • Batchvorgänge beim Erstellen mehrerer Arbeitselemente
  • Zwischenspeichern von Verbindungen bei mehreren API-Aufrufen
  • Verwenden geeigneter Timeoutwerte für lange ausgeführte Vorgänge
  • Implementieren Sie eine Rücksetzlogik mit exponentiellem Backoff für flüchtige Fehler

Datenvalidierung

  • Überprüfen der erforderlichen Felder vor API-Aufrufen
  • Überprüfen von Feldberechtigungen und Regeln für Arbeitsaufgabentypen
  • Bereinigung der Benutzereingabe , um Einfügungsangriffe zu verhindern
  • Befolgen Sie projektspezifische Feldanforderungen und Benennungsrichtlinien

Problembehandlung

Authentifizierungsprobleme

  • Microsoft Entra ID-Authentifizierungsfehler: Stellen Sie sicher, dass der Benutzer über die richtigen Berechtigungen zum Erstellen von Arbeitsaufgaben verfügt.
  • Fehler bei der Dienstprinzipalauthentifizierung: Überprüfen, ob Client-ID, geheimer Schlüssel und Mandanten-ID korrekt sind; Überprüfen von Dienstprinzipalberechtigungen in Azure DevOps
  • Fehler bei der verwalteten Identitätsauthentifizierung: Stellen Sie sicher, dass die Azure-Ressource über eine verwaltete Identität verfügt und ordnungsgemäße Berechtigungen besitzt.
  • PAT-Authentifizierungsfehler: Überprüfen, ob das Token Gültigkeitsbereich hat vso.work_write und nicht abgelaufen ist
  • 403 Unzulässige Fehler: Überprüfen von Projektberechtigungen und Zugriff auf Arbeitsaufgabentypen

Fehler bei der Feldüberprüfung

  • Erforderliches Feld fehlt: Stellen Sie sicher, dass alle erforderlichen Felder im Patchdokument enthalten sind.
  • Ungültige Feldwerte: Überprüfen, ob Feldwerte mit dem erwarteten Format und zulässigen Werten übereinstimmen
  • Feld nicht gefunden: Überprüfen Sie, ob die Feldnamen korrekt geschrieben sind und für diesen Arbeitsaufgabentyp existieren.
  • Fehler bei schreibgeschützten Feldern: Einige Felder können während der Erstellung nicht festgelegt werden (z. B. System.CreatedBy)

Allgemeine Ausnahmen

  • VssUnauthorizedException: Authentifizierung fehlgeschlagen oder unzureichende Berechtigungen
  • VssServiceException: Serverseitige Überprüfungsfehler oder API-Probleme
  • ArgumentException: Ungültige Parameter oder falsch formatiertes Patchdokument
  • JsonReaderException: Probleme mit der JSON-Serialisierung/Deserialisierung

Leistungsprobleme

  • Langsame API-Antworten: Überprüfen des Netzwerkkonnektivitäts- und Azure DevOps-Dienststatus
  • Speicherauslastung: Ordnungsgemäßes Löschen von Verbindungen und Clients
  • Rate-Limitierung: Implementieren Sie angemessene Verzögerungen zwischen API-Aufrufen.

Nächste Schritte