Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Steve Gordon
September 22, 2018

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

Steve Gordon

September 22, 2018
Tweet

More Decks by Steve Gordon

Other Decks in Technology

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 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(); } }
  6. @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(); } }
  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.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  8. @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)); } } }
  9. @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)); } } }
  10. @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)); } } }
  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() { 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() { 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)); } }
  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() { 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)); } }
  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 _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  15. DNS

  16. @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
  17. @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(); } }
  18. @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(); } }
  19. @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(); } }
  20. @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)); } }
  21. @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)); } }
  22. @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)); } }
  23. @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)); } }
  24. @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)); } }
  25. @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) { … } }
  26. @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) { … } }
  27. @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) { … } }
  28. @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) { … } }
  29. @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)); } }
  30. @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)); } }
  31. @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)); } }
  32. @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)); } }
  33. @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)); } }
  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 = "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)); } }
  35. @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); } }
  36. @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); } }
  37. @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); } }
  38. @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); } }
  39. @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) { … } }
  40. @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) { … } }
  41. @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)); } }
  42. @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)); } }
  43. @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)); } }
  44. @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); } }
  45. @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
  46. @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
  47. @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; } }
  48. @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; } }
  49. @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; } }
  50. @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; } }
  51. @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; } }
  52. @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; } }
  53. @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) { … } }
  54. @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) { … } }
  55. @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) { … } }
  56. @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
  57. @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
  58. @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
  59. @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) { … } }
  60. @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) { … } }
  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>() .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) { … } }
  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>() .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) { … } }
  63. @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) { … } }
  64. @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) { … } }
  65. @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; });
  66. @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; });
  67. @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; });
  68. @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");
  69. @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");
  70. @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");
  71. @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");
  72. @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");
  73. @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; });
  74. @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); } }
  75. @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); } }
  76. @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>>(); } }
  77. @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>>(); } }
  78. @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>>(); } }
  79. @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>(); } }
  80. @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>(); } }
  81. @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>(); } }
  82. @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>(); } }
  83. @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>(); } }
  84. @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>(); } }
  85. @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); } }
  86. @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); } }
  87. @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); } }
  88. @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>(); } }
  89. @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>(); } }
  90. @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(); } } }
  91. @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
  92. @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)
  93. @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
  94. @stevejgordon www.stevejgordon.co.uk var s = new SocketsHttpHandler() { MaxConnectionsPerServer =

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

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

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

    5, PooledConnectionLifetime = TimeSpan.FromMinutes(2), PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1) }; var client = new HttpClient(s);
  98. @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
  99. @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! ☺