Reputation: 2063
I want to build in my .net core application an MVC scenario when I can inject in my controller 2 different implementations of an abstract class. These implementations call their external relative API. Maybe the architecture is wrong and therefore I ask you suggestions but first follow me in my thoughts please. I create a general abstract class. Why abstract? Because the basic way/properties for calling an API is the same for everyone. In my case so far I only have an HttpClient.
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();
}
Afterwards I will have my two different classes Api1Service and Api2Service that extend ApiCaller and will have their own different ways of calling their relative APIs.
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()
{
...
}
}
Now, in my controller I want to inject both istances since I want to use both business services.. but I don't know if this is possible.
[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
}
}
So, somehow, in my container I would need to register both implementations of my abstract class. How can I achieve this? If you see flaws in my architecture please let me know!
Upvotes: 2
Views: 2149
Reputation: 10071
You can inject an IEnumerable<ApiCaller>
and then use them both.
Register both ApiCallers in the container and then inject the IEnumerable<ApiCaller>
in your controller.
Something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ApiCaller, Api1Service>();
services.AddSingleton<ApiCaller, Api2Service>();
}
MyController
[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();
}
}
}
Another possibility is to register the Api1Service and Api2Service and then inject them both like this. It will not be as dynamic/flexible as the first solution though.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Api1Service>();
services.AddSingleton<Api2Service>();
}
MyController
[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();
}
}
Upvotes: 5
Reputation:
You can use NamedHttpClients and a factory
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"));
});
and then create a factory which will be injected in the controller
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);
}
}
register the factory
services.AddSingleton<IFanApiClientFactory, FanApiClientFactory>();
at least implement the concrete api clients
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();
}
}
and finally the controller
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();
}
}
Upvotes: 1
Reputation: 476
Check about Composite Pattern.
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();
}
}
Upvotes: 0