Chiamare API personalizzate

Microsoft. Identity.Web offre tre approcci per chiamare le proprie API protette: IDownstreamApi, IAuthorizationHeaderProvider e MicrosoftIdentityMessageHandler.

Scegliere un approccio

Quando si chiamano API REST personalizzate, sono disponibili tre opzioni principali a seconda delle esigenze:

Avvicinarsi Complessità Flessibilità Caso d'uso
IDownstreamApi Low Medium API REST standard con configurazione
MicrosoftIdentityMessageHandler Medium Alto HttpClient con Iniezione delle Dipendenze e pipeline componibile
IAuthorizationHeaderProvider Alto Molto alto Controllo completo sulle richieste HTTP

Usare IDownstreamApi per scenari standard

IDownstreamApi offre un approccio semplice basato sulla configurazione per chiamare le API REST con acquisizione automatica dei token.

Installare il pacchetto

Aggiungere il pacchetto NuGet DownstreamApi al progetto.

dotnet add package Microsoft.Identity.Web.DownstreamApi

Configurare le impostazioni dell'API

Definire l'API in 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
    }
  }
}

Configurare ASP.NET Core

Registrare l'autenticazione e i servizi API downstream nel Program.cs file.

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

Eseguire operazioni API di base

Il controller seguente illustra le operazioni GET, POST, PUT e DELETE su un'API downstream configurata.

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

Configurare le opzioni IDownstreamApi avanzate

Utilizzare queste opzioni per personalizzare le intestazioni delle richieste, sovrascrivere la configurazione, aggiungere parametri di query e leggere le intestazioni di risposta.

Aggiungere intestazioni e opzioni personalizzate

Nell'esempio seguente vengono aggiunte intestazioni HTTP personalizzate alla richiesta in uscita.

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

Eseguire l'override della configurazione per richiesta

Nell'esempio seguente viene eseguito l'override dell'URL di base, degli ambiti e del tipo di token per una singola richiesta.

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

Aggiungere parametri di query

Nell'esempio seguente viene compilata una richiesta di ricerca con parametri di query nel percorso 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);
}

È anche possibile usare il dizionario options.ExtraQueryParameters.

Gestire le intestazioni di risposta

L'esempio seguente legge le informazioni sul limite di velocità dalle intestazioni di risposta.

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

Acquisire token solo app con IDownstreamApi

Usare GetForAppAsync per chiamare un'API con autorizzazioni dell'applicazione anziché autorizzazioni utente delegate.

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

Usare MicrosoftIdentityMessageHandler per l'integrazione di HttpClient

MicrosoftIdentityMessageHandler aggiunge automaticamente l'autenticazione Microsoft Entra alla pipeline HttpClient.

Identificare quando usare MicrosoftIdentityMessageHandler

  • È necessario un controllo granulare sulle richieste HTTP
  • Si desidera comporre più gestori di messaggi
  • Stai integrando con il codice esistente basato su HttpClient
  • È necessario accedere a HttpResponseMessage non elaborato

Configurare gli overload di MicrosoftIdentityMessageHandler

MicrosoftIdentityMessageHandler è un DelegatingHandler che aggiunge autenticazione alle richieste HttpClient. Usare questo gestore quando sono necessarie funzionalità HttpClient complete con acquisizione automatica dei token. I metodi di estensione AddMicrosoftIdentityMessageHandler offrono un modo flessibile per configurare HttpClient con l'autenticazione automatica Microsoft Entra ID:

  • Senza parametri: per la flessibilità di configurazione per richiesta
  • Istanza delle opzioni: per oggetti opzioni preconfigurati
  • Delegato azione: per la configurazione inline (più comune)
  • IConfiguration: per la configurazione da appsettings.json

Scegli l'overload più adatto al tuo scenario e usufruisci dell'autenticazione automatica per le chiamate API a valle.

Usare l'overload senza parametri per la configurazione per richiesta

Configurare opzioni di autenticazione specificamente per ogni richiesta con questo overload.

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

Passare un'istanza di opzioni preconfigurate

Usare questo overload quando si dispone di un oggetto opzioni preconfigurato.

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

Configurare in linea con un delegato di azione

Usare questo sovraccarico per la configurazione inline, lo scenario più comune.

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

Caricare la configurazione da appsettings.json

Usa questo sovraccarico per associare le impostazioni direttamente dal tuo file di configurazione.

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

Esaminare gli esempi di configurazione

Questi esempi illustrano modelli di configurazione comuni per il gestore di messaggi.

Creare un semplice client API Web

L'esempio seguente registra e usa un client API meteo.

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

Configurare più client API

L'esempio seguente registra due client API separati con ambiti e tipi di token diversi.

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

Caricare opzioni complesse da appsettings.json

L'esempio seguente associa più configurazioni API da una sezione di configurazione condivisa.

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

Eseguire l'override delle opzioni per richiesta

È possibile eseguire l'override delle opzioni predefinite per ogni richiesta usando il metodo di WithAuthenticationOptions estensione.

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

Implementare scenari avanzati

Le sezioni seguenti illustrano l'identità dell'agente, la composizione del gestore e la gestione automatica delle sfide.

Configurare l'identità dell'agente

Usare l'identità dell'agente quando l'applicazione deve agire per conto di un'altra applicazione:

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

Comporre con altri gestori

Collegare più gestori nella pipeline per aggiungere log, tentativi o altri interessi trasversali.

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

Gestire le richieste WWW-Authenticate

Il gestore elabora automaticamente le sfide WWW-Authenticate per gli scenari di Accesso Condizionale.

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

Gestire gli errori

L'esempio seguente rileva separatamente gli errori http e di autenticazione quando si chiama un'API tramite il gestore di messaggi.

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

Usare IAuthorizationHeaderProvider per il controllo massimo

IAuthorizationHeaderProvider consente di accedere direttamente alle intestazioni di autorizzazione per il controllo completo sulle richieste HTTP.

Identificare quando usare IAuthorizationHeaderProvider

  • È necessario il controllo completo sulla costruzione di richieste HTTP
  • Stai integrandoti con API HTTP non standard
  • È necessario usare HttpClient senza DI
  • Si stanno creando astrazioni HTTP personalizzate

Eseguire operazioni di base

Il controller seguente recupera un'intestazione di autorizzazione e la collega a una richiesta HTTP manuale.

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

Acquisire token esclusivi per app

Usare CreateAuthorizationHeaderForAppAsync per ottenere un token solo per app per scenari daemon o in background.

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

Integrazione con librerie HTTP personalizzate

L'esempio seguente usa IAuthorizationHeaderProvider con una libreria HTTP di terze parti.

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

Configurare le opzioni avanzate

Nell'esempio seguente viene creato un AuthorizationHeaderProviderOptions oggetto con ambiti espliciti e impostazioni di acquisizione dei 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);
}

Confrontare gli approcci

Usare i criteri seguenti per selezionare l'approccio migliore per lo scenario in uso.

Usare IDownstreamApi quando:

Utilizzo delle API REST standard
Si vuole un approccio basato sulla configurazione
È necessaria la serializzazione/deserializzazione automatica
Si vuole un codice minimo
Seguendo i modelli di Microsoft.Identity.Web

Esempio:

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

Usare MicrosoftIdentityMessageHandler quando:

Sono necessarie funzionalità HttpClient complete
Vuoi comporre più handler
Uso dei modelli HttpClientFactory
È necessario accedere a HttpResponseMessage
Integrazione con il codice HttpClient esistente

Esempio:

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

Usa il provider IAuthorizationHeader quando:

È necessario il controllo completo sulle richieste HTTP
Uso di librerie HTTP personalizzate
Compilazione di astrazioni personalizzate
Non è possibile usare HttpClientFactory
Necessità di costruire manualmente le richieste

Esempio:

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

Gestire gli errori

Le sezioni seguenti illustrano i modelli di gestione degli errori per ogni approccio.

Gestire gli errori IDownstreamApi

L'esempio seguente rileva i problemi di consenso, gli errori di stato HTTP e le eccezioni generali.

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

Gestire gli errori di MicrosoftIdentityMessageHandler

L'esempio seguente esamina il codice di stato della risposta e registra informazioni dettagliate sull'errore.

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

Seguire le migliori pratiche

Applicare questi modelli per creare integrazioni api affidabili e gestibili.

1. Configurare i valori di timeout

Impostare timeout espliciti per impedire che le richieste vengano sospese per un periodo illimitato.

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. Utilizzare client tipizzato

Avvolgere IDownstreamApi in un'interfaccia tipizzata del client per migliorare la testabilità e l'incapsulamento.

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. Registrare i dettagli della richiesta

Tenere traccia dei risultati e della durata delle chiamate API per identificare i colli di bottiglia delle prestazioni.

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

Implementare il supporto OWIN

Usare l'integrazione OWIN quando l'applicazione viene eseguita nella pipeline di ASP.NET classica anziché 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();
    }
}

Passaggi successivi: esaminare la documentazione principale per l'albero delle decisioni e il confronto di tutti gli approcci.