Condividi tramite


esempi di libreria client .NET per Azure DevOps

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

Informazioni su come estendere e integrare con Azure DevOps usando le librerie client .NET con metodi di autenticazione moderni e procedure di codifica sicure.

Suggerimento

È possibile usare l'intelligenza artificiale per facilitare questa attività più avanti in questo articolo, oppure vedere Abilitare l'assistenza AI con Azure DevOps MCP Server per iniziare.

Prerequisiti

Pacchetti NuGet necessari:

Raccomandazioni per l'autenticazione:

Importante

Questo articolo illustra più metodi di autenticazione per diversi scenari. Scegliere il metodo più appropriato in base all'ambiente di distribuzione e ai requisiti di sicurezza.

Esempio di connessione principale ed elemento di lavoro

Questo esempio completo illustra le procedure consigliate per la connessione a Azure DevOps e l'uso degli elementi di lavoro:

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

Metodi di autenticazione

Per le applicazioni che supportano l'autenticazione interattiva o hanno token di 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);
}

Autenticazione di un'entità servizio

Per scenari automatizzati e pipeline 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);
}

Autenticazione identità gestita

Per le applicazioni ospitate Azure (consigliate per gli scenari 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);
}

Autenticazione interattiva

Per le applicazioni desktop che richiedono l'accesso utente:

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

Usare il codice del dispositivo MSAL o il flusso del browser di sistema per l'autenticazione interattiva multipiattaforma. Visualizza l'esempio di CreateEntraDeviceCodeConnectionAsync in autenticazione Microsoft Entra oppure usa l'approccio del browser di sistema:

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

Autenticazione del token di accesso personale (Legacy)

Importante

Prendere in considerazione l'uso dei token Microsoft Entra più sicuri rispetto ai token personali di accesso ad alto rischio. Per altre informazioni, vedere Ridurre l'utilizzo di PAT. Esaminare le indicazioni per l'autenticazione per scegliere il meccanismo di autenticazione appropriato per le proprie esigenze.

Se è necessario usare un token di accesso personale, vedere Usare i token di accesso personale per crearne uno. Quindi passarlo come VssBasicCredential:

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

Esempi di utilizzo completi

funzione Azure con identità gestita

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

Applicazione console con entità servizio

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

Procedure consigliate

Considerazioni sulla sicurezza

Gestione delle credenziali:

  • Non inserire mai credenziali hardcoded nel codice sorgente
  • Usare Azure Key Vault per archiviare i segreti
  • Preferisce identità gestite per le applicazioni ospitate Azure
  • Usare certificati invece di segreti client per le entità servizio
  • Ruotare le credenziali regolarmente seguendo i criteri di sicurezza

Controllo di accesso:

  • Applicare il principio dei privilegi minimi
  • Usare ambiti specifici durante l'acquisizione di token
  • Monitorare e controllare gli eventi di autenticazione
  • Implementare i criteri di accesso condizionale , se appropriato

Ottimizzazione delle prestazioni

Gestione connessione:

  • Riutilizzare le istanze di VssConnection tra le operazioni
  • Pool di client HTTP tramite l'oggetto di connessione
  • Implementare modelli di eliminazione appropriati
  • Configurare i timeout in modo appropriato

Operazioni batch:

  • Elaborare gli elementi di lavoro in batch (scelta consigliata: 100 elementi)
  • Usare l'elaborazione parallela per operazioni indipendenti
  • Implementare la logica di ripetizione dei tentativi con backoff esponenziale
  • Memorizzare nella cache i dati a cui si accede di frequente quando appropriato

Gestione degli errori

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

Indicazioni sulla migrazione

Da PT all'autenticazione moderna

Passaggio 1: Valutare l'utilizzo corrente

  • Identificare tutte le applicazioni che usano PAT
  • Determinare gli ambienti di distribuzione (Azure rispetto all'ambiente locale)
  • Valutare i requisiti di sicurezza

Passaggio 2: Scegliere il metodo di sostituzione

  • Ospitato su Azure: Migrare verso le identità gestite
  • Pipeline CI/CD: usare le entità servizio
  • App interattive: Implementare l'autenticazione Microsoft Entra
  • App desktop: prendere in considerazione il flusso del codice del dispositivo

Passaggio 3: Implementazione

  • Aggiornare il codice di autenticazione usando gli esempi precedenti
  • Testare accuratamente nell'ambiente di sviluppo
  • Distribuire in modo incrementale nell'ambiente di produzione
  • Monitorare i problemi di autenticazione

Per indicazioni dettagliate sulla migrazione, vedere Replace PAT con token di Microsoft Entra.

Usare l'intelligenza artificiale per generare .NET codice client

Se si dispone del Azure DevOps MCP Server connesso all'agente di intelligenza artificiale in modalità agente, è possibile usare i prompt del linguaggio naturale per generare .NET codice della libreria client per Azure DevOps.

Attività Richiesta di esempio
Creare una query per un elemento di lavoro Write C# code using the Azure DevOps .NET client library to create a work item query, execute it, and process the results
Elencare repo Git e commit Show me how to use the Azure DevOps GitHttpClient to list repositories and get recent commits in a project
Connettersi con l'identità gestita Create a .NET application that connects to Azure DevOps using managed identity and retrieves build definitions
Accesso interattivo Entra Write code to authenticate to Azure DevOps using the .NET client library with interactive Microsoft Entra sign-in
Gestire le impostazioni del team Write C# code using the Azure DevOps .NET client to get team members and iteration paths for a project
Creare un'esecuzione della pipeline Show me how to trigger a pipeline run in Azure DevOps using the .NET client libraries with service principal authentication

Annotazioni

La modalità agente e il server MCP usano il linguaggio naturale, quindi è possibile modificare queste richieste o porre domande di completamento per perfezionare i risultati.