Mengambil item kerja dengan kueri secara terprogram

Azure DevOps Services

Mengambil item kerja menggunakan kueri adalah skenario umum di Azure DevOps Services. Artikel ini menjelaskan cara menerapkan skenario ini secara terprogram menggunakan REST API atau pustaka klien .NET.

Petunjuk

Anda dapat menggunakan AI untuk membantu tugas ini nanti dalam artikel ini, atau lihat Mengaktifkan bantuan AI dengan Azure DevOps MCP Server untuk memulai.

Prasyarat

Kategori Persyaratan
Azure DevOps - Organisasi
- Akses ke proyek dengan item kerja
Autentikasi Pilih salah satu metode berikut:
- Microsoft Entra ID autentikasi (direkomendasikan untuk aplikasi interaktif)
- Autentikasi Perwakilan Layanan (disarankan untuk otomatisasi)
- Managed Identity authentication (disarankan untuk aplikasi yang dihosting Azure)
- Token Akses Pribadi (untuk pengujian)
lingkungan Pengembangan Lingkungan pengembangan C#. Anda dapat menggunakan Visual Studio

Penting

Pertimbangkan untuk menggunakan token Microsoft Entra yang lebih aman daripada token akses personal yang berisiko lebih tinggi. Untuk informasi selengkapnya, lihat Mengurangi penggunaan PAT. Tinjau panduan autentikasi untuk memilih mekanisme autentikasi yang tepat untuk kebutuhan Anda.

Opsi autentikasi

Artikel ini menunjukkan beberapa metode autentikasi yang sesuai dengan skenario yang berbeda:

Untuk aplikasi produksi dengan interaksi pengguna, gunakan autentikasi 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" />

Untuk skenario otomatis, alur CI/CD, dan aplikasi server:

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

Untuk aplikasi yang berjalan pada layanan Azure (Functions, App Service, dll.):

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

Autentikasi token akses pribadi

Untuk skenario pengembangan dan pengujian:

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

Contoh kode C#

Contoh berikut menunjukkan cara mengambil item kerja menggunakan metode autentikasi yang berbeda.

Contoh 1: autentikasi Microsoft Entra ID (interaktif)

Nota

Kelas VssAadCredential yang digunakan dalam contoh ini memerlukan paket Microsoft.VisualStudio.Services.InteractiveClient dan menargetkan .NET Framework. Untuk aplikasi .NET Core/.NET 5+, gunakan pendekatan berbasis MSAL yang ditunjukkan dalam Example 2 (Perwakilan Layanan) atau Example 3 (Identitas Terkelola) dengan 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"]}");
        }
    }
}

Contoh 2: Autentikasi perwakilan layanan (skenario otomatis)

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

Contoh 3: Autentikasi identitas terkelola (aplikasi yang dihosting 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>();
            }
        }
    }
}

Contoh 4: Autentikasi token akses pribadi

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

Contoh penggunaan

Contoh berikut menunjukkan cara memanggil setiap kelas autentikasi.

Menggunakan autentikasi Microsoft Entra ID (interaktif)

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

Menggunakan autentikasi prinsipal layanan (skenario 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"]}");
        }
    }
}

Menggunakan autentikasi identitas terkelola (Azure Functions/App Service)

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

Menggunakan autentikasi token akses pribadi (pengembangan/pengujian)

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

Praktik terbaik

Otentikasi

  • Gunakan Microsoft Entra ID untuk aplikasi interaktif dengan masuk pengguna
  • Gunakan service principal untuk skenario otomatis, alur CI/CD, dan aplikasi server
  • Gunakan identitas terkelola untuk aplikasi yang berjalan pada layanan Azure (Functions, App Service, VM)
  • Hindari token akses pribadi dalam produksi; hanya digunakan untuk pengembangan dan pengujian
  • Jangan pernah menyimpan kredensial secara langsung dalam kode sumber; gunakan variabel lingkungan atau Azure Key Vault
  • Menerapkan rotasi kredensial untuk aplikasi yang berjalan lama
  • Pastikan cakupan yang tepat - Kueri item kerja memerlukan izin baca yang tepat di Azure DevOps

Penanganan kesalahan

  • Menerapkan logika coba lagi dengan backoff eksponensial untuk kegagalan sementara
  • Catat kesalahan dengan tepat untuk penelusuran kesalahan dan pemantauan
  • Menangani pengecualian tertentu seperti kegagalan autentikasi dan batas waktu jaringan
  • Menggunakan token pembatalan untuk operasi jangka panjang

Penampilan

  • Pengambilan item kerja batch saat mengkueri beberapa item
  • Membatasi hasil kueri menggunakan klausul TOP untuk himpunan data besar
  • Cache data yang sering diakses untuk mengurangi panggilan API
  • Gunakan bidang yang sesuai untuk meminimalkan transfer data

Pengoptimalan kueri

  • Gunakan nama bidang tertentu alih-alih SELECT * untuk performa yang lebih baik
  • Menambahkan klausa WHERE yang tepat untuk memfilter hasil di server
  • Memesan hasil dengan tepat untuk kasus penggunaan Anda
  • Pertimbangkan batas kueri dan penomoran halaman untuk kumpulan hasil besar

Penyelesaian Masalah

Masalah autentikasi

  • Microsoft Entra ID kegagalan autentikasi - Pastikan pengguna memiliki izin yang tepat dan masuk ke Azure DevOps
  • Kegagalan autentikasi service principal - Memastikan bahwa ID klien, kode rahasia, dan ID penyewa sudah benar; periksa izin perwakilan layanan di Azure DevOps
  • Kelola kegagalan autentikasi identitas - Pastikan sumber daya Azure mengaktifkan identitas terkelola dan izin yang tepat
  • Kegagalan autentikasi PAT - Pastikan token valid dan memiliki cakupan yang sesuai (vso.work untuk akses item kerja)
  • Kedaluwarsa token - Periksa apakah PAT Anda kedaluwarsa dan buat yang baru jika diperlukan

Masalah kueri

  • Sintaks WIQL tidak valid - Pastikan sintaks Bahasa Kueri Item Kerja Anda sudah benar
  • Kesalahan nama proyek - Verifikasi nama proyek ada dan dieja dengan benar
  • Kesalahan nama bidang - Gunakan nama bidang sistem yang benar (misalnya, System.Id, System.Title)

Pengecualian umum

  • VssUnauthorizedException - Periksa kredensial dan izin autentikasi
  • ArgumentException - Pastikan semua parameter yang diperlukan disediakan dan valid
  • HttpRequestException - Periksa konektivitas jaringan dan ketersediaan layanan

Masalah performa

  • Kueri lambat - Tambahkan klausa WHERE yang sesuai dan batasi set hasil
  • Penggunaan memori - Memproses tataan hasil besar dalam batch
  • Pembatasan laju - Menerapkan logika percobaan ulang dengan penundaan eksponensial

Menggunakan AI untuk mengkueri item kerja secara terprogram

Jika Anda memiliki Azure DevOps MCP Server tersambung ke agen AI Anda dalam mode agen, Anda dapat menggunakan perintah bahasa alami untuk menghasilkan kode untuk mengkueri item kerja.

Tugas Contoh tanggapan
Membuat kode kueri Write C# code to query all active bugs assigned to me in Azure DevOps using the .NET client libraries with Microsoft Entra authentication
Kueri REST API Create a REST API call to fetch work items from Azure DevOps using a WIQL query with a personal access token
Menjalankan kueri yang disimpan Show me how to use the Azure DevOps .NET client to run a saved query and retrieve work item details including custom fields
Ekspor ke CSV Build a .NET app that fetches work items from Azure DevOps and exports them to CSV using managed identity authentication
Filter menurut jalur area Write C# code to query work items under area path <Contoso\Backend> that were modified in the last 7 days
Paginasi hasil yang besar Show me how to query Azure DevOps work items in batches of 200 using the .NET client libraries with proper pagination

Nota

Mode agen dan Server MCP menggunakan bahasa alami, sehingga Anda dapat menyesuaikan perintah ini atau mengajukan pertanyaan tindak lanjut untuk memperbaiki hasilnya.