Author : MD TAREQ HASSAN | Updated : 2020/10/21
Options
- Vanilla HttpClient
System.Net.Http
・HttpClient- Provides a class for sending HTTP requests and receiving HTTP responses from a resource identified by a URI
HttpClient
is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors- The
HttpClient
is a high-level API that wraps the lower-level functionality - Users can also configure a specific transport for HttpClient by invoking the HttpClient constructor that takes an HttpMessageHandler
- Before .Net 5.0: you can configure whether SocketsHttpHandler is used by default
- .Net 5.0+:
SocketsHttpHandler
is used and can not be changed
IHttpClientFactory
: A factory abstraction for a component that can createHttpClient
instances with custom configuration
- Http Client Libraries:
Flurl.Http
(details below)- Refit
- RestSharp
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
- As http client (API calls + URL building):
Install-Package Flurl.Http
- Stand-alone URL builder without the HTTP features:
Install-Package Flurl
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
- Set BaseUri in
appsettings.json
- During development, use dev BaseUri (i.e.
https://localhost:44321/api/
) - For production, set BaseUri in Azure App Service or set by Azure DevOps pipeline (variable translation)
- During development, use dev BaseUri (i.e.
- Configure HTTP Client in
Startup.cs
- Scope: Singleton
- Get ServicePoint and set desired properties (i.e.
servicePoint.ConnectionLeaseTimeout = 6000
)
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
- New namespace for netcore:
System.Text.Json
(aspnet core 3.0+) - https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to
- https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/
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:
- Samples : https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/fundamentals/http-requests/samples
- Named-clients : https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#named-clients
- Make requests : https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#make-post-put-and-delete-requests
Querying CosmosDB
- Select Storage account > Select target CosmosDB
- Get keys
- See: Querying Azure Cosmos DB
Refit
- PMC:
Install-Package refit
- Links:
- https://github.com/reactiveui/refit
- IHttpClientFactory with Refit:
- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#generated-clients
Install-Package Refit.HttpClientFactory
- https://www.hanselman.com/blog/UsingASPNETCore21sHttpClientFactoryWithRefitsRESTLibrary.aspx
Generating http client with NSwag
- CLI:
dotnet-nswag.dll
is a command line tool for .Net core- https://github.com/RicoSuter/NSwag/wiki/CommandLine
- CodeGeneration nuget package:
- Generates client inside project using
dotnet-nswag.dll
- PMC:
Install-Package NSwag.AspNetCore
(https://www.nuget.org/packages/NSwag.AspNetCore/)Install-Package NSwag.CodeGeneration.CSharp
(https://www.nuget.org/packages/NSwag.CodeGeneration.CSharp/)
- You need to write C# code to generate client(C# API exposed by
NSwag.CodeGeneration.CSharp
) - https://github.com/RicoSuter/NSwag/wiki/CSharpClientGenerator
- Generates client inside project using
- NSwagStudio:
- https://github.com/RicoSuter/NSwag/wiki/NSwagStudio
- Download latest NSwagStudio MSI installer (Windows Desktop application)
- https://blog.rsuter.com/nswag-tutorial-integrate-the-nswag-toolchain-into-your-asp-net-web-api-project/
- Use HTTP Client Factory with NSwag Generated Classes :
- Visual Studio extension:
- NSwag.MSBuild:
- Allows to run the NSwag command line tool in an MSBuild target (
.csproj
file) - NSwag.MSBuild NuGet package generates code for our API clients before the project is build, this way we can generate our code and compile it everytime you build your project
- https://github.com/RicoSuter/NSwag/wiki/NSwag.MSBuild
- https://blog.sanderaernouts.com/autogenerate-csharp-api-client-with-nswag
- https://stu.dev/generating-typed-client-for-httpclientfactory-with-nswag/
- Allows to run the NSwag command line tool in an MSBuild target (
- https://github.com/RicoSuter/NSwag/wiki/NSwag-Configuration-Document
Links
- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
- https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient
- https://stackoverflow.com/a/53715282/4802664
- https://josef.codes/you-are-probably-still-using-httpclient-wrong-and-it-is-destabilizing-your-software/
- https://medium.com/cheranga/calling-web-apis-using-typed-httpclients-net-core-20d3d5ce980
- https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore
- Flurl