Reputation: 5307
I am using Refit to call an API using a Typed Client in asp.net core 2.2 which is currently bootstrapped using a single BaseAddress from our configuration Options:
services.AddRefitClient<IMyApi>()
.ConfigureHttpClient(c => { c.BaseAddress = new Uri(myApiOptions.BaseAddress);})
.ConfigurePrimaryHttpMessageHandler(() => NoSslValidationHandler)
.AddPolicyHandler(pollyOptions);
In our Configuration json:
"MyApiOptions": {
"BaseAddress": "https://server1.domain.com",
}
In our IMyApi interface:
public IMyAPi interface {
[Get("/api/v1/question/")]
Task<IEnumerable<QuestionResponse>> GetQuestionsAsync([AliasAs("document_type")]string projectId);
}
Example Current Service:
public class MyProject {
private IMyApi _myApi;
public MyProject (IMyApi myApi) {
_myApi = myApi;
}
public Response DoSomething(string projectId) {
return _myApi.GetQuestionsAsync(projectId);
}
}
I now have the requirement to use different BaseAddresses based on data at runtime. My understanding is that Refit adds a single Instance of the HttpClient into DI and so switching BaseAddresses at runtime won't directly work in a multithreaded app. Right now it's really simple to inject an instance of IMyApi and call the interface method GetQuestionsAsync. At that point it is too late to set the BaseAddress. If I have multiple BaseAddresses, is there an easy way to dynamically select one?
Example config:
"MyApiOptions": {
"BaseAddresses": {
"BaseAddress1": "https://server1.domain.com",
"BaseAddress2": "https://server2.domain.com"
}
}
Example Future Service:
public class MyProject {
private IMyApi _myApi;
public MyProject (IMyApi myApi) {
_myApi = myApi;
}
public Response DoSomething(string projectId) {
string baseUrl = SomeOtherService.GetBaseUrlByProjectId(projectId);
return _myApi.UseBaseUrl(baseUrl).GetQuestionsAsync(projectId);
}
}
UPDATE Based on the accepted answer I ended up with the following:
public class RefitHttpClientFactory<T> : IRefitHttpClientFactory<T>
{
private readonly IHttpClientFactory _clientFactory;
public RefitHttpClientFactory(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public T CreateClient(string baseAddressKey)
{
var client = _clientFactory.CreateClient(baseAddressKey);
return RestService.For<T>(client);
}
}
Upvotes: 24
Views: 15507
Reputation: 411
I've solved this by inheriting my API interface.
Here are the interfaces:
public interface IApi1 : IApi;
public interface IApi2 : IApi;
public interface IApi
{
[Get("/Data/Foos")]
Task<List<Foo>> GetFoos();
[Get("/Data/Bars")]
Task<List<Bar>> GetBars();
}
In Program.cs
:
services
.AddRefitClient<IApi1>()
.ConfigureHttpClient(c =>
c.BaseAddress = new Uri(baseAddress1)
);
services
.AddRefitClient<IApi2>()
.ConfigureHttpClient(c =>
c.BaseAddress = new Uri(baseAddress2)
);
My controller's primary constructor:
public class SomeController(
IApi1 _api1,
IApi2 _api2)
The method that dynamically selects the first or the second API:
private IApi GetApi(bool second)
{
if (second)
{
Log.Warning("Using backup server.");
return _api2; /
}
return _api1;
}
And finally getting data:
var foos = await (GetApi(false)).GetFoos(); //gets data from endpoint 1
var bars = await (GetApi(true)).GetBars(); //gets data from endpoint 2
Upvotes: 0
Reputation: 179
Another option is to assign a fake base address in the configs and then override it using a DelegatingHandler
services
.AddRefitClient<IMyApi>()
.ConfigureHttpClient((sp, c) => c.BaseAddress = new Uri(MyDelegatingHandler.FakeBaseAddress))
.AddHttpMessageHandler<MyDelegatingHandler>()
public class MyDelegatingHandler : DelegatingHandler
{
public const string FakeBaseAddress = "http://fake.to.replace";
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if(!request.RequestUri!.AbsoluteUri.StartsWith(FakeBaseAddress))
return;
var relativeUrl = request.RequestUri!.AbsoluteUri.Substring(FakeBaseAddress.Length);
var newUri = Combine(GetDynamicBaseAddress(), relativeUrl);
request.RequestUri = new Uri(newUri);
return await base.SendAsync(request, cancellationToken);
}
private static string Combine(string baseUrl, string relativeUrl) =>
return $"{baseUrl.TrimEnd('/')}/{relativeUrl.TrimStart('/')}";
}
Upvotes: 5
Reputation: 29243
Inject a ClientFactory instead of a client:
public class ClientFactory
{
public IMyApi CreateClient(string url) => RestService.For<IMyApi>(url);
}
public class MyProject {
private ClientFactory _factory;
public MyProject (ClientFactory factory) {
_factory = factory;
}
public Response DoSomething(string projectId) {
string baseUrl = SomeOtherService.GetBaseUrlByProjectId(projectId);
var client = _factory.CreateClient(baseUrl);
return client.GetQuestionsAsync(projectId);
}
}
Upvotes: 11