Author : MD TAREQ HASSAN | Updated : 2020/10/21

Options

Vanilla HttpClient vs Flurl

Feature Vanilla HttpClient Flurl
Implementation by .Net Framework FlurlClient is a lightweight wrapper around HttpClient and is tightly bound to its lifetime
Instance creation IHttpClientFactory IFlurlClientFactory
(DefaultFlurlClientFactory, PerBaseUrlFlurlClientFactory)
Lifecycle HttpClient is intended to be instantiated once and re-used throughout the life of an application Fluent methods will create an HttpClient lazily, cache it, and reuse it for every call to the same host
Usage To consume APIs only URL builder and http client
License Same as .Net Framework MIT License

Flurl

Usage

Basic example

using Flurl;
using Flurl.Http;

//
// Example 1
//
var result = await baseUrl.AppendPathSegment("endpoint").GetAsync();


//
// Example 2
//
var person = await "https://api.com"
    .AppendPathSegment("person")
    .SetQueryParams(new { a = 1, b = 2 })
    .WithOAuthBearerToken("my_oauth_token")
    .PostJsonAsync(new
    {
        first_name = "Claire",
        last_name = "Underwood"
    })
    .ReceiveJson<Person>();

See: https://flurl.dev/docs/fluent-http/

Lifecycle

Official docs: https://flurl.dev/docs/client-lifetime/

Program.cs (Blazor WebAssembly)

namespace BlazorWebAssemblyNet5
{
	public class Program
	{
		public static async Task Main(string[] args)
		{
			var builder = WebAssemblyHostBuilder.CreateDefault(args);
			builder.RootComponents.Add<App>("#app");

			// builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
			builder.Services..AddSingleton<IFlurlClientFactory, PerBaseUrlFlurlClientFactory>();
			
           // ... ... ...

			await builder.Build().RunAsync();
		}
	}
}

FooService.cs

public class FooService : IFooService
{
    private readonly IFlurlClient _flurlClient;

    public FooService(IFlurlClientFactory flurlClientFactory) {
	
        _flurlClient = flurlClientFactory.Get(SERVICE_BASE_URL);
		
        // configure _flurlClient as needed
    }

    public Task<Thing> GetThingAsync(int id) {
	
        return _flurlClient.Request("things", id).GetAsync<Thing>();
    }
	
    // ... ... ...
}

Vanilla HttpClient

Setup for dependency injection

appsettings.json

{
  "APIHost": "https://localhost:44321/api/",
}

Constants.cs

public static class Constants
{
	public static readonly string KEY_API_BASE_URI = "APIHost";
}

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
	// ... ... ...

	// Configure HTTP Client
	var apiAddress = Configuration[Constants.KEY_API_BASE_URI];
	Uri apiBaseUri = new Uri(apiAddress);

	HttpClient apiClient = new HttpClient();
	apiClient.BaseAddress = apiBaseUri;
	var servicePoint = ServicePointManager.FindServicePoint(apiBaseUri);
	servicePoint.ConnectionLeaseTimeout = 60000;

	services.AddSingleton<HttpClient>(apiClient);
}

Usage in controller by dependency injection

ProductController .cs

using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace Foo.Controllers
{
    public class ProductController : Controller
    {
        HttpClient client;

        public ProductController(HttpClient apiClient)
        {
            client = apiClient;
        }

        public async Task<IActionResult> Index()
        {
            var response = await client.GetStringAsync("product");
            var products = JArray.Parse(response);
            return View(products);
        }

        public async Task<IActionResult> Detail(int id)
        {
            var response = await client.GetStringAsync($"product/{id}" );

            var product = JsonConvert.DeserializeObject<Models.Product>(response);
			
            return View(product);
        }
    }
}

public class Product
{
	public int ID { get; set; }
	public string Name { get; set; }
	public string Description { get; set; }

	public string ImageTitle { get; set; }
	public string Image { get; set; }
}

Json Serialization

To serialize

using System.Text.Json;
using System.Text.Json.Serialization;

var jsonStr = JsonSerializer.Serialize(MyObject)

To deserialize

var weatherForecast = JsonSerializer.Deserialize<MyObject>(jsonStr);

Using IHttpClientFactory - Typed Client

RepoService.cs

public class RepoService
{
    private readonly HttpClient _httpClient;

    public RepoService(HttpClient client)
    {
        _httpClient = client;
    }

    public async Task<IEnumerable<string>> GetRepos()
    {
        var response = await _httpClient.GetAsync("aspnet/repos");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync(); // inline 'using' => C# 8.0+
        return await JsonSerializer.DeserializeAsync<IEnumerable<string>>(responseStream);
    }
}

Startup.ConfigureServices(...)

services.AddHttpClient<RepoService>(repoClient =>
{
    repoClient.BaseAddress = new Uri("https://api.github.com/");
    repoClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    repoClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

Usage

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            var issues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            // ... ... ...
        }
    }
}

See:

Querying CosmosDB

Refit

Generating http client with NSwag