Chamar APIs personalizadas

Microsoft. O Identity.Web fornece três abordagens para chamar suas próprias APIs protegidas: IDownstreamApi, IAuthorizationHeaderProvider e MicrosoftIdentityMessageHandler.

Escolher uma abordagem

Ao chamar APIs REST personalizadas, você tem três opções principais, dependendo de suas necessidades:

Abordagem Complexidade Flexibilidade Caso de uso
IDownstreamApi Baixo Medium APIs REST padrão com configuração
MicrosoftIdentityMessageHandler Medium Alto HttpClient com DI e pipeline composable
IAuthorizationHeaderProvider Alto Muito alto Controle total sobre solicitações HTTP

Usar iDownstreamApi para cenários padrão

IDownstreamApi fornece uma abordagem simples e orientada por configuração para chamar APIs REST com aquisição automática de token.

Instalar o pacote

Adicione o pacote NuGet DownstreamApi ao seu projeto.

dotnet add package Microsoft.Identity.Web.DownstreamApi

Definir as configurações da API

Defina sua API em appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "your-client-secret"
      }
    ]
  },
  "DownstreamApis": {
    "MyApi": {
      "BaseUrl": "https://api.example.com",
      "Scopes": ["api://my-api-client-id/read", "api://my-api-client-id/write"],
      "RelativePath": "api/v1",
      "RequestAppToken": false
    },
    "PartnerApi": {
      "BaseUrl": "https://partner.example.com",
      "Scopes": ["api://partner-api-id/.default"],
      "RequestAppToken": true
    }
  }
}

Configurar ASP.NET Core

Registre a autenticação e os serviços de API downstream em seu Program.cs arquivo.

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add authentication
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// Register downstream APIs
builder.Services.AddDownstreamApis(
    builder.Configuration.GetSection("DownstreamApis"));

builder.Services.AddControllersWithViews();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

Executar operações básicas de API

O controlador a seguir demonstra as operações GET, POST, PUT e DELETE em uma API downstream configurada.

using Microsoft.Identity.Abstractions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize]
public class ProductsController : Controller
{
    private readonly IDownstreamApi _api;
    
    public ProductsController(IDownstreamApi api)
    {
        _api = api;
    }
    
    // GET request
    public async Task<IActionResult> Index()
    {
        var products = await _api.GetForUserAsync<List<Product>>(
            "MyApi",
            "products");
        
        return View(products);
    }
    
    // Call downstream API with GET request with query parameters
    public async Task<IActionResult> Details(int id)
    {
        var product = await _api.GetForUserAsync<Product>(
            "MyApi",
            $"products/{id}");
        
        return View(product);
    }
    
    // Call downstream API with POST request
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] Product product)
    {
        var created = await _api.PostForUserAsync<Product, Product>(
            "MyApi",
            "products",
            product);
        
        return CreatedAtAction(nameof(Details), new { id = created.Id }, created);
    }
    
    // Call downstream API with PUT request
    [HttpPut("{id}")]
    public async Task<IActionResult> Update(int id, [FromBody] Product product)
    {
        var updated = await _api.PutForUserAsync<Product, Product>(
            "MyApi",
            $"products/{id}",
            product);
        
        return Ok(updated);
    }
    
    // Call downstream API with DELETE request
    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
        await _api.DeleteForUserAsync<Product>(
            "MyApi",
            $"products/{id}");
        
        return NoContent();
    }
}

Configurar opções avançadas do IDownstreamApi

Use essas opções para personalizar cabeçalhos de solicitação, substituir a configuração, adicionar parâmetros de consulta e ler cabeçalhos de resposta.

Adicionar cabeçalhos e opções personalizados

O exemplo a seguir adiciona cabeçalhos HTTP personalizados à solicitação de saída.

public async Task<IActionResult> GetDataWithHeaders()
{
    var options = new DownstreamApiOptions
    {
        CustomizeHttpRequestMessage = message =>
        {
            message.Headers.Add("X-Custom-Header", "MyValue");
            message.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());
            message.Headers.Add("X-Correlation-Id", HttpContext.TraceIdentifier);
        }
    };
    
    var data = await _api.CallApiForUserAsync<MyData>(
        "MyApi",
        options,
        content: null);
    
    return Ok(data);
}

Substituir a configuração por solicitação

O exemplo a seguir substitui a URL base, os escopos e o tipo de token para uma única solicitação.

public async Task<IActionResult> CallDifferentEndpoint()
{
    var options = new DownstreamApiOptions
    {
        BaseUrl = "https://alternative-api.example.com",
        RelativePath = "v2/data",
        Scopes = new[] { "api://alternative/.default" },
        RequestAppToken = true
    };
    
    var data = await _api.CallApiForAppAsync<MyData>(
        "MyApi",
        options);
    
    return Ok(data);
}

Adicionar parâmetros de consulta

O exemplo a seguir cria uma solicitação de pesquisa com parâmetros de consulta no caminho relativo.

public async Task<IActionResult> Search(string query, int page, int pageSize)
{
    var options = new DownstreamApiOptions
    {
        RelativePath = $"search?q={Uri.EscapeDataString(query)}&page={page}&pageSize={pageSize}"
    };
    
    var results = await _api.GetForUserAsync<SearchResults>(
        "MyApi",
        options);
    
    return Ok(results);
}

Você também pode usar o dicionário options.ExtraQueryParameters.

Manipular cabeçalhos de resposta

O exemplo a seguir lê as informações de limite de taxa nos cabeçalhos de resposta.

public async Task<IActionResult> GetWithHeaders()
{
    var response = await _api.CallApiAsync<MyData>(
        "MyApi",
        options =>
        {
            options.RelativePath = "data";
        });
    
    // Access response headers
    if (response.Headers.TryGetValues("X-RateLimit-Remaining", out var values))
    {
        var remaining = values.FirstOrDefault();
        _logger.LogInformation("Rate limit remaining: {Remaining}", remaining);
    }
    
    return Ok(response.Content);
}

Adquirir tokens somente de aplicativo com IDownstreamApi

Use GetForAppAsync para chamar uma API com permissões de aplicativo em vez de permissões de usuário delegadas.

[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
    private readonly IDownstreamApi _api;
    
    public DataController(IDownstreamApi api)
    {
        _api = api;
    }
    
    [HttpGet("batch")]
    public async Task<ActionResult> GetBatchData()
    {
        // Call with application permissions
        var data = await _api.GetForAppAsync<BatchData>(
            "MyApi",
            "batch/process");
        
        return Ok(data);
    }
}

Use MicrosoftIdentityMessageHandler para integração com HttpClient

MicrosoftIdentityMessageHandler adiciona autenticação Microsoft Entra ao pipeline httpClient automaticamente.

Identificar quando usar MicrosoftIdentityMessageHandler

  • Você precisa de controle refinado sobre solicitações HTTP
  • Você deseja compor vários manipuladores de mensagens
  • Você está integrando-se a um código existente baseado em HttpClient.
  • Você precisa acessar o HttpResponseMessage bruto

Configurar sobrecargas do MicrosoftIdentityMessageHandler

MicrosoftIdentityMessageHandler é um DelegatingHandler que adiciona autenticação a solicitações HttpClient. Use esse manipulador quando precisar de recursos completos do HttpClient com aquisição automática de token. Os métodos de extensão AddMicrosoftIdentityMessageHandler fornecem uma maneira flexível de configurar o HttpClient com autenticação Microsoft Entra ID automática:

  • Sem parâmetros: para flexibilidade de configuração por solicitação
  • Instância de opções: para objetos de opções pré-configurados
  • Delegado de ação: para configuração embutida (mais comum)
  • IConfiguration: Para configuração a partir de appsettings.json

Escolha a sobrecarga que melhor se ajusta ao seu cenário e aproveite a autenticação automática para suas chamadas à API downstream.

Usar a sobrecarga sem parâmetros para a configuração por solicitação

Configure as opções de autenticação por solicitação com essa sobrecarga.

services.AddHttpClient("FlexibleClient")
    .AddMicrosoftIdentityMessageHandler();

// Later, in a service:
var request = new HttpRequestMessage(HttpMethod.Get, "/api/data")
    .WithAuthenticationOptions(options =>
    {
        options.Scopes.Add("https://api.example.com/.default");
    });

var response = await httpClient.SendAsync(request);

Passar uma instância de opções pré-configurada

Use essa sobrecarga quando você tiver um objeto de opções pré-configurado.

var options = new MicrosoftIdentityMessageHandlerOptions
{
    Scopes = { "https://graph.microsoft.com/.default" }
};
options.WithAgentIdentity("agent-application-id");

services.AddHttpClient("GraphClient", client =>
{
    client.BaseAddress = new Uri("https://graph.microsoft.com");
})
.AddMicrosoftIdentityMessageHandler(options);

Configurar inline com um delegado de ação

Use essa sobrecarga para a configuração embutida, o cenário mais comum.

services.AddHttpClient("MyApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
})
.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("https://api.example.com/.default");
    options.RequestAppToken = true;
});

Carregar configurações de appsettings.json

Use essa sobrecarga para vincular as configurações diretamente ao seu arquivo de configuração.

appsettings.json:

{
  "DownstreamApi": {
    "Scopes": ["https://api.example.com/.default"]
  },
  "GraphApi": {
    "Scopes": ["https://graph.microsoft.com/.default", "User.Read"]
  }
}

Program.cs:

services.AddHttpClient("DownstreamApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
})
.AddMicrosoftIdentityMessageHandler(
    configuration.GetSection("DownstreamApi"),
    "DownstreamApi");

services.AddHttpClient("GraphClient", client =>
{
    client.BaseAddress = new Uri("https://graph.microsoft.com");
})
.AddMicrosoftIdentityMessageHandler(
    configuration.GetSection("GraphApi"),
    "GraphApi");

Examinar exemplos de configuração

Esses exemplos demonstram padrões de configuração comuns para o manipulador de mensagens.

Criar um cliente de API Web simples

O exemplo a seguir registra e consome um cliente de API de clima.

// Configure in Program.cs
services.AddHttpClient("WeatherApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.weather.com");
})
.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("https://api.weather.com/.default");
});

// Use in a controller or service
public class WeatherService
{
    private readonly HttpClient _httpClient;
    
    public WeatherService(IHttpClientFactory factory)
    {
        _httpClient = factory.CreateClient("WeatherApiClient");
    }
    
    public async Task<WeatherForecast> GetForecastAsync(string city)
    {
        var response = await _httpClient.GetAsync($"/forecast/{city}");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<WeatherForecast>();
    }
}

Configurar vários clientes de API

O exemplo a seguir registra dois clientes de API separados com escopos e tipos de token diferentes.

// Configure multiple clients in Program.cs
services.AddHttpClient("ApiClient1")
    .AddMicrosoftIdentityMessageHandler(options =>
    {
        options.Scopes.Add("https://api1.example.com/.default");
    });

services.AddHttpClient("ApiClient2")
    .AddMicrosoftIdentityMessageHandler(options =>
    {
        options.Scopes.Add("https://api2.example.com/.default");
        options.RequestAppToken = true;
    });

// Use in a service
public class MultiApiService
{
    private readonly HttpClient _client1;
    private readonly HttpClient _client2;
    
    public MultiApiService(IHttpClientFactory factory)
    {
        _client1 = factory.CreateClient("ApiClient1");
        _client2 = factory.CreateClient("ApiClient2");
    }
    
    public async Task<string> GetFromBothApisAsync()
    {
        var data1 = await _client1.GetStringAsync("/data");
        var data2 = await _client2.GetStringAsync("/data");
        return $"{data1} | {data2}";
    }
}

Carregar opções complexas de appsettings.json

O exemplo a seguir associa várias configurações de API de uma seção de configuração compartilhada.

appsettings.json:

{
  "DownstreamApis": {
    "CustomerApi": {
      "Scopes": ["api://customer-api/.default"]
    },
    "OrderApi": {
      "Scopes": ["api://order-api/.default"]
    },
    "InventoryApi": {
      "Scopes": ["api://inventory-api/.default"]
    }
  }
}

Program.cs:

var downstreamApis = configuration.GetSection("DownstreamApis");

services.AddHttpClient("CustomerApiClient", client =>
{
    client.BaseAddress = new Uri("https://customer-api.example.com");
})
.AddMicrosoftIdentityMessageHandler(
    downstreamApis.GetSection("CustomerApi"),
    "CustomerApi");

services.AddHttpClient("OrderApiClient", client =>
{
    client.BaseAddress = new Uri("https://order-api.example.com");
})
.AddMicrosoftIdentityMessageHandler(
    downstreamApis.GetSection("OrderApi"),
    "OrderApi");

services.AddHttpClient("InventoryApiClient", client =>
{
    client.BaseAddress = new Uri("https://inventory-api.example.com");
})
.AddMicrosoftIdentityMessageHandler(
    downstreamApis.GetSection("InventoryApi"),
    "InventoryApi");

Opções de substituição por solicitação

Você pode substituir as opções padrão por solicitação usando o WithAuthenticationOptions método de extensão.

// Configure client with default options
services.AddHttpClient("ApiClient")
    .AddMicrosoftIdentityMessageHandler(options =>
    {
        options.Scopes.Add("https://api.example.com/.default");
    });

// Override for specific requests
public class MyService
{
    private readonly HttpClient _httpClient;
    
    public MyService(IHttpClientFactory factory)
    {
        _httpClient = factory.CreateClient("ApiClient");
    }
    
    public async Task<string> GetSensitiveDataAsync()
    {
        // Override scopes for this specific request
        var request = new HttpRequestMessage(HttpMethod.Get, "/api/sensitive")
            .WithAuthenticationOptions(options =>
            {
                options.Scopes.Clear();
                options.Scopes.Add("https://api.example.com/sensitive.read");
                options.RequestAppToken = true;
            });
        
        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

Implementar cenários avançados

As seções a seguir abrangem a identidade do agente, a composição do manipulador e o tratamento automático de desafios.

Configurar a identidade do agente

Use a identidade do agente quando seu aplicativo precisar agir em nome de outro aplicativo:

services.AddHttpClient("AgentClient")
    .AddMicrosoftIdentityMessageHandler(options =>
    {
        options.Scopes.Add("https://graph.microsoft.com/.default");
        options.WithAgentIdentity("agent-application-id");
        options.RequestAppToken = true;
    });

Compor com outros manipuladores

Encadear vários manipuladores no pipeline para adicionar log, novas tentativas ou outras questões transversais.

services.AddHttpClient("ApiClient")
    .AddMicrosoftIdentityMessageHandler(options =>
    {
        options.Scopes.Add("https://api.example.com/.default");
    })
    .AddHttpMessageHandler<LoggingHandler>()
    .AddHttpMessageHandler<RetryHandler>();

Gerenciar desafios de autenticação WWW

O manipulador processa automaticamente desafios WWW-Authenticate para cenários de Acesso Condicional.

// No additional code needed - automatic handling
services.AddHttpClient("ProtectedApiClient")
    .AddMicrosoftIdentityMessageHandler(options =>
    {
        options.Scopes.Add("https://api.example.com/.default");
    });

// The handler will automatically:
// 1. Detect 401 responses with WWW-Authenticate challenges
// 2. Extract required claims from the challenge
// 3. Acquire a new token with the additional claims
// 4. Retry the request with the new token

Gerenciar erros

O exemplo a seguir captura a autenticação e os erros HTTP separadamente ao chamar uma API por meio do manipulador de mensagens.

public class MyService
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<MyService> _logger;
    
    public MyService(IHttpClientFactory factory, ILogger<MyService> logger)
    {
        _httpClient = factory.CreateClient("ApiClient");
        _logger = logger;
    }
    
    public async Task<string> GetDataWithErrorHandlingAsync()
    {
        try
        {
            var response = await _httpClient.GetAsync("/api/data");
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
        catch (MicrosoftIdentityAuthenticationException authEx)
        {
            _logger.LogError(authEx, "Authentication failed: {Message}", authEx.Message);
            throw;
        }
        catch (HttpRequestException httpEx)
        {
            _logger.LogError(httpEx, "HTTP request failed: {Message}", httpEx.Message);
            throw;
        }
    }
}

Usar IAuthorizationHeaderProvider para controle máximo

IAuthorizationHeaderProvider fornece acesso direto aos cabeçalhos de autorização para controle completo sobre solicitações HTTP.

Identificar quando usar IAuthorizationHeaderProvider

  • Você precisa de controle total sobre a construção da solicitação HTTP
  • Você está integrando com APIs HTTP não padrão
  • Você precisa usar HttpClient sem DI
  • Você está criando abstrações HTTP personalizadas

Executar operações básicas

O controlador a seguir recupera um cabeçalho de autorização e o anexa a uma solicitação HTTP manual.

using Microsoft.Identity.Abstractions;

[Authorize]
public class CustomApiController : Controller
{
    private readonly IAuthorizationHeaderProvider _headerProvider;
    private readonly ILogger<CustomApiController> _logger;
    
    public CustomApiController(
        IAuthorizationHeaderProvider headerProvider,
        ILogger<CustomApiController> logger)
    {
        _headerProvider = headerProvider;
        _logger = logger;
    }
    
    public async Task<IActionResult> GetData()
    {
        // Get authorization header (includes "Bearer " prefix)
        var authHeader = await _headerProvider.CreateAuthorizationHeaderForUserAsync(
            scopes: new[] { "api://my-api/read" });
        
        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Authorization", authHeader);
        client.DefaultRequestHeaders.Add("X-Custom-Header", "MyValue");
        
        var response = await client.GetAsync("https://api.example.com/data");
        response.EnsureSuccessStatusCode();
        
        var content = await response.Content.ReadAsStringAsync();
        return Content(content, "application/json");
    }
}

Adquirir tokens exclusivos para aplicativos

Use CreateAuthorizationHeaderForAppAsync para obter um token somente de aplicativo para cenários de plano de fundo ou daemon.

public async Task<IActionResult> GetBackgroundData()
{
    // Get app-only authorization header
    var authHeader = await _headerProvider.CreateAuthorizationHeaderForAppAsync(
        scopes: new[] { "api://my-api/.default" });
    
    using var client = new HttpClient();
    client.DefaultRequestHeaders.Add("Authorization", authHeader);
    
    var response = await client.GetAsync("https://api.example.com/background");
    var data = await response.Content.ReadFromJsonAsync<BackgroundData>();
    
    return Ok(data);
}

Integrar com bibliotecas HTTP personalizadas

O exemplo a seguir usa IAuthorizationHeaderProvider com uma biblioteca HTTP de terceiros.

public async Task<IActionResult> CallWithRestSharp()
{
    var authHeader = await _headerProvider.CreateAuthorizationHeaderForUserAsync(
        scopes: new[] { "api://my-api/read" });
    
    // Example with RestSharp
    var client = new RestClient("https://api.example.com");
    var request = new RestRequest("data", Method.Get);
    request.AddHeader("Authorization", authHeader);
    
    var response = await client.ExecuteAsync<MyData>(request);
    
    return Ok(response.Data);
}

Configurar opções avançadas

O exemplo a seguir cria um AuthorizationHeaderProviderOptions objeto com escopos explícitos e configurações de aquisição de token.

public async Task<IActionResult> GetDataWithOptions()
{
    var options = new AuthorizationHeaderProviderOptions
    {
        Scopes = new[] { "api://my-api/read" },
        RequestAppToken = false,
        AcquireTokenOptions = new AcquireTokenOptions
        {
            AuthenticationOptionsName = JwtBearerDefaults.AuthenticationScheme,
            ForceRefresh = false,
            Claims = null
        }
    };
    
    var authHeader = await _headerProvider.CreateAuthorizationHeaderAsync(options);
    
    using var client = new HttpClient();
    client.DefaultRequestHeaders.Add("Authorization", authHeader);
    
    var response = await client.GetAsync("https://api.example.com/data");
    var data = await response.Content.ReadFromJsonAsync<MyData>();
    
    return Ok(data);
}

Comparar abordagens

Use os critérios a seguir para selecionar a melhor abordagem para seu cenário.

Use IDownstreamApi quando:

Chamando APIs REST padrão
Deseja uma abordagem baseada em configuração
Precisa de serialização/desserialização automática
Deseja código mínimo
Seguindo os padrões de Microsoft.Identity.Web

Exemplo:

var product = await _api.GetForUserAsync<Product>("MyApi", "products/123");

Use MicrosoftIdentityMessageHandler quando:

Precisa de recursos completos do HttpClient
Deseja compor vários manipuladores
Usando padrões de HttpClientFactory
Precisa de acesso ao HttpResponseMessage
Integração com o código HttpClient existente

Exemplo:

var response = await _httpClient.GetAsync("api/products/123");
var product = await response.Content.ReadFromJsonAsync<Product>();

Use IAuthorizationHeaderProvider quando:

Precisa de controle total sobre solicitações HTTP
Usando bibliotecas HTTP personalizadas
Criando abstrações personalizadas
Não é possível usar HttpClientFactory
Precisa construir solicitações manualmente

Exemplo:

var authHeader = await _headerProvider.CreateAuthorizationHeaderForUserAsync(scopes);
client.DefaultRequestHeaders.Add("Authorization", authHeader);

Gerenciar erros

As seções a seguir mostram padrões de tratamento de erros para cada abordagem.

Manipular os erros do IDownstreamApi

O exemplo a seguir captura desafios de consentimento, erros de status HTTP e exceções gerais.

try
{
    var data = await _api.GetForUserAsync<MyData>("MyApi", "data");
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // User needs to consent
    _logger.LogWarning(ex, "Consent required for scopes: {Scopes}", string.Join(", ", ex.Scopes));
    throw; // Let ASP.NET Core handle consent flow
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    return NotFound("Resource not found");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
    return Unauthorized("API returned 401");
}
catch (Exception ex)
{
    _logger.LogError(ex, "API call failed");
    return StatusCode(500, "An error occurred");
}

Manipulação de erros do MicrosoftIdentityMessageHandler

O exemplo a seguir inspeciona o código de status de resposta e registra informações detalhadas de erro.

try
{
    var response = await _httpClient.GetAsync("api/data");
    
    if (!response.IsSuccessStatusCode)
    {
        var error = await response.Content.ReadAsStringAsync();
        _logger.LogError("API returned {StatusCode}: {Error}", response.StatusCode, error);
        return StatusCode((int)response.StatusCode, error);
    }
    
    var data = await response.Content.ReadFromJsonAsync<MyData>();
    return Ok(data);
}
catch (HttpRequestException ex)
{
    _logger.LogError(ex, "HTTP request failed");
    return StatusCode(500, "Failed to call API");
}

Seguir as práticas recomendadas

Aplique esses padrões para criar integrações de API confiáveis e manteneveis.

1. Configurar valores de tempo limite

Defina tempos limite explícitos para impedir que as solicitações sejam suspensas indefinidamente.

builder.Services.AddDownstreamApi("MyApi", options =>
{
    options.BaseUrl = "https://api.example.com";
    options.HttpClientName = "MyApi";
});

builder.Services.AddHttpClient("MyApi", client =>
{
    client.Timeout = TimeSpan.FromSeconds(30);
});

2. Usar clientes tipados

Envolver IDownstreamApi em uma interface de cliente tipada para melhorar a testabilidade e o encapsulamento.

public interface IProductApiClient
{
    Task<List<Product>> GetProductsAsync();
    Task<Product> GetProductAsync(int id);
    Task<Product> CreateProductAsync(Product product);
}

public class ProductApiClient : IProductApiClient
{
    private readonly IDownstreamApi _api;
    
    public ProductApiClient(IDownstreamApi api)
    {
        _api = api;
    }
    
    public Task<List<Product>> GetProductsAsync() =>
        _api.GetForUserAsync<List<Product>>("MyApi", "products");
    
    public Task<Product> GetProductAsync(int id) =>
        _api.GetForUserAsync<Product>("MyApi", $"products/{id}");
    
    public Task<Product> CreateProductAsync(Product product) =>
        _api.PostForUserAsync<Product, Product>("MyApi", "products", product);
}

// Register
builder.Services.AddScoped<IProductApiClient, ProductApiClient>();

3. Detalhes da solicitação de log

Acompanhe a duração da chamada à API e os resultados para identificar gargalos de desempenho.

public async Task<IActionResult> GetDataWithLogging()
{
    _logger.LogInformation("Calling MyApi for data");
    
    var stopwatch = Stopwatch.StartNew();
    
    try
    {
        var data = await _api.GetForUserAsync<MyData>("MyApi", "data");
        
        stopwatch.Stop();
        _logger.LogInformation("API call succeeded in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
        
        return Ok(data);
    }
    catch (Exception ex)
    {
        stopwatch.Stop();
        _logger.LogError(ex, "API call failed after {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
        throw;
    }
}

Implementar o suporte ao OWIN

Use a integração do OWIN quando o seu aplicativo estiver sendo executado no pipeline clássico do ASP.NET em vez do ASP.NET Core.

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Owin;

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
      OwinTokenAcquirerFactory factory = TokenAcquirerFactory.GetDefaultInstance<OwinTokenAcquirerFactory>();

      app.AddMicrosoftIdentityWebApp(factory);
      factory.Services
        .AddDownstreamApis(factory.Configuration.GetSection("DownstreamAPI"))
        .AddInMemoryTokenCaches();
        factory.Build();
    }
}

Próximas etapas: examine a documentação principal para árvore de decisão e comparação de todas as abordagens.