Compartilhar via


.NET exemplos de biblioteca de clientes para Azure DevOps

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

Saiba como estender e integrar com Azure DevOps usando as bibliotecas de cliente .NET com métodos de autenticação modernos e práticas de codificação seguras.

Dica

Você pode usar a IA para ajudar nessa tarefa mais adiante neste artigo ou consulte Ativar a assistência de IA com o Azure DevOps Server MCP para começar.

Pré-requisitos

Pacotes NuGet necessários:

Recomendações de autenticação:

Importante

Este artigo mostra vários métodos de autenticação para cenários diferentes. Escolha o método mais apropriado com base em seu ambiente de implantação e requisitos de segurança.

Exemplo de conexão principal e item de trabalho

Este exemplo abrangente demonstra as práticas recomendadas para se conectar a Azure DevOps e trabalhar com itens de trabalho:

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

Métodos de autenticação

Para aplicativos que dão suporte à autenticação interativa ou têm tokens 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);
}

Autenticação da entidade de serviço

Para cenários automatizados e pipelines de 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);
}

Autenticação de identidade gerenciada

Para aplicativos hospedados Azure (recomendado para cenários de nuvem):

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

Autenticação interativa

Para aplicativos da área de trabalho que exigem a entrada do usuário:

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

Use o código do dispositivo MSAL ou o fluxo do navegador do sistema para autenticação interativa multiplataforma. Consulte o exemplo CreateEntraDeviceCodeConnectionAsync na autenticação Microsoft Entra ou use a abordagem do navegador do 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);

Autenticação de token de acesso pessoal (herdado)

Importante

Considere usar os mais seguros tokens Microsoft Entra em relação aos tokens de acesso pessoal de maior risco. Para obter mais informações, consulte Reduzir o uso do PAT. Examine as diretrizes de autenticação para escolher o mecanismo de autenticação correto para suas necessidades.

Se você precisar usar um PAT, consulte Usar tokens de acesso pessoal para criar um. Em seguida, passe-o como um VssBasicCredential:

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

Exemplos de uso completos

Função Azure com identidade gerenciada

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

Aplicativo de console com entidade de serviço

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

Práticas recomendadas

Considerações de segurança

Gerenciamento de credenciais:

  • Nunca codifique credenciais no código-fonte
  • Usar Azure Key Vault para armazenar segredos
  • Preferir identidades gerenciadas para aplicativos hospedados do Azure
  • Usar os certificados sobre os segredos de cliente para as entidades de serviço
  • Atualizar credenciais regularmente seguindo políticas de segurança

Controle de acesso:

  • Aplicar princípio de privilégio mínimo
  • Usar os escopos específicos quando adquirir os tokens
  • Monitorar e auditar eventos de autenticação
  • Implementar políticas de acesso condicional quando apropriado

Otimização de desempenho

Gerenciamento de conexões:

  • Reutilizar instâncias do VssConnection entre operações
  • Agrupar clientes HTTP por meio do objeto de conexão
  • Implementar padrões de descarte adequados
  • Configurar tempos limite adequadamente

Operações em lote:

  • Processar itens de trabalho em lotes (recomendado: 100 itens)
  • Usar o processamento paralelo para operações independentes
  • Implementar a lógica de repetição com a retirada exponencial
  • Armazenar dados acessados com frequência quando apropriado

Tratamento de erros

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

Diretrizes de migração

De PATs à autenticação moderna

Etapa 1: Avaliar o uso atual

  • Identificar todos os aplicativos usando PATs
  • Determinar ambientes de implantação (Azure versus local)
  • Avaliar os requisitos de segurança

Etapa 2: Escolher método de substituição

  • Azure-hosted: Migrar para identidades gerenciadas
  • Pipelines de CI/CD: use as entidades de serviço
  • Interactive apps: Implementar a autenticação Microsoft Entra
  • Aplicativos de desktop: considere o fluxo de código de dispositivo

Etapa 3: Implementação

  • Atualizar código de autenticação usando os exemplos anteriores
  • Testar minuciosamente no ambiente de desenvolvimento
  • Implantar incrementalmente na produção
  • Monitorar os problemas de autenticação

Para obter diretrizes de migração detalhadas, consulte Replace PATs com tokens Microsoft Entra.

Usar IA para gerar .NET código do cliente

Se você tiver o Servidor MCP Azure DevOps conectado ao seu agente de IA no modo agente, poderá usar prompts de linguagem natural para gerar código da biblioteca cliente .NET para o Azure DevOps.

Tarefa Prompt de exemplo
Criar uma consulta de item de trabalho Write C# code using the Azure DevOps .NET client library to create a work item query, execute it, and process the results
Listar repositórios e confirmações do Git Show me how to use the Azure DevOps GitHttpClient to list repositories and get recent commits in a project
Conectar-se com a identidade gerenciada Create a .NET application that connects to Azure DevOps using managed identity and retrieves build definitions
Entrada interativa do Entra Write code to authenticate to Azure DevOps using the .NET client library with interactive Microsoft Entra sign-in
Gerenciar configurações de equipe Write C# code using the Azure DevOps .NET client to get team members and iteration paths for a project
Criar uma execução de pipeline Show me how to trigger a pipeline run in Azure DevOps using the .NET client libraries with service principal authentication

Observação

O modo de agente e o servidor MCP usam linguagem natural, para que você possa ajustar esses prompts ou fazer perguntas de acompanhamento para refinar os resultados.