Bagikan melalui


.NET contoh pustaka klien untuk Azure DevOps

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

Pelajari cara memperluas dan mengintegrasikan dengan Azure DevOps menggunakan pustaka klien .NET dengan metode autentikasi modern dan praktik pengodean yang aman.

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

Paket NuGet yang diperlukan:

Rekomendasi autentikasi:

Penting

Artikel ini memperlihatkan beberapa metode autentikasi untuk skenario yang berbeda. Pilih metode yang paling tepat berdasarkan lingkungan penyebaran dan persyaratan keamanan Anda.

Contoh koneksi inti dan item kerja

Contoh komprehensif ini menunjukkan praktik terbaik untuk menyambungkan ke Azure DevOps dan bekerja dengan item kerja:

using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

/// <summary>
/// Demonstrates secure Azure DevOps integration with proper error handling and resource management
/// </summary>
public class AzureDevOpsService
{
    private readonly VssConnection _connection;
    private readonly WorkItemTrackingHttpClient _witClient;

    public AzureDevOpsService(string organizationUrl, VssCredentials credentials)
    {
        // Create connection with proper credential management
        _connection = new VssConnection(new Uri(organizationUrl), credentials);
        
        // Get work item tracking client (reused for efficiency)
        _witClient = _connection.GetClient<WorkItemTrackingHttpClient>();
    }

    /// <summary>
    /// Creates a work item query, executes it, and returns results with proper error handling
    /// </summary>
    public async Task<IEnumerable<WorkItem>> GetNewBugsAsync(string projectName)
    {
        try
        {
            // Get query hierarchy with proper depth control
            var queryHierarchyItems = await _witClient.GetQueriesAsync(projectName, depth: 2);

            // Find 'My Queries' folder using safe navigation
            var myQueriesFolder = queryHierarchyItems
                .FirstOrDefault(qhi => qhi.Name.Equals("My Queries", StringComparison.OrdinalIgnoreCase));

            if (myQueriesFolder == null)
            {
                throw new InvalidOperationException("'My Queries' folder not found in project.");
            }

            const string queryName = "New Bugs Query";
            
            // Check if query already exists
            var existingQuery = myQueriesFolder.Children?
                .FirstOrDefault(qhi => qhi.Name.Equals(queryName, StringComparison.OrdinalIgnoreCase));

            QueryHierarchyItem query;
            if (existingQuery == null)
            {
                // Create new query with proper WIQL
                query = new QueryHierarchyItem
                {
                    Name = queryName,
                    Wiql = @"
                        SELECT [System.Id], [System.WorkItemType], [System.Title], 
                               [System.AssignedTo], [System.State], [System.Tags] 
                        FROM WorkItems 
                        WHERE [System.TeamProject] = @project 
                          AND [System.WorkItemType] = 'Bug' 
                          AND [System.State] = 'New'
                        ORDER BY [System.CreatedDate] DESC",
                    IsFolder = false
                };
                
                query = await _witClient.CreateQueryAsync(query, projectName, myQueriesFolder.Name);
            }
            else
            {
                query = existingQuery;
            }

            // Execute query and get results
            var queryResult = await _witClient.QueryByIdAsync(query.Id);
            
            if (!queryResult.WorkItems.Any())
            {
                return Enumerable.Empty<WorkItem>();
            }

            // Batch process work items for efficiency
            const int batchSize = 100;
            var allWorkItems = new List<WorkItem>();
            
            for (int skip = 0; skip < queryResult.WorkItems.Count(); skip += batchSize)
            {
                var batch = queryResult.WorkItems.Skip(skip).Take(batchSize);
                var workItemIds = batch.Select(wir => wir.Id).ToArray();
                
                // Get detailed work item information
                var workItems = await _witClient.GetWorkItemsAsync(
                    ids: workItemIds,
                    fields: new[] { "System.Id", "System.Title", "System.State", 
                                   "System.AssignedTo", "System.CreatedDate" });
                
                allWorkItems.AddRange(workItems);
            }

            return allWorkItems;
        }
        catch (Exception ex)
        {
            // Log error appropriately in real applications
            throw new InvalidOperationException($"Failed to retrieve work items: {ex.Message}", ex);
        }
    }

    /// <summary>
    /// Properly dispose of resources
    /// </summary>
    public void Dispose()
    {
        _witClient?.Dispose();
        _connection?.Dispose();
    }
}

Metode autentikasi

Untuk aplikasi yang mendukung autentikasi interaktif atau memiliki token Microsoft Entra:

using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;

/// <summary>
/// Authenticate using Microsoft Entra ID credentials
/// Recommended for interactive applications and modern authentication scenarios
/// </summary>
public static VssConnection CreateEntraConnection(string organizationUrl, string accessToken)
{
    // Use Microsoft Entra access token for authentication
    var credentials = new VssOAuthAccessTokenCredential(accessToken);
    return new VssConnection(new Uri(organizationUrl), credentials);
}

/// <summary>
/// For device code flow (cross-platform interactive authentication)
/// Works with .NET Core, .NET 5+, and .NET Framework
/// </summary>
public static async Task<VssConnection> CreateEntraDeviceCodeConnectionAsync(
    string organizationUrl, string clientId, string tenantId)
{
    var app = PublicClientApplicationBuilder
        .Create(clientId)
        .WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
        .Build();

    var result = await app
        .AcquireTokenWithDeviceCode(
            new[] { "https://app.vssps.visualstudio.com/.default" },
            callback =>
            {
                Console.WriteLine(callback.Message);
                return Task.CompletedTask;
            })
        .ExecuteAsync();

    var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
    return new VssConnection(new Uri(organizationUrl), credentials);
}

Autentikasi prinsip layanan

Untuk skenario otomatis dan alur CI/CD:

using Microsoft.Identity.Client;
using Microsoft.VisualStudio.Services.Client;

/// <summary>
/// Authenticate using service principal with certificate (most secure)
/// Recommended for production automation scenarios
/// </summary>
public static async Task<VssConnection> CreateServicePrincipalConnectionAsync(
    string organizationUrl, 
    string clientId, 
    string tenantId, 
    X509Certificate2 certificate)
{
    try
    {
        // Create confidential client application with certificate
        var app = ConfidentialClientApplicationBuilder
            .Create(clientId)
            .WithCertificate(certificate)
            .WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
            .Build();

        // Acquire token for Azure DevOps
        var result = await app
            .AcquireTokenForClient(new[] { "https://app.vssps.visualstudio.com/.default" })
            .ExecuteAsync();

        // Create connection with acquired token
        var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
        return new VssConnection(new Uri(organizationUrl), credentials);
    }
    catch (Exception ex)
    {
        throw new AuthenticationException($"Failed to authenticate service principal: {ex.Message}", ex);
    }
}

/// <summary>
/// Service principal with client secret (less secure than certificate)
/// </summary>
public static async Task<VssConnection> CreateServicePrincipalSecretConnectionAsync(
    string organizationUrl,
    string clientId,
    string tenantId,
    string clientSecret)
{
    var app = ConfidentialClientApplicationBuilder
        .Create(clientId)
        .WithClientSecret(clientSecret)
        .WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
        .Build();

    var result = await app
        .AcquireTokenForClient(new[] { "https://app.vssps.visualstudio.com/.default" })
        .ExecuteAsync();

    var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
    return new VssConnection(new Uri(organizationUrl), credentials);
}

Autentikasi identitas terkelola

Untuk aplikasi yang dihosting Azure (direkomendasikan untuk skenario cloud):

using Azure.Identity;
using Azure.Core;
using Microsoft.VisualStudio.Services.Client;

/// <summary>
/// Authenticate using managed identity (most secure for Azure-hosted apps)
/// No credentials to manage - Azure handles everything automatically
/// </summary>
public static async Task<VssConnection> CreateManagedIdentityConnectionAsync(string organizationUrl)
{
    try
    {
        // Use system-assigned managed identity
        var credential = new ManagedIdentityCredential();
        
        // Acquire token for Azure DevOps
        var tokenRequest = new TokenRequestContext(new[] { "https://app.vssps.visualstudio.com/.default" });
        var tokenResponse = await credential.GetTokenAsync(tokenRequest);

        // Create connection with managed identity token
        var credentials = new VssOAuthAccessTokenCredential(tokenResponse.Token);
        return new VssConnection(new Uri(organizationUrl), credentials);
    }
    catch (Exception ex)
    {
        throw new AuthenticationException($"Failed to authenticate with managed identity: {ex.Message}", ex);
    }
}

/// <summary>
/// Use user-assigned managed identity with specific client ID
/// </summary>
public static async Task<VssConnection> CreateUserAssignedManagedIdentityConnectionAsync(
    string organizationUrl, 
    string managedIdentityClientId)
{
    var credential = new ManagedIdentityCredential(managedIdentityClientId);
    var tokenRequest = new TokenRequestContext(new[] { "https://app.vssps.visualstudio.com/.default" });
    var tokenResponse = await credential.GetTokenAsync(tokenRequest);

    var credentials = new VssOAuthAccessTokenCredential(tokenResponse.Token);
    return new VssConnection(new Uri(organizationUrl), credentials);
}

Autentikasi interaktif

Untuk aplikasi desktop yang mengharuskan pengguna masuk:

kerangka kerja .NET

/// <summary>
/// Interactive authentication with Visual Studio sign-in prompt
/// .NET Framework only - not supported in .NET Core/.NET 5+
/// </summary>
public static VssConnection CreateInteractiveConnection(string organizationUrl)
{
    var credentials = new VssClientCredentials();
    return new VssConnection(new Uri(organizationUrl), credentials);
}

.NET Core / .NET 5+

Gunakan kode perangkat MSAL atau alur browser sistem untuk autentikasi interaktif lintas platform. Lihat contoh CreateEntraDeviceCodeConnectionAsync dalam autentikasi Microsoft Entra, atau gunakan pendekatan browser sistem:

var app = PublicClientApplicationBuilder
    .Create(clientId)
    .WithRedirectUri("http://localhost")
    .WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
    .Build();

var result = await app
    .AcquireTokenInteractive(new[] { "https://app.vssps.visualstudio.com/.default" })
    .ExecuteAsync();

var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
var connection = new VssConnection(new Uri(organizationUrl), credentials);

Autentikasi token akses pribadi (Warisan)

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.

Jika Anda harus menggunakan PAT, lihat Menggunakan token akses pribadi untuk membuatnya. Kemudian operkan sebagai VssBasicCredential:

var credentials = new VssBasicCredential(string.Empty, personalAccessToken);
var connection = new VssConnection(new Uri(organizationUrl), credentials);

Contoh penggunaan lengkap

Fungsi Azure dengan identitas terkelola

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

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

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

    [Function("ProcessWorkItems")]
    public async Task<string> ProcessWorkItems(
        [TimerTrigger("0 0 8 * * MON")] TimerInfo timer)
    {
        try
        {
            var organizationUrl = Environment.GetEnvironmentVariable("AZURE_DEVOPS_ORG_URL");
            var projectName = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PROJECT");

            // Use managed identity for secure authentication
            using var connection = await CreateManagedIdentityConnectionAsync(organizationUrl);
            using var service = new AzureDevOpsService(organizationUrl, connection.Credentials);

            var workItems = await service.GetNewBugsAsync(projectName);
            
            _logger.LogInformation($"Processed {workItems.Count()} work items");
            
            return $"Successfully processed {workItems.Count()} work items";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process work items");
            throw;
        }
    }
}

Aplikasi konsol dengan service principal

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

class Program
{
    static async Task Main(string[] args)
    {
        // Configure logging and configuration
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddEnvironmentVariables()
            .Build();

        using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
        var logger = loggerFactory.CreateLogger<Program>();

        try
        {
            var settings = configuration.GetSection("AzureDevOps");
            var organizationUrl = settings["OrganizationUrl"];
            var projectName = settings["ProjectName"];
            var clientId = settings["ClientId"];
            var tenantId = settings["TenantId"];
            var clientSecret = settings["ClientSecret"]; // Better: use Key Vault

            // Authenticate with service principal
            using var connection = await CreateServicePrincipalSecretConnectionAsync(
                organizationUrl, clientId, tenantId, clientSecret);
            
            using var service = new AzureDevOpsService(organizationUrl, connection.Credentials);

            // Process work items
            var workItems = await service.GetNewBugsAsync(projectName);
            
            foreach (var workItem in workItems)
            {
                Console.WriteLine($"Bug {workItem.Id}: {workItem.Fields["System.Title"]}");
            }

            logger.LogInformation($"Successfully processed {workItems.Count()} work items");
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Application failed");
            Environment.Exit(1);
        }
    }
}

Praktik terbaik

Pertimbangan keamanan

Manajemen kredensial:

  • Jangan pernah menyimpan kredensial secara langsung di dalam kode sumber
  • Gunakan Azure Key Vault untuk menyimpan rahasia
  • Lebih suka identitas managed untuk aplikasi yang dihosting Azure
  • Gunakan sertifikat dibandingkan rahasia klien untuk perwakilan layanan
  • Putar kredensial secara teratur mengikuti kebijakan keamanan

Kontrol akses:

  • Menerapkan prinsip hak istimewa paling sedikit
  • Gunakan cakupan tertentu saat memperoleh token
  • Memantau dan mengaudit peristiwa autentikasi
  • Menerapkan kebijakan akses kondisional jika sesuai

Pengoptimalan performa

Manajemen koneksi:

  • Menggunakan kembali instans VssConnection di seluruh operasi
  • Kumpulan klien HTTP melalui objek koneksi
  • Menerapkan pola pembuangan yang tepat
  • Mengonfigurasi batas waktu dengan tepat

Operasi batch:

  • Memproses item kerja dalam batch (disarankan : 100 item)
  • Menggunakan pemrosesan paralel untuk operasi independen
  • Menerapkan logika ulang coba dengan penundaan eksponensial
  • Cache data yang sering diakses jika sesuai

Penanganan kesalahan

public async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3)
{
    var retryCount = 0;
    var baseDelay = TimeSpan.FromSeconds(1);

    while (retryCount < maxRetries)
    {
        try
        {
            return await operation();
        }
        catch (Exception ex) when (IsTransientError(ex) && retryCount < maxRetries - 1)
        {
            retryCount++;
            var delay = TimeSpan.FromMilliseconds(baseDelay.TotalMilliseconds * Math.Pow(2, retryCount));
            await Task.Delay(delay);
        }
    }

    // Final attempt without catch
    return await operation();
}

private static bool IsTransientError(Exception ex)
{
    return ex is HttpRequestException ||
           ex is TaskCanceledException ||
           (ex is VssServiceException vssEx && vssEx.HttpStatusCode >= 500);
}

Panduan migrasi

Dari PAT hingga autentikasi modern

Langkah 1: Menilai penggunaan saat ini

  • Mengidentifikasi semua aplikasi menggunakan PATs
  • Menentukan lingkungan penyebaran (Azure vs. lokal)
  • Mengevaluasi persyaratan keamanan

Langkah 2: Pilih metode penggantian

  • Dihosting di Azure: Migrasikan ke identitas terkelola
  • Alur CI/CD: Menggunakan prinsipal layanan
  • Aplikasi Interaktif: Menerapkan autentikasi Microsoft Entra
  • Aplikasi desktop: Pertimbangkan alur kode perangkat

Langkah 3: Implementasi

  • Memperbarui kode autentikasi menggunakan contoh sebelumnya
  • Uji secara menyeluruh di lingkungan pengembangan
  • Menyebarkan secara bertahap ke lingkungan produksi
  • Memantau masalah autentikasi

Untuk panduan migrasi terperinci, lihat Ganti PAT dengan token Microsoft Entra.

Menggunakan AI untuk menghasilkan kode klien .NET

Jika Anda memiliki Azure DevOps MCP Server tersambung ke agen AI Anda dalam mode agen, Anda dapat menggunakan perintah bahasa alami untuk menghasilkan kode pustaka klien .NET untuk Azure DevOps.

Tugas Contoh tanggapan
Membuat kueri item kerja Write C# code using the Azure DevOps .NET client library to create a work item query, execute it, and process the results
Mencantumkan repositori dan komit Git Show me how to use the Azure DevOps GitHttpClient to list repositories and get recent commits in a project
Hubungkan identitas terkelola Create a .NET application that connects to Azure DevOps using managed identity and retrieves build definitions
Masuk Entra Interaktif Write code to authenticate to Azure DevOps using the .NET client library with interactive Microsoft Entra sign-in
Mengelola pengaturan tim Write C# code using the Azure DevOps .NET client to get team members and iteration paths for a project
Menjalankan pipeline Show me how to trigger a pipeline run in Azure DevOps using the .NET client libraries with service principal authentication

Nota

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