I have some API client to make request. Those are described in startup.
Simply, is it make sense to create HttpClient via base class and calling common request methods from base. Or each controller should create own client ? Is there will be a problem ?
Base
public class BaseController : ControllerBase
{
public HttpClient client;
public BaseController(IHttpClientFactory factory, string clientName)
{
client = factory.CreateClient(clientName);
}
public async Task<IActionResult> Get(string query)
{
}
}
Foo
public class FooController : BaseController
{
public FooController(IHttpClientFactory factory) : base(factory, "fooclient")
{
}
[HttpGet]
public async Task<IActionResult> Get(int id)
{
return await Get($"Foo/Get/{id}");
}
}
There's nothing technically wrong with this approach, but it's preferable to use typed clients. The way that is done is by creating a "service" class which will own the client:
public class FooService
{
private readonly HttpClient _httpClient;
public FooService(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
...
}
Then, you register this in ConfigureServices:
services.AddHttpClient<FooService>(c =>
{
// configure your HttpClient
});
Finally, you inject this service class into your controller:
public class FooController : ControllerBase
{
private readonly FooService _fooService;
public FooController(FooService fooService)
{
_fooService = fooService ?? throw new ArgumentNullException(nameof(fooService));
}
...
}
This then serves to encapsulate your HttpClient logic. You simply add methods to the service to do things the controller needs and the service makes the actual HttpClient requests to do that. That makes it infinitely easier to change things if the API you're utilizing should change. You just change the service and you're good to go, instead of having to track down every place you used HttpClient to interact with this API, which is a much more difficult task.
Additionally, having the client be typed gives you the ability to configure it once for all, as well as add things like retry and exception handling policies in one place. Since the client is injected for a particular type (i.e. FooService) there's no magic strings for the client name that you could fat finger or otherwise mess up.
Related
In my app I will have few TypedClient services.
However, these classes will share a bunch of methods.
My solution to this is to create CustomHttpClient:
public class CustomHttpClient:HttpClient
{
//here shared methods
}
Then my typed client classes will use this derived class instead of standard HttpClient:
public class MyService : IMyService
{
public SomeService(CustomHttpClient client, IConfiguration configuration){}
}
However, if i try to register this service in startup i get an error that there is no suitable constructor for 'MyService' :
services.AddHttpClient<IMyService, MyService>();
In the documentation I have found:
A Typed Client is a class that accepts an HttpClient object (injected through its constructor) and uses it to call some remote HTTP service
Does it mean, that it cannot accept a subclass of HttpClient?
If this is the case, then my only solution would be to implement shared methods as HttpClient extension methods ( i don't really like this solution).
Is there maybe a workaround this, or extension methods are my only way out?
I have tried registering also CustomHttpClient so DI container would find it but the error is still the same.
What can you advise me?
Does it mean, that it cannot accept a subclass of HttpClient?
Yes.
If this is the case, then my only solution would be to implement shared methods as HttpClient extension methods
No. Per the docs typed clients encapsulate an HttpClient, rather than extending it. You configure the HttpClient in the constructor then add custom methods to the Typed Client that use the encapsulated HttpClient instance.
If you don't want to use the framework's pattern for HttpClient handling, you're free to create your own, but it's probably not worth the effort.
You can have typed clients that share a base class. eg
public class MyBaseTypedClient
{
public HttpClient Client { get; }
public MyBaseTypedClient(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
//other methods
}
public class MyTypedClient : MyBaseTypedClient
{
public MyTypedClient(HttpClient client) : base(client) { }
}
If you need to add common methods only you can create an Interface with default implementation of those methods, then you would just need to inherit your IMyService with that interface.
You can also have a look at the below link which has some interesting workarounds.
https://github.com/dotnet/extensions/issues/1988
I think this is what you want to do:
(1) Create your base MyBaseTypedClient as follows:
public interface IMyBaseTypedClient
{
//other methods
//like FetchAsync(), PostAsync()
}
public class MyBaseTypedClient
{
private HttpClient _client
public MyBaseTypedClient(HttpClient client)
{
_client = client;
}
//other methods
//like FetchAsync(), PostAsync()
}
(2) Then create your typed clients as follows:
public interface IServiceOne: IMyBaseTypedClient{ }
public class ServiceOne: MyBaseTypedClient, IServiceOne
{
public ServiceOne(HttpClient httpClient) : base(httpClient)
{
}
}
public interface IServiceTwo: IMyBaseTypedClient{ }
public class ServiceTwo: MyBaseTypedClient, IServiceTwo
{
public ServiceTwo(HttpClient httpClient) : base(httpClient)
{
}
}
(3) Then register as follows
services
.AddHttpClient<IServiceOne, ServiceOne>(c => c.BaseAddress = new Uri("https://serviceone.com"));
.AddHttpClient<IServiceTwo, ServiceTwo>(c => c.BaseAddress = new Uri("https://servicetwo.com");
(4) Then inject as follows
public void SomeMethodThatNeedsOne(IServiceOne serviceOne)
{
//etc
}
public void SomeMethodThatNeedsTwo(IServiceTwo serviceTwo)
{
//etc
}
I am trying to build an MVC service which calls 2 different APIs, an Amazon one and an Apple one. The code looks like this:
public abstract class ApiHttpCaller<T>
{
protected static HttpClient _client;
protected ApiHttpCaller()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public abstract Task<T> RetrieveApiResultAsync(string searchValue);
}
This ApiHttpCaller is implemented by my 2 specifics AmazonApiCaller and AppleApiCaller, let's take only one of them into account:
public class AmazonApiCaller : ApiHttpCaller<AmazonResponseModel>
{
protected static IOptions<ApiUrls> _apiUrls;
public AmazonApiCaller(IOptions<ApiUrls> apiUrls)
{
_apiUrls = apiUrls;
}
public override async Task<AmazonResponseModel> RetrieveApiResultAsync(string searchValue)
{
..logic to call the api..
string responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AmazonResponseModel>(responseBody);
}
}
as you can see, correct me if the architecture is wrong, there is an AmazonResponseModel used as generics here. As you can imagine AmazonApi and AppleApi return 2 different models. That's why my abstract parent class ApiHttpCaller uses a generics T that into the specifc AmazonApiCaller becomes an AmazonResponseModel. Such APIs are called from my controller.
[Route("api/[controller]")]
[ApiController]
public class ItemsController<T> : ControllerBase
{
private readonly IEnumerable<ApiHttpCaller<T>> _apiCallers;
[HttpPost]
public async Task<ActionResult> Post([FromBody] string value)
{
var amazonCaller = _apiCallers.First(x => x.GetType() == typeof(AmazonApiCaller));
var itemResult = await amazonCaller.RetrieveApiResultAsync(value);
..more logic to map the itemResult to a viewModel..
}
}
So, first question is: do you think it's correct to use the genercis T in the controller that then becomes a specifc type inside each api caller?
Second and more important: I don't know how to register in Startup.cs the ApiHttpCallers in such a way that they get injected properly in my controller. First guess is:
services.AddSingleton<ApiCaller<T>, AmazonApiCaller<AmazonResponseModel>>();
services.AddSingleton<ApiCaller<T>, AppleApiCaller<AppleResponseModel>>();
point is Startup.cs doesn't know anything of T .
services to be registred:
services.AddSingleton<ApiCaller<AmazonResponseModel>, AmazonApiCaller>();
services.AddSingleton<ApiCaller<AppleResponseModel>, AppleApiCaller>();
services.AddTransient(typeof(ItemsController<>));
Change the controller as follows:
public class ItemsController<T> : ControllerBase
{
private readonly ApiHttpCaller<T> _apiCaller;
public ItemsController(ApiHttpCaller<T> apicaller){
_apiCaller = apicaller;
}
[HttpPost]
public async Task<ActionResult> Post([FromBody] string value)
{
// do something with the requested API Caller
}
}
This should now inject the correct ApiCaller into your service.
Of course you need to specify the type when injecting an ItemsController:
// Constructor
public AnyClass(ItemsController<AmazonResponseModel> controller){
// _apiCaller of controller will be AmazonApiCaller
}
Or maybe use another IoC Container like ninject.
You could benefit from Features like Contextual and named Bindings, which is documented on their page.
You DI registration is incorrect here. It should be like this:
services.AddSingleton<ApiCaller<AmazonResponseModel>, AmazonApiCaller>();
services.AddSingleton<ApiCaller<AppleResponseModel>, AppleApiCaller>();
you need to specify which generic would correspond to which implementation.
Say I have a controller with an Index() method, and this controller utilizes multiple "Manager classes" that manage certain assets that need to be retrieved with an HttpClient from an API.
I've read that sharing an HttpClient with multiple calls is better than to reinstantiate it with every call to save ports.
I do however want to dispose of the HttpClient before the controller returns the view, because the view contains an entire Knockout/Typescript based front end project that handles the rest of the data (so it's basically only settings and meta data stuff).
Do I need to pass the HttpClient variable to each and every "Manager class", or does it suffice to do something like the following, and use a static HttpClient inside the classes?
public ActionResult Index()
{
using (Globals.Client = new System.Net.Http.HttpClient())
{
// do stuff like SettingManager.GetSetting("settingKey") which uses
// the Globals.Client variable
}
return View();
}
Or should I not even want to dispose the HttpClient in the first place?
One solution is to make a separate dependency responsible for managing your HttpClient. This has the side benefit of keeping your controllers from depending directly on HttpClient. Any class that depends on HttpClient becomes harder to test. It's also a maintenance issue because if you want to change the behavior you have to change it everywhere. Imagine if you decide one day that whatever you're getting from that HttpClient can be cached? You'd have to change it in lots of classes.
You can define an abstraction and implementation like this:
public interface IDoesSomething
{
string GetSetting(string key);
}
public class HttpClientDoesSomething : IDoesSomething, IDisposable
{
private readonly HttpClient _client;
private readonly string _apiUrl;
public HttpClientDoesSomething(string apiUrl)
{
_client = new HttpClient();
_apiUrl = apiUrl;
}
public string GetSetting(string key)
{
// use the client to retrieve the setting
}
public void Dispose()
{
_client?.Dispose();
}
}
Now the problem is moved out of your controller because you inject the interface:
public class MyController : Controller
{
private readonly IDoesSomething _doesSomething;
public MyController(IDoesSomething doesSomething)
{
_doesSomething = doesSomething;
}
public ActionResult Index()
{
var setting = _doesSomething.GetSetting("whatever");
// whatever else this does.
return View();
}
}
Now in your startup configuration you can register HttpClientDoesSomething as a singleton:
services.AddSingleton<IDoesSomething>(new HttpClientDoesSomething("url from settings"));
Your implementation is disposable, so if you do need to create and dispose it you will also dispose the HttpClient. But it won't be an issue because your application will keep reusing the same one.
One way is to create use class inheritance, but is there any other way I could reuse methods that I created in one controller in another controller?
EDIT: should I use a custom basecontroller like this?
public class BaseController : Controller
{
private readonly ApplicationDbContext _context;
private readonly IIdentityService _identityService;
public BaseController(ApplicationDbContext context, IIdentityService identityService)
{
_context = context;
_identityService = identityService;
}
public BaseController()
{
}
//reusable methods
public async Task<Account> GetAccount()
{
//code to do something, i.e query database
}
}
public class MyController : BaseController
{
private readonly ApplicationDbContext _context;
private readonly IIdentityService _identityService;
public MyController(ApplicationDbContext context, IIdentityService identityService)
{
_context = context;
_identityService = identityService;
}
public async Task<IActionResult> DoSomething()
{
var account = await GetAccount();
//do something
Return Ok();
}
}
There are several aspects we want to touch:
if code you have is useful in all controllers, most of the time it is good practice to create BaseApiController that will inherit from ApiController and put things that are used across all controllers there. (You also inherit your controllers from that class of course)
if code is some kind of business logic and is not strictly speaking related to handling http request one way or another ( i.e. you have model Square and you want to calculate area and return it from controller method). Things like that you want to refactor to specific service which might or might not be model, dedicated service, static class or something completely different
Generally when you find something you want to use in two places it's a candidate to be spun into it's own class. Then both controllers can use the common class.
I have started a new Web API project that requires that we switch the database the application is running to based on HTTP header information sent to the API. The application user will be identified by a HTTP header and the application should then change to use their database.
I have a base controller CrudControllerBase<T> ( to handle simple generic HTTP requests ) which creates a DataService<T> in it's constructor. All of my controllers will derive from this base controller and will have access to this DataService. The DataService is used to do common DB queries ( FindById(), FindAll(), etc. ) and more complex queries are bolted on using extension methods.
public abstract class CrudControllerBase<T> : ApiController where T : class, IEntity
{
protected IDataService<T> _dataService;
public CrudControllerBase()
{
this._dataService = new DataService<T>();
}
[HttpGet]
public virtual async Task<IHttpActionResult> Get(Guid id)
{
var model = await _dataService.FindByIdAsync(id);
return Ok<T>(model);
}
//code left out
}
public class OrdersController : CrudControllerBase<OrderItem>
{
}
and in the DataService I new up the DbContext class:
public class DataService<T> : IDataService<T> where T:class, IEntity
{
private readonly AppDbContext _context;
public DataService()
{
_context = new AppDbContext(); // need to pass in connection string
}
// code left out
}
I need to be able to pass in the connection string to the constructor of AppDbContext but in the constructor of CrudControllerBase I do not have access to the HttpRequestMessage to be able to pass this info to the DataService.
Can anyone suggest a solution ? I am quite happy to try a completely different way of doing this if someone can suggest something. Thanks !
OK so I have got this working. It may not be the best solution but it works and if anyone has any feedback / improvements then please share. Thanks to #AaronLS for pointing me in the right direction. This article also helped a lot.
The first step was to create a CustomControllerFactory that implements the IHttpControllerActivator interface. This gives you a Create method in which you can write your own code for newing up your Controllers. This is my CustomControllerFactory where I new up my controller passing in the HTTP Header as a string:
public class CustomControllerFactory : IHttpControllerActivator
{
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
var schemaKey = request.Headers.Where(k => k.Key == "schema").FirstOrDefault().Value.FirstOrDefault();
return (IHttpController)Activator.CreateInstance(controllerType, new string[] { schemaKey });
}
}
The next step is to tell the web API to use this method for instantiating controllers. To do this I added this line to my WebApiConfig class:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new CustomControllerFactory());
The last thing I needed to was add a constructor to each controller which took in the string value and passed it to the base controller
public OrdersController(String databaseName) : base(databaseName) { }
and my base controller passes the parameter to the DataService
public CrudControllerBase(String databaseName)
{
this._dataService = new DataService<T>(databaseName);
}
and my database passes in the connection string to the AppDbContext() constructor
public DataService(String databaseName)
{
this._context = new AppDbContext(BuildConnectionString(databaseName));
}
I realise there is no error handling / security checking yet but I will add that in :-)