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