Let's Talk HTTP in .NET Core

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

Let's Talk HTTP in .NET Core

This presentation covers refactoring some code to use HTTP best practices and new features in ASP.NET Core 2.1 such as IHttpClientFactory.

863d6186d3bc32b7c9036101c47d5d5b?s=128

Steve Gordon

September 12, 2018
Tweet

Transcript

  1. @stevejgordon www.stevejgordon.co.uk Let's Talk HTTP in .NET Core with IHttpClientFactory

    and Polly Steve Gordon @stevejgordon | stevejgordon.co.uk https://www.meetup.com/dotnetsoutheast
  2. @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)); } } }
  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 What’s the solution?

  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. @stevejgordon www.stevejgordon.co.uk But…

  16. @stevejgordon www.stevejgordon.co.uk 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
  17. @stevejgordon www.stevejgordon.co.uk Converting Our Code

  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.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  20. @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(); } }
  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

    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)); } }
  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 response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  25. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

    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)); } }
  26. @stevejgordon www.stevejgordon.co.uk Named Clients

  27. @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) { … } }
  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 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) { … } }
  30. @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) { … } }
  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 = "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)); } }
  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); 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)); } }
  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); 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 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)); } }
  35. @stevejgordon www.stevejgordon.co.uk public class GitHubController : ControllerBase { private readonly

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

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

  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 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); } }
  40. @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); } }
  41. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  42. @stevejgordon www.stevejgordon.co.uk public class 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) { … } }
  43. @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) { … } }
  44. @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)); } }
  45. @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)); } }
  46. @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)); } }
  47. @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); } }
  48. @stevejgordon www.stevejgordon.co.uk 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
  49. @stevejgordon www.stevejgordon.co.uk 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
  50. @stevejgordon www.stevejgordon.co.uk Delegating Handlers

  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 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 public class StatusCodeMetricHandler : DelegatingHandler { private readonly

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

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

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

    services) { services.AddHttpClient<IGitHubClient, GitHubClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  58. @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) { … } }
  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 Polly • Polly is a .NET resilience and

    transient-fault-handling library • Integrates easily with IHttpClientFactory • Retry • Circuit-breaker • Timeout • Bulkhead isolation • Cache • Fallback • PolicyWrap
  61. @stevejgordon www.stevejgordon.co.uk Our Application 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
  62. @stevejgordon www.stevejgordon.co.uk Our Application 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
  63. @stevejgordon www.stevejgordon.co.uk Handling Transient Errors with Polly

  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>() .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  65. @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) { … } }
  66. @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) { … } }
  67. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .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) { … } }
  68. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

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

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

  71. @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; });
  72. @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; });
  73. @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; });
  74. @stevejgordon www.stevejgordon.co.uk Using the Policy Registry

  75. @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");
  76. @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");
  77. @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");
  78. @stevejgordon www.stevejgordon.co.uk var timeout = Policy.TimeoutAsync(10); var longTimeout = Policy.TimeoutAsync(45);

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

    var registry = services.AddPolicyRegistry(); registry.Add("regular", timeout); registry.Add("long", longTimeout); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddPolicyHandlerFromRegistry("regular");
  80. @stevejgordon www.stevejgordon.co.uk Configuring the Primary Handler

  81. @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; });
  82. @stevejgordon www.stevejgordon.co.uk Other HTTP-based Tips

  83. @stevejgordon www.stevejgordon.co.uk Taking care when handling response content

  84. @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); } }
  85. @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); } }
  86. @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>>(); } }
  87. @stevejgordon www.stevejgordon.co.uk Handling Errors

  88. @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 await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  89. @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); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  90. @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>(); } }
  91. @stevejgordon www.stevejgordon.co.uk Handling Cancellation

  92. @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>(); } }
  93. @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>(); } }
  94. @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>(); } }
  95. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(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>(); } }
  96. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

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

  101. @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) { … } }
  102. @stevejgordon www.stevejgordon.co.uk public class Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddMemoryCache(); 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) { … } }
  103. @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(CancellationToken ct = default) { // ... contents of this method hidden for brevity ... } }
  104. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; private readonly IMemoryCache _memoryCache; public GitHubClient(HttpClient client, IMemoryCache memoryCache) { 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; _memoryCache = memoryCache; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { // ... contents of this method hidden for brevity ... } }
  105. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { 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>(); } } }
  106. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { 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>(); } } }
  107. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { 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>(); } } }
  108. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { 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>(); } } }
  109. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { 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>(); } } }
  110. @stevejgordon www.stevejgordon.co.uk Bonus Tip 1 – Extension Methods Credit to:

    @randompunter and @leastprivilege
  111. @stevejgordon www.stevejgordon.co.uk public static class HttpClientExtensions { public static async

    Task<IEnumerable<GitHubRepo>> GetReposAsync( this HttpClient client, string org, CancellationToken ct = default) { if (string.IsNullOrEmpty(org)) throw new ArgumentException(nameof(org)); var url = $"orgs/{org}/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await client.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  112. @stevejgordon www.stevejgordon.co.uk public static class HttpClientExtensions { public static async

    Task<IEnumerable<GitHubRepo>> GetReposAsync( this HttpClient client, string org, CancellationToken ct = default) { if (string.IsNullOrEmpty(org)) throw new ArgumentException(nameof(org)); var url = $"orgs/{org}/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await client.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  113. @stevejgordon www.stevejgordon.co.uk public static class HttpClientExtensions { public static async

    Task<IEnumerable<GitHubRepo>> GetReposAsync( this HttpClient client, string org, CancellationToken ct = default) { if (string.IsNullOrEmpty(org)) throw new ArgumentException(nameof(org)); var url = $"orgs/{org}/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await client.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  114. @stevejgordon www.stevejgordon.co.uk public static class HttpClientExtensions { public static async

    Task<IEnumerable<GitHubRepo>> GetReposAsync( this HttpClient client, string org, CancellationToken ct = default) { if (string.IsNullOrEmpty(org)) throw new ArgumentException(nameof(org)); var url = $"orgs/{org}/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await client.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  115. @stevejgordon www.stevejgordon.co.uk public static class HttpClientExtensions { public static async

    Task<IEnumerable<GitHubRepo>> GetReposAsync( this HttpClient client, string org, CancellationToken ct = default) { if (string.IsNullOrEmpty(org)) throw new ArgumentException(nameof(org)); var url = $"orgs/{org}/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); ct.ThrowIfCancellationRequested(); var response = await client.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  116. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { 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>(); } } }
  117. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { return await _httpClient.GetReposAsync("aspnet", ct); } } }
  118. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor

    - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { return await _memoryCache.GetOrCreateAsync("aspnet-repo-data", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); return GetFromGitHub(); }); async Task<IEnumerable<GitHubRepo>> GetFromGitHub() { return await _httpClient.GetReposAsync("aspnet", ct); } } }
  119. @stevejgordon www.stevejgordon.co.uk Bonus Tip 2 - Correlation IDs https://github.com/stevejgordon/CorrelationId https://www.nuget.org/packages/CorrelationId

  120. @stevejgordon www.stevejgordon.co.uk 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)
  121. @stevejgordon www.stevejgordon.co.uk 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 • Cache wisely • Pass correlation IDs • Go enjoy talking HTTP! ☺
  122. @stevejgordon www.stevejgordon.co.uk Thanks for listening! @stevejgordon www.stevejgordon.co.uk youtube.stevejgordon.co.uk https://www.meetup.com/dotnetsoutheast

  123. @stevejgordon www.stevejgordon.co.uk Resources 1. http://bit.ly/httpslides 2. https://www.stevejgordon.co.uk 3. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ 4.

    https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http- requests?view=aspnetcore-2.1 5. https://github.com/App-vNext/Polly 6. https://leastprivilege.com/2018/06/18/making-the-identitymodel-client- libraries-httpclientfactory-friendly/