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

Let's Talk HTTP in .NET Core

Let's Talk HTTP in .NET Core

863d6186d3bc32b7c9036101c47d5d5b?s=128

Steve Gordon

June 07, 2019
Tweet

Transcript

  1. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Let's Talk HTTP in .NET Core

  2. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 What we’ll cover • Why do we

    need IHttpClientFactory? • How to use IHttpClientFactory • Outgoing middleware • Handling transient errors with Polly • Patterns and recommendations • Other HTTP improvements in .NET Core 2.1 • Coming in .NET Core 3.0 NOTE: I'm generally speaking about .NET Core today.
  3. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 The Solution

  6. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 What’s the solution?

  7. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 But…

  18. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 IHttpClientFactory • 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
  19. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Converting Our Code

  20. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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(); } }
  21. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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(); } }
  23. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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)); } }
  24. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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)); } }
  26. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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)); } }
  28. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Named Clients

  29. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  30. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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) { … } }
  31. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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)); } }
  34. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); 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 https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); 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 https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); 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 https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); 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 https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var httpClient = _factory.CreateClient("github"); 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 https://bit.ly/letstalkhttp40 Typed Clients

  40. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  41. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  42. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  43. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  44. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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) { … } }
  45. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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) { … } }
  46. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

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

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); 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 https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); 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 https://bit.ly/letstalkhttp40 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); } }
  50. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Outgoing "middleware" https://api.github.com Our Application 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
  51. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Outgoing "middleware" Outer Handler Middle Handler Inner

    Handler HttpClientHandler SocketsHttpHandler https://api.github.com Our Application 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
  52. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Delegating Handlers

  53. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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; } }
  54. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 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) { … } }
  60. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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) { … } }
  61. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 https://bit.ly/letstalkhttp40 Polly • Polly is a .NET resilience

    and transient-fault-handling library • Integrates easily with IHttpClientFactory • Retry • Circuit-breaker • Timeout • Bulkhead isolation • Cache • Fallback • PolicyWrap
  63. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Handling Transient Errors with Polly

  64. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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) { … } }
  65. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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) { … } }
  66. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Taking care when handling response content

  67. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  68. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  69. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Handling Errors

  70. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

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

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

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

  74. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  75. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  76. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  77. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  78. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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 request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  79. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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); } }
  80. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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); } }
  81. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 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); } }
  82. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 When Should I Dispose? • HttpClient (using

    IHttpClientFactory) – No effect • HttpClient (without IHttpClientFactory) – Generally never • HttpRequestMessage – Only has an effect if sending StreamContent. • HttpResponseMessage – Safest is always to dispose after handling response. No effect in most cases.
  83. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { //

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

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

    ctor - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct); try { return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } finally { response.Dispose(); } } }
  86. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 SocketsHttpHandler • Sockets are the basis of

    both outgoing and incoming networking communication • SocketsHttpHandler is a brand-new managed HttpMessageHandler and is the default implementation for HttpClient • Consistent behaviour across platforms and platform/dependency versions. Built on top of System.Net.Sockets • Elimination of platform dependencies on libcurl (for Linux) and WinHTTP (for Windows) – simplifies both development, deployment and servicing • Enabled by default in .NET Core 2.1+ (Does not support HTTP/2)
  87. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Coming in .NET Core 3.0 • HTTP/2

    is supported (primarily for gRPC scenarios) although 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
  88. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 In Summary • 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 • Go enjoy talking HTTP! ☺
  89. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Thanks for listening! @stevejgordon www.stevejgordon.co.uk youtube.stevejgordon.co.uk https://app.pluralsight.com/profile/author/steve-gordon

  90. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Resources 1. https://bit.ly/letstalkhttp40 2. https://www.stevejgordon.co.uk/httpclient-creation-and-disposal-internals-should-i-dispose-of- httpclient 3.

    https://www.stevejgordon.co.uk/demystifying-httpclient-internals-httprequestmessage 4. https://www.stevejgordon.co.uk/demystifying-httpclient-internals-httpresponsemessage 5. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ 6. https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore- 2.1 7. https://github.com/App-vNext/Polly