Christopher
Christopher

Reputation: 41

Design pattern to consume REST API

I am making a DLL to consume a REST API in aspnetcore.

Ideally, I would like it to be accessed this way:

API api = new API(clientInfo);
api.Module.Entity.Action(params);

But I am struggling to make that a reality. I can't make anything static because more than 1 session might be instanced at the same time. I can't pass the session around except by reference otherwise session state(cookies etc.) might change in the copy. Is there a design pattern I should be using?

public class API
{
    private Session _session;
    public API(ClientInfo clientInfo)
    {
        _session = new Session(clientInfo);
    }
}

The session serves as middleware for the client, stores login data in case the client needs to repeat login, handles some errors/retries and exposes client methods.

public class Session
{
    private Client _client;
    private string _path;
    public Session(ClientInfo clientInfo)
    {
        _client= new Client(clientInfo);
        _path = clientInfo.Path;
    }
    public HttpResponseMessage Get(string name, string arguments = "")
    {
        return _client.Get(_path, name, arguments);
    }
    ...
}

The client actually performs the calls.

public class Client
{
    public HttpResponseMessage Get(string path, string endpointName, string arguments)
    {
        return GetClient().GetAsync(path + endpointName + arguments).Result;
    }
    private HttpClient GetClient(){...}
    ...
}

Upvotes: 4

Views: 6187

Answers (2)

Chris Pratt
Chris Pratt

Reputation: 239290

Personally, I just build a simple client for my APIs, with methods corresponding to the endpoints the API has:

public class FooClient
{
    private readonly HttpClient _httpClient;

    public FooClient(HttpClient httpClient)
    {
        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
    }

    public async Task<GetFooResult> Get(int id)
    {
        ...
    }

    // etc
}

The HttpClient dependency is provided by registering a typed client in Startup.cs:

services.AddHttpClient<FooClient>(c =>
{
    // configure client
});

And I add an IServiceCollection extension to encapsulate this and any other setup logic:

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddFooClient(this IServiceCollection services, string uri)
    {
        ...
    }
}

Then, in my Startup, I can simply do something like:

services.AddFooClient(Configuration.GetValue<string>("FooUri"));

This is extremely helpful for setting up automatic error handling, retry policies, etc. via Polly, as you can then set up all that configuration just once in the extension.

Now, getting to your issue of persisting things like auth tokens, you have a few possibilities. I tend to prefer persisting auth tokens as claims, in which case you can simply retrieve the claim and pass it into methods on your client that need it:

var foo = await _fooClient.Get(fooId, User.FindFirstValue("FooAuthToken"));

If you handle things that way, you can bind your client in any scope, including singleton.

An alternative approach would be to actually persist the auth token in your client, but this has to be done with care. You should definitely avoid using singleton scope, unless you're employing something like a ConcurrentDictionary and even then, ensuring that the right token is always used could be a bit gnarly.

Assuming you're using a request scope, you can store the token directly as an ivar or something, but you'd still need to persist it some place else beyond that, or you'd still need to re-auth for each request. If you were to store it in the session, for example, then you could do something like:

services.AddScoped<FooClient>(p =>
{
    var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
    var httpContextAccessor = p.GetRequiredService<IHttpContextAccessor>();

    var httpClient = httpClientFactory.Create("ClientName");
    var session = httpContextAccessor.HttpContext.Session;

    var client = new FooClient(httpClient);
    client.SetAuthToken(session["FooAuthToken"]);
});

However, even then, I'd still say it's better to pass the auth token into the method than do any of this. It's more explicit about which actions require auth versus those that do not, and you always know exactly what's coming from where.

Upvotes: 4

Robert Perry
Robert Perry

Reputation: 1956

One of your biggest problems will be the reuse of the HttpClient. This is a known problem for "pre-Core" days. Luckily, its been addressed and as of Net Core 2.1 we now have an HttpClientFactory which allows you to spin up as manage HttpClients as you need and they're handled for you as part of the framework.

https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore

With this in mind, theres nothing stopping you from using DI to inject an IHttpClientFactory which will provide you with the necessary access to the pipeline you need. Other than that, its entirely up to you how you design the code which "manages" your access to the REST resources. Maybe some sort of Repository Pattern? (Purely guess work really without knowing your architecture etc)

Upvotes: 1

Related Questions