Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refit Client using a dynamic base address

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);
    }
}
like image 460
sarin Avatar asked Oct 29 '19 16:10

sarin


1 Answers

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);
    }
}
like image 196
Rik Avatar answered Oct 12 '22 00:10

Rik