Поделиться через


Извлечение рабочих элементов с помощью запросов программным способом

Azure DevOps Services

Извлечение рабочих элементов с помощью запросов — это распространенный сценарий в службах Azure DevOps. В этой статье объясняется, как реализовать этот сценарий программным способом с помощью REST API или клиентских библиотек .NET.

Подсказка

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

Предварительные условия

Категория Требования
Azure DevOps - Организация
— доступ к проекту с рабочими элементами
Аутентификация Выберите для этого один из следующих методов:
- Microsoft Entra ID аутентификации (рекомендуется для интерактивных приложений)
- Аутентификация служебного принципала (рекомендуется для автоматизации)
Аутентификация с управляемой идентичностью (рекомендуется для приложений, размещенных в Azure)
- Личный маркер доступа (для тестирования)
среды разработки Среда разработки 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.Collections.Generic;
using System.Linq;
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;

public class EntraIdQueryExecutor
{
    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 EntraIdQueryExecutor(string orgName)
    {
        this.uri = new Uri("https://dev.azure.com/" + orgName);
    }

    /// <summary>
    /// Execute a WIQL query using Microsoft Entra ID authentication.
    /// </summary>
    /// <param name="project">The name of your project within your organization.</param>
    /// <returns>A list of WorkItem objects representing all the open bugs.</returns>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // Use Microsoft Entra ID authentication
        var credentials = new VssAadCredential();
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var result = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = result.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }

    /// <summary>
    /// Print the results of the work item query.
    /// </summary>
    public async Task PrintOpenBugsAsync(string project)
    {
        var workItems = await this.QueryOpenBugsAsync(project).ConfigureAwait(false);
        Console.WriteLine($"Query Results: {workItems.Count} items found");

        foreach (var workItem in workItems)
        {
            Console.WriteLine($"{workItem.Id}\t{workItem.Fields["System.Title"]}\t{workItem.Fields["System.State"]}");
        }
    }
}

Пример 2: Проверка подлинности сервисного принципала (автоматизированные сценарии)

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.Identity.Client
using System;
using System.Collections.Generic;
using System.Linq;
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;

public class ServicePrincipalQueryExecutor
{
    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 ServicePrincipalQueryExecutor(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>
    /// Execute a WIQL query using Service Principal authentication.
    /// </summary>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // 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 wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var queryResult = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

Пример 3: Аутентификация управляемой идентификации (приложения, размещенные в Azure)

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Azure.Identity
using System;
using System.Collections.Generic;
using System.Linq;
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;

public class ManagedIdentityQueryExecutor
{
    private readonly Uri uri;

    /// <summary>
    /// Initializes a new instance using Managed Identity authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    public ManagedIdentityQueryExecutor(string orgName)
    {
        this.uri = new Uri($"https://dev.azure.com/{orgName}");
    }

    /// <summary>
    /// Execute a WIQL query using Managed Identity authentication.
    /// </summary>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // 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 wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var queryResult = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

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

// NuGet package: Microsoft.TeamFoundationServer.Client
using System;
using System.Collections.Generic;
using System.Linq;
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;

public class PatQueryExecutor
{
    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 PatQueryExecutor(string orgName, string personalAccessToken)
    {
        this.uri = new Uri("https://dev.azure.com/" + orgName);
        this.personalAccessToken = personalAccessToken;
    }

    /// <summary>
    /// Execute a WIQL query using Personal Access Token authentication.
    /// </summary>
    /// <param name="project">The name of your project within your organization.</param>
    /// <returns>A list of WorkItem objects representing all the open bugs.</returns>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        var credentials = new VssBasicCredential(string.Empty, this.personalAccessToken);
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var result = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = result.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

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

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

Использование аутентификации Microsoft Entra ID (интерактивной)

class Program
{
    static async Task Main(string[] args)
    {
        var executor = new EntraIdQueryExecutor("your-organization-name");
        await executor.PrintOpenBugsAsync("your-project-name");
    }
}

Использование проверки подлинности учетной записи службы (сценарии 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 executor = new ServicePrincipalQueryExecutor("your-organization-name", clientId, clientSecret, tenantId);
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");
        
        Console.WriteLine($"Found {workItems.Count} open bugs via automation");
        foreach (var item in workItems)
        {
            Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
        }
    }
}

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

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

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

    [Function("QueryOpenBugs")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req)
    {
        var executor = new ManagedIdentityQueryExecutor("your-organization-name");
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");

        var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
        await response.WriteAsJsonAsync(new { 
            Count = workItems.Count,
            Items = workItems.Select(wi => new { 
                Id = wi.Id, 
                Title = wi.Fields["System.Title"],
                State = wi.Fields["System.State"]
            })
        });
        return response;
    }
}

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

class Program
{
    static async Task Main(string[] args)
    {
        var pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT"); // Never hardcode PATs
        var executor = new PatQueryExecutor("your-organization-name", pat);
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");
        
        Console.WriteLine($"Found {workItems.Count} open bugs");
        foreach (var item in workItems)
        {
            Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
        }
    }
}

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

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

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

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

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

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

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

Оптимизация запросов

  • Используйте определенные имена полей вместо SELECT * для повышения производительности
  • Добавьте правильные предложения WHERE для фильтрации результатов на сервере
  • Упорядочение результатов соответствующим образом для вашего варианта использования
  • Рассмотрите ограничения запросов и разбиение на страницы для больших результирующих наборов

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

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

  • Microsoft Entra ID сбои аутентификации - Убедитесь, что у пользователя есть необходимые разрешения и выполнен вход в Azure DevOps
  • Сбои аутентификации субъекта-службы — проверьте правильность идентификатора клиента, секрета и идентификатора арендатора; проверьте разрешения субъекта-службы в Azure DevOps
  • Managed identity authentication failures. Убедитесь, что ресурс Azure имеет включенное управляемое удостоверение и соответствующие разрешения
  • Сбои аутентификации PAT - Убедитесь, что токен действителен и имеет соответствующие области (vso.work для доступа к рабочему элементу)
  • Срок действия маркера . Проверьте, истек ли срок действия PAT и при необходимости создайте новый.

Проблемы с запросами

  • Недопустимый синтаксис WIQL . Убедитесь, что синтаксис языка запросов рабочих элементов правильно
  • Ошибки имени проекта. Убедитесь, что имя проекта существует и правильно орфографировано
  • Ошибки имени поля . Используйте правильные имена системных полей (например, System.Id, System.Title)

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

  • VssUnauthorizedException — проверьте учетные данные и разрешения на доступ
  • ArgumentException — убедитесь, что указаны и допустимы все необходимые параметры.
  • HttpRequestException — проверка доступности сетевых подключений и служб

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

  • Медленные запросы - Добавьте соответствующие предложения WHERE и ограничьте результирующие наборы
  • Использование памяти . Обработка больших результирующих наборов в пакетах
  • Ограничение скорости - Реализация логики повторных попыток с экспоненциальной обратной задержкой

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

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

задачи Пример запроса
Создание кода запроса Write C# code to query all active bugs assigned to me in Azure DevOps using the .NET client libraries with Microsoft Entra authentication
Запрос REST API Create a REST API call to fetch work items from Azure DevOps using a WIQL query with a personal access token
Выполнение сохраненного запроса Show me how to use the Azure DevOps .NET client to run a saved query and retrieve work item details including custom fields
Экспорт в CSV Build a .NET app that fetches work items from Azure DevOps and exports them to CSV using managed identity authentication
Фильтр по пути области Write C# code to query work items under area path <Contoso\Backend> that were modified in the last 7 days
Разбить на страницы большие результаты Show me how to query Azure DevOps work items in batches of 200 using the .NET client libraries with proper pagination

Замечание

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