Создание ошибки в службах Azure DevOps с помощью клиентских библиотек .NET

Сервисы Azure DevOps | Azure DevOps Server | Azure DevOps Server 2022

Создание рабочих элементов программным способом — это распространенный сценарий автоматизации в службах Azure DevOps. В этой статье показано, как создать ошибку (или любой рабочий элемент), используя клиентские библиотеки .NET с современными методами проверки подлинности.

Подсказка

Вы можете использовать ИИ, чтобы помочь с этой задачей позже в этой статье или ознакомиться с включение помощи ИИ в Azure DevOps MCP Server, чтобы начать работу.

Необходимые условия

Категория Требования
Azure DevOps - Организация
— доступ к проекту, в котором можно создавать рабочие элементы
Аутентификация Выберите один из следующих вариантов:
- Microsoft Entra ID аутентификация (рекомендуется)
- Личный маркер доступа (PAT) ( для тестирования)
среды разработки Среда разработки C#. Можно использовать Visual Studio

Это важно

Рассмотрите возможность использования более безопасных токенов Microsoft Entra вместо более рискованных персональных токенов доступа. Дополнительные сведения см. в разделе "Сокращение использования PAT". Просмотрите рекомендации по проверке подлинности , чтобы выбрать правильный механизм проверки подлинности для ваших потребностей.

Варианты проверки подлинности

В этой статье показано несколько методов проверки подлинности для различных сценариев:

Для рабочих приложений с взаимодействием с пользователем используйте проверку подлинности 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" />

Для автоматизированных сценариев, конвейеров CI/CD и серверных приложений:

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

Для приложений, работающих в службах Azure (функции, служба приложений и т. д.):

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

Проверка подлинности токена личного доступа

Для сценариев разработки и тестирования:

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

Примеры кода C#

В следующих примерах показано, как создавать рабочие элементы с помощью различных методов проверки подлинности.

Пример 1. Проверка подлинности Microsoft Entra ID (интерактивный режим)

Замечание

Для использования в этом примере класса VssAadCredential требуется пакет Microsoft.VisualStudio.Services.InteractiveClient и поддержка .NET Framework. Для приложений .NET Core/.NET 5+ используйте подход на основе MSAL, показанный в Example 2 (субъект-служба) или Example 3 (управляемое удостоверение) с 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;
            }
        }
    }
}

Пример 2. Аутентификация Service Principal (автоматизированные сценарии)

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

Пример 3: Проверка подлинности управляемого удостоверения (приложения, размещенные в 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;
            }
        }
    }
}

Пример 4: Аутентификация с использованием персонального токена доступа

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

Примеры использования

Использование проверки подлинности 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}");
    }
}

Использование проверки подлинности субъекта-службы (сценарии 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}");
    }
}

Использование аутентификации с управляемым удостоверением (Функции Azure/служба приложений)

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 through Azure Function...",
            priority: 3,
            severity: "4 - Low"
        );

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

Использование аутентификации с личным токеном доступа (разработка и тестирование)

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

Справочник по полю рабочего элемента

При создании рабочих элементов обычно используются следующие поля:

Обязательные поля

  • System.Title: название рабочего элемента (обязательно для всех типов рабочих элементов)
  • System.WorkItemType: автоматически устанавливается при указании типа в вызове API

Общие необязательные поля

  • Microsoft.VSTS.TCM.ReproSteps: подробные действия по воспроизведению
  • Microsoft.VSTS.Common.Priority: уровень приоритета (1=самый высокий, 4=самый низкий)
  • Microsoft.VSTS.Common.Severity: классификация уровня серьезности
  • System.Description: Общее описание или дополнительные сведения
  • System.AssignedTo: лицо, ответственное за рабочий элемент
  • System.AreaPath: классификация областей
  • System.IterationPath: назначение итерации/спринта

Значения приоритета

  • 1. Критически важный/самый высокий приоритет
  • 2. Высокий приоритет
  • 3. Средний приоритет (по умолчанию)
  • 4. Низкий приоритет

Общие значения серьезности

  • 1. Критическое: неиспользуемая система, блокировка хода выполнения
  • 2. Высокий: основные функциональные возможности нарушены
  • 3 — средний: некоторые функциональные возможности нарушены (по умолчанию)
  • 4 - Низкий: мелкие проблемы или косметические дефекты

Лучшие практики

Аутентификация

  • Use Microsoft Entra ID для интерактивных приложений с входом пользователя
  • Используйте учетную запись службы для автоматизированных сценариев, потоков CI/CD и серверных приложений
  • Use Managed Identity для приложений, работающих в службах Azure (Функции, служба приложений, виртуальные машины)
  • Избегайте личных маркеров доступа в рабочей среде; используется только для разработки и тестирования
  • Никогда не используйте учетные данные в исходном коде напрямую; используйте переменные окружения или Azure Key Vault
  • Реализуйте ротацию учетных данных для долго работающих приложений
  • Ensure правильные области доступа: Для создания рабочих элементов требуются соответствующие разрешения в Azure DevOps

Обработка ошибок

  • Реализация правильной обработки исключений для сбоев проверки подлинности и API
  • Проверка значений полей перед попыткой создания рабочих элементов
  • Обработка ошибок проверки поля , возвращаемых API
  • Использование шаблонов async/await для повышения скорости реагирования приложений

Производительность

  • Пакетные операции при создании нескольких элементов работы
  • Кэширование подключений при выполнении нескольких вызовов API
  • Использование соответствующих значений времени ожидания для длительных операций
  • Реализовать логику повторных попыток с экспоненциальной задержкой для временных сбоев

Проверка данных

  • Проверка обязательных полей перед вызовами API
  • Проверка разрешений поля и правил типов рабочих элементов
  • Обработка входных данных пользователей, чтобы предотвратить инъекционные атаки
  • Следуйте требованиям к полям для конкретного проекта и соглашениям об именовании

Устранение неполадок

Проблемы аутентификации

  • Microsoft Entra ID сбои аутентификации. Убедитесь, что у пользователя есть соответствующие разрешения для создания рабочих элементов
  • Сбои аутентификации учетной записи службы: проверьте правильность идентификатора клиента, секрета и идентификатора арендатора; проверьте разрешения учетной записи службы в Azure DevOps
  • Ошибка аутентификации Managed Identity: Убедитесь, что ресурс Azure имеет включённое управляемое удостоверение и соответствующие разрешения.
  • Сбои проверки подлинности PAT: убедитесь, что токен имеет vso.work_write область и срок его действия не истек.
  • 403 Запрещенные ошибки: проверка разрешений проекта и доступа к типу рабочего элемента

Ошибки проверки полей

  • Отсутствует обязательное поле: убедитесь, что все обязательные поля включены в документ патча
  • Недопустимые значения полей: проверка значений полей соответствует ожидаемому формату и допустимым значениям
  • Поле не найдено: проверьте, правильно ли написаны имена полей и существуют для типа рабочего элемента
  • Ошибки полей только для чтения: некоторые поля нельзя задать во время создания (например, System.CreatedBy)

Распространенные исключения

  • VssUnauthorizedException: сбой проверки подлинности или недостаточно разрешений
  • VssServiceException: ошибки проверки на стороне сервера или проблемы с API
  • ArgumentException: недопустимые параметры или неправильный документ исправления
  • JsonReaderException: проблемы с сериализацией и десериализацией JSON

Проблемы с производительностью

  • Проблемы с медленной работой API: проверьте сетевое подключение и статус сервиса Azure DevOps.
  • Использование памяти: правильное удаление подключений и клиентов
  • Ограничение скорости. Реализация соответствующих задержек между вызовами API

Использование искусственного интеллекта для программного создания рабочих элементов

Если Azure DevOps MCP Server подключен к вашему агенту ИИ в режиме агента, вы можете использовать естественно-языковые запросы для создания рабочих элементов.

задачи Пример запроса
Создание кода для создания багов Write a C# console app that creates a bug in Azure DevOps project <Contoso> using Microsoft Entra ID authentication and the .NET client libraries
Создайте с настраиваемыми полями Write code to create a work item with priority, severity, and repro steps fields in Azure DevOps using a managed identity
Пакетное создание рабочих элементов Show me how to create multiple bugs in Azure DevOps from a CSV file using the .NET client libraries with service principal authentication
Создание из функции Azure Generate an Azure Function that creates bugs in Azure DevOps project <Contoso> using a system-assigned managed identity
Добавление вложений Write C# code to create a bug in Azure DevOps and attach a log file using the .NET client libraries
Создание связанных рабочих элементов Write code to create a bug in Azure DevOps and link it to an existing user story using the .NET client libraries

Замечание

Режим агента и сервер MCP используют естественный язык, чтобы настроить эти запросы или задать дальнейшие вопросы, чтобы уточнить результаты.

Следующий шаг