Udostępnij za pośrednictwem


przykłady biblioteki klienta .NET dla Azure DevOps

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

Dowiedz się, jak rozszerzyć i zintegrować z Azure DevOps przy użyciu bibliotek klienckich .NET z nowoczesnymi metodami uwierzytelniania i bezpiecznymi praktykami kodowania.

Wymagania wstępne

Wymagane pakiety NuGet:

Zalecenia dotyczące uwierzytelniania:

Ważne

W tym artykule przedstawiono wiele metod uwierzytelniania dla różnych scenariuszy. Wybierz najbardziej odpowiednią metodę na podstawie środowiska wdrażania i wymagań dotyczących zabezpieczeń.

Przykład podstawowego połączenia i elementu roboczego

W tym kompleksowym przykładzie przedstawiono najlepsze rozwiązania dotyczące nawiązywania połączenia z Azure DevOps i pracy z elementami roboczymi:

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

Metody uwierzytelniania

W przypadku aplikacji, które obsługują uwierzytelnianie interakcyjne lub mają tokeny 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);
}

Uwierzytelnianie głównego elementu usługi

W przypadku scenariuszy automatycznych oraz potoków 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);
}

Uwierzytelnianie tożsamości zarządzanej

W przypadku aplikacji hostowanych Azure (zalecane w scenariuszach w chmurze):

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

Uwierzytelnianie interakcyjne

W przypadku aplikacji komputerowych wymagających logowania się użytkownika:

.NET Framework

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

Użyj kodu urządzenia MSAL lub przepływu przeglądarki systemowej na potrzeby uwierzytelniania interakcyjnego międzyplatformowego. Zobacz przykład CreateEntraDeviceCodeConnectionAsync w Microsoft Entra Uwierzytelnianie lub użyj metody przeglądarki systemowej:

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

Uwierzytelnianie za pomocą tokenu osobistego dostępu (wersja legacy)

Ważne

Rozważ użycie bardziej bezpiecznych tokenów Microsoft Entra zamiast bardziej ryzykownych osobowych tokenów dostępu. Aby uzyskać więcej informacji, zobacz Zmniejszanie użycia PAT. Zapoznaj się ze wskazówkami dotyczącymi uwierzytelniania , aby wybrać odpowiedni mechanizm uwierzytelniania dla Twoich potrzeb.

Jeśli musisz użyć osobistego tokenu dostępu, zobacz Use personal access tokens, aby utworzyć jeden. Następnie przekaż go jako element VssBasicCredential:

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

Kompletne przykłady użycia

funkcja Azure z tożsamością zarządzaną

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

Aplikacja konsolowa z jednostką usługi

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

Najlepsze rozwiązania

Zagadnienia dotyczące zabezpieczeń

Zarządzanie poświadczeniami:

  • Nigdy nie zakodowano poświadczeń w kodzie źródłowym
  • Użyj Azure Key Vault do przechowywania wpisów tajnych
  • Preferuj zarządzane tożsamości dla aplikacji hostowanych Azure
  • Używanie certyfikatów zamiast wpisów tajnych klienta dla jednostek usługi
  • Regularnie zmieniaj poświadczenia zgodnie z zasadami polityki zabezpieczeń

Kontrola dostępu:

  • Stosowanie zasady najniższych uprawnień
  • Używanie określonych zakresów podczas uzyskiwania tokenów
  • Monitorowanie i inspekcja zdarzeń uwierzytelniania
  • Implementowanie zasad dostępu warunkowego w odpowiednich przypadkach

Optymalizacja wydajności

Zarządzanie połączeniami:

  • Ponowne używanie wystąpień programu VssConnection w operacjach
  • Tworzenie puli klientów HTTP za pośrednictwem obiektu połączenia
  • Implementowanie prawidłowych wzorców usuwania
  • Odpowiednio skonfiguruj limity czasu

Operacje wsadowe:

  • Przetwarzanie elementów roboczych w partiach (zalecane: 100 elementów)
  • Używanie przetwarzania równoległego na potrzeby niezależnych operacji
  • Implementowanie logiki ponawiania przy użyciu opóźnienia wykładniczego
  • Buforowanie często używanych danych , jeśli jest to konieczne

Obsługa błędów

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

Wskazówki dotyczące migracji

Od PATs do nowoczesnego uwierzytelniania

Krok 1. Ocena bieżącego użycia

  • Identyfikowanie wszystkich aplikacji przy użyciu paT
  • Określanie środowisk wdrażania (Azure a lokalne)
  • Ocena wymagań dotyczących zabezpieczeń

Krok 2. Wybieranie metody zastępczej

  • Hostowane w Azure: Przejście na tożsamości zarządzane
  • Potoki CI/CD: używanie głównych zasad usług
  • Interactive apps: Implementowanie uwierzytelniania Microsoft Entra
  • Aplikacje desktopowe: rozważ przepływ kodu urządzenia

Krok 3. Implementacja

  • Aktualizowanie kodu uwierzytelniania przy użyciu poprzednich przykładów
  • Dokładnie przetestuj w środowisku projektowym
  • Wdrażanie przyrostowe w środowisku produkcyjnym
  • Monitorowanie problemów z uwierzytelnianiem

Aby uzyskać szczegółowe wskazówki dotyczące migracji, zobacz Zastąp PATs tokenami Microsoft Entra.

Generowanie kodu klienta .NET za pomocą sztucznej inteligencji

Jeśli masz serwer Azure DevOps MCP połączony z agentem sztucznej inteligencji w trybie agenta, możesz użyć monitów języka naturalnego, aby wygenerować kod biblioteki klienta .NET dla Azure DevOps.

Zadanie Przykładowy monit
Tworzenie zapytania elementu roboczego Write C# code using the Azure DevOps .NET client library to create a work item query, execute it, and process the results
Lista repozytoriów i zatwierdzeń Git Show me how to use the Azure DevOps GitHttpClient to list repositories and get recent commits in a project
Nawiązywanie połączenia przy użyciu tożsamości zarządzanej Create a .NET application that connects to Azure DevOps using managed identity and retrieves build definitions
Logowanie interakcyjne entra Write code to authenticate to Azure DevOps using the .NET client library with interactive Microsoft Entra sign-in
Zarządzanie ustawieniami zespołu Write C# code using the Azure DevOps .NET client to get team members and iteration paths for a project
Utwórz przebieg potoku Show me how to trigger a pipeline run in Azure DevOps using the .NET client libraries with service principal authentication

Uwaga / Notatka

Tryb agenta i serwer MCP używają języka naturalnego, dzięki czemu można dostosować te komunikaty lub zadać dalsze pytania w celu uściślenia wyników.