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

Let's Talk HTTP in .NET Core

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.

Steve Gordon

September 12, 2018
Tweet

More Decks by Steve Gordon

Other Decks in Technology

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 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(); } }
  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.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  7. @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)); } } }
  8. @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)); } } }
  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() { 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() { 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)); } }
  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 _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  14. @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
  15. @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(); } }
  16. @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(); } }
  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.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  18. @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)); } }
  19. @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)); } }
  20. @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)); } }
  21. @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)); } }
  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 httpClient = _factory.CreateClient(); 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 Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  24. @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) { … } }
  25. @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) { … } }
  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 GitHubController : ControllerBase { private readonly

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

    IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  40. @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)); } }
  41. @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)); } }
  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 data = await _gitHubClient.GetAspNetReposAsync(); return Ok(data); } }
  43. @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
  44. @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; } }
  45. @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; } }
  46. @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; } }
  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 Startup { ... public void ConfigureServices(IServiceCollection

    services) { services.AddHttpClient<IGitHubClient, GitHubClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  51. @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) { … } }
  52. @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) { … } }
  53. @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
  54. @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
  55. @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
  56. @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) { … } }
  57. @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) { … } }
  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>() .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) { … } }
  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>() .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) { … } }
  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 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; });
  63. @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; });
  64. @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; });
  65. @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");
  66. @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");
  67. @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");
  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 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; });
  71. @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); } }
  72. @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); } }
  73. @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>>(); } }
  74. @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>>(); } }
  75. @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>>(); } }
  76. @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>(); } }
  77. @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>(); } }
  78. @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>(); } }
  79. @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>(); } }
  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(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>(); } }
  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); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  82. @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); } }
  83. @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); } }
  84. @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); } }
  85. @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) { … } }
  86. @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) { … } }
  87. @stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { private readonly

    HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { // ... contents of this method hidden for brevity ... } }
  88. @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 ... } }
  89. @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>(); } } }
  90. @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>(); } } }
  91. @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>(); } } }
  92. @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>(); } } }
  93. @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>(); } } }
  94. @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>(); } }
  95. @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>(); } }
  96. @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>(); } }
  97. @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>(); } }
  98. @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>(); } }
  99. @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>(); } } }
  100. @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); } } }
  101. @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); } } }
  102. @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)
  103. @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! ☺
  104. @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/