Je souhaite créer dans mon application principale .net un scénario MVC lorsque je peux injecter dans mon contrôleur 2 implémentations différentes d'une classe abstraite. Ces implémentations appellent leur API relative externe. Peut-être que l'architecture est fausse et donc je vous demande des suggestions, mais suivez-moi d'abord dans mes pensées s'il vous plaît. Je crée une classe abstraite générale. Pourquoi abstrait? Parce que la méthode / les propriétés de base pour appeler une API sont les mêmes pour tout le monde. Dans mon cas jusqu'à présent, je n'ai qu'un HttpClient.
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly ApiCaller _apiCaller;
public BooksAndAlbumsController(ApiCaller apiCaller)
{
_apiCaller = apiCaller;
}
[HttpPost]
public void Post([FromBody] string value)
{
_apiCaller.GetApiResultAsync() //but I want to use both my apiCallers
}
}
Ensuite, j'aurai mes deux classes différentes Api1Service et Api2Service qui s'étendent ApiCaller et auront leurs propres façons d'appeler leurs API relatives.
public class Api1Service : ApiCaller
{
public Api1Service(string apiUrl) : base(apiUrl)
{
}
public override string GetApiResultAsync()
{
...
}
}
public class Api2Service : ApiCaller
{
public Api2Service(string apiUrl) : base(apiUrl)
{
}
public override string GetApiResultAsync()
{
...
}
}
Maintenant, dans mon contrôleur, je veux injecter les deux istances puisque je veux utiliser les deux services métier ... mais je ne sais pas si c'est possible.
public abstract class ApiCaller
{
protected static HttpClient client;
protected static string ApiUrl;
public ApiCaller(string apiUrl)
{
client = new HttpClient();
ApiUrl = apiUrl;
}
public abstract string GetApiResultAsync();
}
Donc, d'une manière ou d'une autre, dans mon conteneur, j'aurais besoin d'enregistrer les deux implémentations de ma classe abstraite . Comment puis-je atteindre cet objectif? Si vous voyez des défauts dans mon architecture, faites-le moi savoir!
3 Réponses :
Vous pouvez injecter un IEnumerable puis les utiliser tous les deux.
Enregistrez les deux ApiCallers dans le conteneur, puis injectez le IEnumerable dans votre contrôleur.
Quelque chose comme ceci:
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly Api1Service _api1Service;
private readonly Api2Service _api2Service;
public MyController(Api1Service api1Service, Api2Service api2Service)
{
_api1Service = api1Service;
_api2Service = api2Service;
}
[HttpPost]
public async Task Post([FromBody] string value)
{
var result1 = await apiService1.GetApiResultAsync();
var result2 = await apiService2.GetApiResultAsync();
}
}
MonContrôleur
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Api1Service>();
services.AddSingleton<Api2Service>();
}
Une autre possibilité est d'enregistrer Api1Service et Api2Service, puis de les injecter tous les deux comme ceci. Elle ne sera cependant pas aussi dynamique / flexible que la première solution.
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly IEnumerable<ApiCaller> _apiCallers;
public MyController(IEnumerable<ApiCaller> apiCallers)
{
_apiCallers = apiCallers;
}
[HttpPost]
public async Task Post([FromBody] string value)
{
// Loop through one by one or call them in parallel, up to you.
foreach(var apiCaller in _apiCallers)
{
var result = await apiCaller.GetApiResultAsync();
}
}
}
MyController
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ApiCaller, Api1Service>();
services.AddSingleton<ApiCaller, Api2Service>();
}
Vérifiez le Motif composite .
public sealed class CompositeApiCaller : ApiCaller
{
private const string SEPARATION_STRING = Environnement.NewLine;
private ApiCaller[] _apiCallers;
public CompositeApiCaller(params ApiCaller[] apiCallers)
{
_apiCallers = apiCallers;
}
public override string GetApiResultAsync()
{
var builder = new StringBuilder();
for (int i = 0; i < _apiCallers.Length; i++)
{
if (i > 0)
builder.Append(SEPARATION_STRING);
builder.Append(apiCaller.GetApiResultAsync());
}
return builder.ToString();
}
}
Vous pouvez utiliser NamedHttpClients et une fabrique
public class HomeController : Controller {
private readonly IFanApiClientFactory _fanApiClientFactory;
public HomeController(IFanApiClientFactory fanApiClientFactory) {
_fanApiClientFactory = fanApiClientFactory;
}
public async Task<IActionResult> Index() {
var starWarsApiClient = _fanApiClientFactory.CreateStarWarsApiClient();
var starTrekApiClient = _fanApiClientFactory.CreateStarTrekApiClient();
var person1 = await starTrekApiClient.GetMostImportantPerson();
var person2 = await starWarsApiClient.GetMostImportantPerson();
return View();
}
}
puis créer une fabrique qui sera injectée dans le contrôleur
public class StarWarsApiClient : IFanApiClient {
private readonly HttpClient _client;
public StarWarsApiClient(HttpClient client) {
_client = client;
}
public async Task<string> GetMostImportantPerson() {
var response = await _client.GetAsync("people/1");
return await response.Content.ReadAsStringAsync();
}
}
public class StarTrekApiClient : IFanApiClient {
private readonly HttpClient _client;
public StarTrekApiClient(HttpClient client) {
_client = client;
}
public async Task<string> GetMostImportantPerson() {
var response = await _client.GetAsync("character/CHMA0000126904");
return await response.Content.ReadAsStringAsync();
}
}
enregistrer le factory
services.AddSingleton<IFanApiClientFactory, FanApiClientFactory>();
implémentez au moins les clients API concrets
public interface IFanApiClientFactory {
IFanApiClient CreateStarWarsApiClient();
IFanApiClient CreateStarTrekApiClient();
}
public class FanApiClientFactory : IFanApiClientFactory {
private readonly IHttpClientFactory _httpClientFactory;
public FanApiClientFactory(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory;
}
public IFanApiClient CreateStarWarsApiClient() {
var client = _httpClientFactory.CreateClient(NamedHttpClients.StarWarsApi);
return new StarWarsApiClient(client);
}
public IFanApiClient CreateStarTrekApiClient() {
var client = _httpClientFactory.CreateClient(NamedHttpClients.StarTrekApi);
return new StarTrekApiClient(client);
}
}
et enfin le contrôleur
public static class NamedHttpClients {
public const string StarTrekApi = "StarTrekApi";
public const string StarWarsApi = "StarWarsApi";
}
services.AddHttpClient(NamedHttpClients.StarTrekApi, client => {
client.BaseAddress = new Uri("http://stapi.co/api/v1/rest");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("apiClientTest", "1.0"));
});
services.AddHttpClient(NamedHttpClients.StarWarsApi, client => {
client.BaseAddress = new Uri("https://swapi.co/api/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("apiClientTest", "1.0"));
});