Pro Yearly is on sale from $80 to $50! »

Let's Talk HTTP in .NET Core (60min Version)

863d6186d3bc32b7c9036101c47d5d5b?s=47 Steve Gordon
September 22, 2018

Let's Talk HTTP in .NET Core (60min Version)

863d6186d3bc32b7c9036101c47d5d5b?s=128

Steve Gordon

September 22, 2018
Tweet

Transcript

  1. @stevejgordon Let's Talk HTTP in .NET Core with IHttpClientFactory and

    Polly Steve Gordon @stevejgordon | stevejgordon.co.uk https://bit.ly/letstalkhttp60 https://www.meetup.com/dotnetsoutheast
  2. @stevejgordon www.stevejgordon.co.uk • Why do we need IHttpClientFactory? • How

    to use IHttpClientFactory • Outgoing middleware • Handling transient errors with Polly • Patterns and recommendations • HTTP improvements in .NET Core 2.1, 2.2 and 3.0 NOTE: I'm generally speaking about .NET Core today.
  3. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { [HttpGet] public

    async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  4. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { [HttpGet] public

    async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  5. @stevejgordon The Solution

  6. WHAT'S THE SOLUTION?

  7. @stevejgordon www.stevejgordon.co.uk public class Startup { public Startup(IConfiguration configuration) {

    Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  8. @stevejgordon www.stevejgordon.co.uk public class Startup { public Startup(IConfiguration configuration) {

    Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  9. @stevejgordon www.stevejgordon.co.uk public class Startup { public Startup(IConfiguration configuration) {

    Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  10. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { [HttpGet] public

    async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  11. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  12. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  13. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  14. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  15. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  16. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  17. BUT…

  18. DNS

  19. @stevejgordon www.stevejgordon.co.uk • Manages the lifetime of HttpMessageHandlers • Provides

    a central location for naming and configuring logical HttpClients • Codifies the concept of outgoing middleware via delegating handlers • Integrates with Polly for transient-fault handling • Improved diagnostics and logging
  20. CONVERTING OUR CODE

  21. @stevejgordon www.stevejgordon.co.uk public class Startup { public Startup(IConfiguration configuration) {

    Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  22. @stevejgordon www.stevejgordon.co.uk public class Startup { public Startup(IConfiguration configuration) {

    Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  23. @stevejgordon www.stevejgordon.co.uk public class Startup { public Startup(IConfiguration configuration) {

    Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  24. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  25. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  26. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  27. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  28. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  29. NAMED CLIENTS

  30. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  31. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  32. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  33. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  34. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  35. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  36. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  37. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  38. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  39. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  40. TYPED CLIENTS

  41. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  42. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  43. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  44. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  45. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  46. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient<IGitHubClient, GitHubClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  47. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  48. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  49. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  50. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var data = await _gitHubClient.GetAspNetReposAsync(); return Ok(data); } }
  51. @stevejgordon www.stevejgordon.co.uk https://api.github.com HttpClient H t t p R e

    q u e s t M e s s a g e H t t p R e s p o n s e M e s s a g e Our Application
  52. @stevejgordon www.stevejgordon.co.uk Outer Handler Middle Handler Inner Handler HttpClientHandler SocketsHttpHandler

    HttpClient H t t p R e q u e s t M e s s a g e H t t p R e s p o n s e M e s s a g e https://api.github.com Our Application
  53. None
  54. @stevejgordon www.stevejgordon.co.uk public class StatusCodeMetricHandler : DelegatingHandler { private readonly

    IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  55. @stevejgordon www.stevejgordon.co.uk public class StatusCodeMetricHandler : DelegatingHandler { private readonly

    IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  56. @stevejgordon www.stevejgordon.co.uk public class StatusCodeMetricHandler : DelegatingHandler { private readonly

    IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  57. @stevejgordon www.stevejgordon.co.uk public class StatusCodeMetricHandler : DelegatingHandler { private readonly

    IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  58. @stevejgordon www.stevejgordon.co.uk public class StatusCodeMetricHandler : DelegatingHandler { private readonly

    IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  59. @stevejgordon www.stevejgordon.co.uk public class StatusCodeMetricHandler : DelegatingHandler { private readonly

    IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  60. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient<IGitHubClient, GitHubClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  61. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  62. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  63. @stevejgordon www.stevejgordon.co.uk • Polly is a .NET resilience and transient-fault-handling

    library • Integrates easily with IHttpClientFactory • Retry • Circuit-breaker • Timeout • Bulkhead isolation • Cache • Fallback • PolicyWrap
  64. @stevejgordon www.stevejgordon.co.uk H t t p R e q u

    e s t M e s s a g e 5 0 3 U n a v a i l a b l e
  65. @stevejgordon www.stevejgordon.co.uk H t t p R e q u

    e s t M e s s a g e 5 0 3 U n a v a i l a b l e H t t p R e q u e s t M e s s a g e 5 0 3 U n a v a i l a b l e H t t p R e q u e s t M e s s a g e 2 0 0 O K
  66. None
  67. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  68. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))) .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  69. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))) .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  70. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))) .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  71. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))) .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  72. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))) .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  73. None
  74. @stevejgordon www.stevejgordon.co.uk var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>(); var retryPolicy = HttpPolicyExtensions

    .HandleTransientHttpError() // HttpRequestException, 5XX and 408 .WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount))); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandler(message => { return message.Method == HttpMethod.Get ? retryPolicy : noOpPolicy; });
  75. @stevejgordon www.stevejgordon.co.uk var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>(); var retryPolicy = HttpPolicyExtensions

    .HandleTransientHttpError() // HttpRequestException, 5XX and 408 .WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount))); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandler(message => { return message.Method == HttpMethod.Get ? retryPolicy : noOpPolicy; });
  76. @stevejgordon www.stevejgordon.co.uk var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>(); var retryPolicy = HttpPolicyExtensions

    .HandleTransientHttpError() // HttpRequestException, 5XX and 408 .WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount))); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandler(message => { return message.Method == HttpMethod.Get ? retryPolicy : noOpPolicy; });
  77. None
  78. @stevejgordon www.stevejgordon.co.uk var timeout = Policy.TimeoutAsync(10); var longTimeout = Policy.TimeoutAsync(45);

    var registry = services.AddPolicyRegistry(); registry.Add("regular", timeout); registry.Add("long", longTimeout); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandlerFromRegistry("regular");
  79. @stevejgordon www.stevejgordon.co.uk var timeout = Policy.TimeoutAsync(10); var longTimeout = Policy.TimeoutAsync(45);

    var registry = services.AddPolicyRegistry(); registry.Add("regular", timeout); registry.Add("long", longTimeout); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandlerFromRegistry("regular");
  80. @stevejgordon www.stevejgordon.co.uk var timeout = Policy.TimeoutAsync(10); var longTimeout = Policy.TimeoutAsync(45);

    var registry = services.AddPolicyRegistry(); registry.Add("regular", timeout); registry.Add("long", longTimeout); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandlerFromRegistry("regular");
  81. @stevejgordon www.stevejgordon.co.uk var timeout = Policy.TimeoutAsync(10); var longTimeout = Policy.TimeoutAsync(45);

    var registry = services.AddPolicyRegistry(); registry.Add("regular", timeout); registry.Add("long", longTimeout); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandlerFromRegistry("regular");
  82. @stevejgordon www.stevejgordon.co.uk var timeout = Policy.TimeoutAsync(10); var longTimeout = Policy.TimeoutAsync(45);

    var registry = services.AddPolicyRegistry(); registry.Add("regular", timeout); registry.Add("long", longTimeout); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandlerFromRegistry("regular");
  83. None
  84. @stevejgordon www.stevejgordon.co.uk services.AddHttpClient<IGitHubClient, GitHubClient>() .ConfigurePrimaryHttpMessageHandler(() => { var handler =

    new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Manual, SslProtocols = SslProtocols.Tls12, AllowAutoRedirect = false }; handler.ClientCertificates.Add(new X509Certificate2("mycert.crt")); return handler; });
  85. OTHER HTTP TIPS

  86. None
  87. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  88. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  89. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  90. None
  91. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  92. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  93. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  94. None
  95. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  96. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  97. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  98. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  99. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  100. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var data = await _gitHubClient.GetAspNetReposAsync(); return Ok(data); } }
  101. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos(CancellationToken ct) { var data = await _gitHubClient.GetAspNetReposAsync(ct); return Ok(data); } }
  102. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos(CancellationToken ct) { var data = await _gitHubClient.GetAspNetReposAsync(ct); return Ok(data); } }
  103. None
  104. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var req = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(req, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  105. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var req = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  106. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var req = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct); try { return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } finally { response.Dispose(); } } }
  107. None
  108. None
  109. @stevejgordon www.stevejgordon.co.uk • HttpClient (using IHttpClientFactory) – No effect •

    HttpClient (without IHttpClientFactory) – Generally never • HttpRequestMessage – Only has an effect if sending StreamContent • HttpResponseMessage – No effect unless using ResponseHeadersRead
  110. @stevejgordon www.stevejgordon.co.uk • Built on top of System.Net.Sockets • Managed

    code • Elimination of platform dependencies on libcurl (for Linux) and WinHTTP (for Windows) • Consistent behaviour across platforms. • Enabled by default in .NET Core 2.1+ (No HTTP/2 support until 3.0)
  111. @stevejgordon www.stevejgordon.co.uk • PooledConnectionLifetime similar to ServicePointManager • Set the

    max lifetime for a connection • Also possible to set the PooledConnectionIdleTimeout • Re-use the same HttpClient and handler chain without DNS concerns
  112. @stevejgordon www.stevejgordon.co.uk var s = new SocketsHttpHandler() { MaxConnectionsPerServer =

    5, PooledConnectionLifetime = TimeSpan.FromMinutes(2), PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1) }; var client = new HttpClient(s);
  113. @stevejgordon www.stevejgordon.co.uk var s = new SocketsHttpHandler() { MaxConnectionsPerServer =

    5, PooledConnectionLifetime = TimeSpan.FromMinutes(2), PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1) }; var client = new HttpClient(s);
  114. @stevejgordon www.stevejgordon.co.uk var s = new SocketsHttpHandler() { MaxConnectionsPerServer =

    5, PooledConnectionLifetime = TimeSpan.FromMinutes(2), PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1) }; var client = new HttpClient(s);
  115. @stevejgordon www.stevejgordon.co.uk var s = new SocketsHttpHandler() { MaxConnectionsPerServer =

    5, PooledConnectionLifetime = TimeSpan.FromMinutes(2), PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1) }; var client = new HttpClient(s);
  116. @stevejgordon www.stevejgordon.co.uk • HTTP/2 is supported (primarily for gRPC scenarios)

    • HTTP/2 is not enabled by default • HttpClient now includes a Version property which applies to all messages sent via that client (HTTP/1.1 by default) • TLS 1.3 and OpenSSL 1.1.1 on Linux reduces connection times and improves security – Windows and macOS when OS support it
  117. @stevejgordon www.stevejgordon.co.uk • Use IHttpClientFactory in .NET Core 2.1+ •

    Use Polly for transient-fault handling • Beware content.ReadAsStringAsync and client.GetStringAsync • Expect and handle errors • Pass cancellation tokens • Dispose appropriately (generally not required) • Go enjoy talking HTTP! ☺
  118. @stevejgordon www.stevejgordon.co.uk @stevejgordon | stevejgordon.co.uk http://bit.ly/dotnet-http https://www.meetup.com/dotnetsoutheast