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

    View full-size slide

  2. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    [HttpGet]
    public async Task>> 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>(data));
    }
    }
    }

    View full-size slide

  3. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    [HttpGet]
    public async Task>> 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>(data));
    }
    }
    }

    View full-size slide

  4. @stevejgordon
    www.stevejgordon.co.uk
    What’s the solution?

    View full-size slide

  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();
    }
    }

    View full-size slide

  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();
    }
    }

    View full-size slide

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

    View full-size slide

  8. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    [HttpGet]
    public async Task>> 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>(data));
    }
    }
    }

    View full-size slide

  9. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }
    }

    View full-size slide

  10. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }
    }

    View full-size slide

  11. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  12. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  13. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  14. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  15. @stevejgordon
    www.stevejgordon.co.uk
    But…

    View full-size slide

  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

    View full-size slide

  17. @stevejgordon
    www.stevejgordon.co.uk
    Converting Our Code

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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();
    }
    }

    View full-size slide

  21. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  22. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly HttpClient _httpClient;
    public GitHubController(HttpClient httpClient) => _httpClient = httpClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  23. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  24. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  25. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  26. @stevejgordon
    www.stevejgordon.co.uk
    Named Clients

    View full-size slide

  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)
    { … }
    }

    View full-size slide

  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)
    { … }
    }

    View full-size slide

  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)
    { … }
    }

    View full-size slide

  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)
    { … }
    }

    View full-size slide

  31. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  32. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  33. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  34. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  35. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  36. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  37. @stevejgordon
    www.stevejgordon.co.uk
    Typed Clients

    View full-size slide

  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> 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>(data);
    }
    }

    View full-size slide

  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> 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>(data);
    }
    }

    View full-size slide

  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> 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>(data);
    }
    }

    View full-size slide

  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> 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>(data);
    }
    }

    View full-size slide

  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)
    { … }
    }

    View full-size slide

  43. @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)
    { … }
    }

    View full-size slide

  44. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IHttpClientFactory _factory;
    public GitHubController(IHttpClientFactory factory) => _factory = factory;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  45. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IGitHubClient _gitHubClient;
    public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

  46. @stevejgordon
    www.stevejgordon.co.uk
    public class GitHubController : ControllerBase
    {
    private readonly IGitHubClient _gitHubClient;
    public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient;
    [HttpGet]
    public async Task>> 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>(data));
    }
    }

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  50. @stevejgordon
    www.stevejgordon.co.uk
    Delegating Handlers

    View full-size slide

  51. @stevejgordon
    www.stevejgordon.co.uk
    public class StatusCodeMetricHandler : DelegatingHandler
    {
    private readonly IMonitoringService _monitoringService;
    public StatusCodeMetricHandler(IMonitoringService monitoringService) =>
    _monitoringService = monitoringService;
    protected override async Task 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;
    }
    }

    View full-size slide

  52. @stevejgordon
    www.stevejgordon.co.uk
    public class StatusCodeMetricHandler : DelegatingHandler
    {
    private readonly IMonitoringService _monitoringService;
    public StatusCodeMetricHandler(IMonitoringService monitoringService) =>
    _monitoringService = monitoringService;
    protected override async Task 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;
    }
    }

    View full-size slide

  53. @stevejgordon
    www.stevejgordon.co.uk
    public class StatusCodeMetricHandler : DelegatingHandler
    {
    private readonly IMonitoringService _monitoringService;
    public StatusCodeMetricHandler(IMonitoringService monitoringService) =>
    _monitoringService = monitoringService;
    protected override async Task 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;
    }
    }

    View full-size slide

  54. @stevejgordon
    www.stevejgordon.co.uk
    public class StatusCodeMetricHandler : DelegatingHandler
    {
    private readonly IMonitoringService _monitoringService;
    public StatusCodeMetricHandler(IMonitoringService monitoringService) =>
    _monitoringService = monitoringService;
    protected override async Task 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;
    }
    }

    View full-size slide

  55. @stevejgordon
    www.stevejgordon.co.uk
    public class StatusCodeMetricHandler : DelegatingHandler
    {
    private readonly IMonitoringService _monitoringService;
    public StatusCodeMetricHandler(IMonitoringService monitoringService) =>
    _monitoringService = monitoringService;
    protected override async Task 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;
    }
    }

    View full-size slide

  56. @stevejgordon
    www.stevejgordon.co.uk
    public class StatusCodeMetricHandler : DelegatingHandler
    {
    private readonly IMonitoringService _monitoringService;
    public StatusCodeMetricHandler(IMonitoringService monitoringService) =>
    _monitoringService = monitoringService;
    protected override async Task 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;
    }
    }

    View full-size slide

  57. @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)
    { … }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  63. @stevejgordon
    www.stevejgordon.co.uk
    Handling Transient Errors
    with Polly

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  70. @stevejgordon
    www.stevejgordon.co.uk
    Dynamically Applying Policies

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  74. @stevejgordon
    www.stevejgordon.co.uk
    Using the Policy Registry

    View full-size slide

  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()
    .AddPolicyHandlerFromRegistry("regular");

    View full-size slide

  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()
    .AddPolicyHandlerFromRegistry("regular");

    View full-size slide

  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()
    .AddPolicyHandlerFromRegistry("regular");

    View full-size slide

  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()
    .AddPolicyHandlerFromRegistry("regular");

    View full-size slide

  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()
    .AddPolicyHandlerFromRegistry("regular");

    View full-size slide

  80. @stevejgordon
    www.stevejgordon.co.uk
    Configuring the Primary Handler

    View full-size slide

  81. @stevejgordon
    www.stevejgordon.co.uk
    services.AddHttpClient()
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
    var handler = new HttpClientHandler
    {
    ClientCertificateOptions = ClientCertificateOption.Manual,
    SslProtocols = SslProtocols.Tls12,
    AllowAutoRedirect = false
    };
    handler.ClientCertificates.Add(new X509Certificate2("mycert.crt"));
    return handler;
    });

    View full-size slide

  82. @stevejgordon
    www.stevejgordon.co.uk
    Other HTTP-based Tips

    View full-size slide

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

    View full-size slide

  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> 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>(data);
    }
    }

    View full-size slide

  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> 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>(data);
    }
    }

    View full-size slide

  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> GetAspNetReposAsync()
    {
    var url = "orgs/aspnet/repos";
    var request = new HttpRequestMessage(HttpMethod.Get, url);
    var response = await _httpClient.SendAsync(request);
    return await response.Content.ReadAsAsync>();
    }
    }

    View full-size slide

  87. @stevejgordon
    www.stevejgordon.co.uk
    Handling Errors

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  91. @stevejgordon
    www.stevejgordon.co.uk
    Handling Cancellation

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  100. @stevejgordon
    www.stevejgordon.co.uk
    Caching

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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> GetAspNetReposAsync(CancellationToken ct = default)
    {
    // ... contents of this method hidden for brevity ...
    }
    }

    View full-size slide

  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> GetAspNetReposAsync(CancellationToken ct = default)
    {
    // ... contents of this method hidden for brevity ...
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  110. @stevejgordon
    www.stevejgordon.co.uk
    Bonus Tip 1 – Extension Methods
    Credit to: @randompunter and @leastprivilege

    View full-size slide

  111. @stevejgordon
    www.stevejgordon.co.uk
    public static class HttpClientExtensions
    {
    public static async Task> 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>(ct)
    : Array.Empty();
    }
    }

    View full-size slide

  112. @stevejgordon
    www.stevejgordon.co.uk
    public static class HttpClientExtensions
    {
    public static async Task> 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>(ct)
    : Array.Empty();
    }
    }

    View full-size slide

  113. @stevejgordon
    www.stevejgordon.co.uk
    public static class HttpClientExtensions
    {
    public static async Task> 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>(ct)
    : Array.Empty();
    }
    }

    View full-size slide

  114. @stevejgordon
    www.stevejgordon.co.uk
    public static class HttpClientExtensions
    {
    public static async Task> 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>(ct)
    : Array.Empty();
    }
    }

    View full-size slide

  115. @stevejgordon
    www.stevejgordon.co.uk
    public static class HttpClientExtensions
    {
    public static async Task> 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>(ct)
    : Array.Empty();
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  119. @stevejgordon
    www.stevejgordon.co.uk
    Bonus Tip 2 - Correlation IDs
    https://github.com/stevejgordon/CorrelationId
    https://www.nuget.org/packages/CorrelationId

    View full-size slide

  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)

    View full-size slide

  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! ☺

    View full-size slide

  122. @stevejgordon
    www.stevejgordon.co.uk
    Thanks for listening!
    @stevejgordon
    www.stevejgordon.co.uk
    youtube.stevejgordon.co.uk
    https://www.meetup.com/dotnetsoutheast

    View full-size slide

  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/

    View full-size slide