Code Monkey home page Code Monkey logo

fluenthttpclient's Introduction

FluentHttpClient is a modern async HTTP client for REST APIs. Its fluent interface lets you send an HTTP request and parse the response in one go — hiding away the gritty details like deserialisation, content negotiation, optional retry logic, and URL encoding:

Blog result = await new FluentClient("https://example.org/api")
   .GetAsync("blogs")
   .WithArgument("id", 15)
   .WithBearerAuthentication(token)
   .As<Blog>();

Designed with discoverability and extensibility as core principles, just autocomplete to see which methods are available at each step.

Install

Install it from NuGet:

Install-Package Pathoschild.Http.FluentClient

The client works on any modern platform (including Linux, Mac, and Windows):

platform min version
.NET Framework 4.5.2
.NET Core 1.0
.NET Standard 1.3
Universal Windows Platform 10

Use

Basic usage

You start by creating a client for an API. You can use this client for one request, or reuse it for many requests for improved performance using its built-in connection pool.

IClient client = new FluentClient("https://example.org/api/");

Then you just chain methods to set up the request and get the response. For example, here's a GET request which returns an Item model (automatically deserialised based on content negotiation):

Item item = await client
    .GetAsync("items/14")
    .As<Item>();

You can get the response as a model, array of models, bytes, string, or stream:

string content = await client
    .GetAsync("items/14")
    .AsString();

If you don't need the response content, you can just wait for the request to complete.

await client.PostAsync("items", new Item());

Request options

You can quickly configure complex requests with the fluent methods. Here's a more complicated example:

Message[] messages = await client
    .GetAsync("messages/latest")
    .WithHeader("Content-Type", "application/json")
    .WithArguments(new { id = 14, tenant = "acme" })
    .WithBearerAuthentication(token)
    .AsArray<Message>();

Get response data

The above examples all parse the response directly, but sometimes you want to peek at the HTTP metadata:

IResponse response = await client.GetAsync("messages/latest");
if (response.Status == HttpStatusCode.OK)
   return response.AsArray<T>();

Error handling

By default the client will throw ApiException if the server returns an error code:

try
{
    await client.Get("items");
}
catch(ApiException ex)
{
    string responseText = await ex.Response.AsString();
    throw new Exception($"The API responded with HTTP {ex.Response.Status}: {responseText}");
}

If you don't want that, you can easily:

  • disable it for one request:

    IResponse response = await client
        .GetAsync("items")
        .WithHttpErrorAsException(false);
  • disable it for all requests:

    client.SetHttpErrorAsException(false);
  • use your own error filter.

Advanced features

Custom formats

The client supports JSON and XML out of the box. If you need more, you can...

  • Add any of the existing media type formatters:

    client.Formatters.Add(new YamlFormatter());
  • Add the included BSON formatter:

    client.Formatters.Add(new BsonFormatter());
  • Create your own by subclassing MediaTypeFormatter (optionally using the included MediaTypeFormatterBase class).

Custom retry / coordination

The client won't retry failed requests by default, but that's easy to configure:

client
    .SetRequestCoordinator(
        maxRetries: 3,
        shouldRetry: request => request.StatusCode != HttpStatusCode.OK,
        getDelay: (attempt, response) => return TimeSpan.FromSeconds(attempt) // 1, 2, and 3 seconds
    );

If that's not enough, implementing IRequestCoordinator lets you control how the client dispatches requests. (You can only have one request coordinator on the client; you should use HTTP filters instead for most overrides.)

For example, here's a simple retry coordinator using Polly:

/// <summary>A request coordinator which retries failed requests with a delay between each attempt.</summary>
public class RetryCoordinator : IRequestCoordinator
{
    /// <summary>Dispatch an HTTP request.</summary>
    /// <param name="request">The response message to validate.</param>
    /// <param name="dispatcher">Dispatcher that executes the request.</param>
    /// <returns>The final HTTP response.</returns>
    public async Task<HttpResponseMessage> ExecuteAsync(IRequest request, Func<IRequest, Task<HttpResponseMessage>> dispatcher)
    {
        int[] retryCodes = { 408, 500, 502, 503, 504 };
        return Policy
            .HandleResult<HttpResponseMessage>(request => retryCodes.Contains((int)request.StatusCode))
            .Retry(3, async () => await send(request));
    }
}

...and here's how you'd set it:

client.SetRequestCoordinator(new RetryCoordinator());

Custom filters

You can read and change the underlying HTTP requests and responses by creating IHttpFilter implementations. They can be useful for automating custom authentication or error-handling.

For example, the default error-handling is just a filter:

/// <summary>Method invoked just after the HTTP response is received. This method can modify the incoming HTTP response.</summary>
/// <param name="response">The HTTP response.</param>
/// <param name="httpErrorAsException">Whether HTTP error responses (e.g. HTTP 404) should be raised as exceptions.</param>
public void OnResponse(IResponse response, bool httpErrorAsException)
{
    if (httpErrorAsException && !response.Message.IsSuccessStatusCode)
        throw new ApiException(response, $"The API query failed with status code {response.Message.StatusCode}: {response.Message.ReasonPhrase}");
}

...which you can replace with your own:

client.Filters.Remove<DefaultErrorFilter>();
client.Filters.Add(new YourErrorFilter());

You can do much more with HTTP filters by editing the requests before they're sent or the responses before they're parsed:

/// <summary>Method invoked just before the HTTP request is submitted. This method can modify the outgoing HTTP request.</summary>
/// <param name="request">The HTTP request.</param>
public void OnRequest(IRequest request)
{
    // example only — you'd normally use a method like client.SetAuthentication(…) instead.
    request.Message.Headers.Authorization = new AuthenticationHeaderValue("token", "");
}

Custom HTTP

For advanced scenarios, you can customise the underlying HttpClient and HttpClientHandler. For example, here's how to create mock requests for unit testing using RichardSzalay.MockHttp:

// create mock
var mockHandler = new MockHttpMessageHandler();
mockHandler.When(HttpMethod.Get, "https://example.org/api/items").Respond(HttpStatusCode.OK, testRequest => new StringContent("[]"));

// create client
var client = new FluentClient("https://example.org/api", new HttpClient(mockHandler));

Cancellation token support

The client fully supports .NET cancellation tokens if you need to abort requests:

var tokenSource = new CancellationTokenSource();
await client
    .PostAsync()
    .WithCancellationToken(tokenSource.Token);
tokenSource.Cancel();

Synchronous use

The client is build around the async and await keywords, but you can use the client synchronously. That's not recommended — it complicates error-handling (e.g. errors get wrapped into AggregateException), and it's very easy to cause thread deadlocks when you do this (see Parallel Programming with .NET: Await, and UI, and deadlocks! Oh my! and Don't Block on Async Code).

If you really need to use it synchronously, you can just call the Result property:

Item item = client.GetAsync("items/14").Result;

Or if you don't need the response:

client.PostAsync("items", new Item()).AsResponse().Wait();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.