Slide 1

Slide 1 text

@stevejgordon Let's Talk HTTP in .NET Core with IHttpClientFactory and Polly Steve Gordon @stevejgordon | stevejgordon.co.uk https://bit.ly/dotnet-http https://www.meetup.com/dotnetsoutheast

Slide 2

Slide 2 text

@stevejgordon www.stevejgordon.co.uk • Why do we need IHttpClientFactory? • How to use IHttpClientFactory • Outgoing middleware • Handling transient errors with Polly • Patterns and recommendations • HTTP improvements in .NET Core 2.1 to 3.1 NOTE: I'm generally speaking about .NET Core today.

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

@stevejgordon The Solution

Slide 6

Slide 6 text

WHAT'S THE SOLUTION?

Slide 7

Slide 7 text

@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(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }

Slide 8

Slide 8 text

@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(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }

Slide 9

Slide 9 text

@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(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

BUT…

Slide 18

Slide 18 text

DNS (Pre .NET Core 2.1)

Slide 19

Slide 19 text

@stevejgordon www.stevejgordon.co.uk • Manages the lifetime of HttpMessageHandlers • Provides a central location for naming and configuring logical HttpClients • Codifies the concept of outgoing middleware via delegating handlers • Integrates with Polly for transient-fault handling

Slide 20

Slide 20 text

CONVERTING OUR CODE

Slide 21

Slide 21 text

@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(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }

Slide 22

Slide 22 text

@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(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

NAMED CLIENTS

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

TYPED CLIENTS

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

@stevejgordon www.stevejgordon.co.uk https://api.github.com HttpClient H t t p R e q u e s t M e s s a g e H t t p R e s p o n s e M e s s a g e Our Application

Slide 51

Slide 51 text

@stevejgordon www.stevejgordon.co.uk Outer Handler Middle Handler Inner Handler HttpClientHandler SocketsHttpHandler HttpClient H t t p R e q u e s t M e s s a g e H t t p R e s p o n s e M e s s a g e https://api.github.com Our Application

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

@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; } }

Slide 54

Slide 54 text

@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; } }

Slide 55

Slide 55 text

@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; } }

Slide 56

Slide 56 text

@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; } }

Slide 57

Slide 57 text

@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; } }

Slide 58

Slide 58 text

@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; } }

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

@stevejgordon www.stevejgordon.co.uk • Polly is a .NET resilience and transient-fault-handling library • Integrates easily with IHttpClientFactory • Retry • Circuit-breaker • Timeout • Bulkhead isolation • Cache • Fallback • PolicyWrap

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

OTHER HTTP TIPS

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

@stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor - 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>(); } }

Slide 77

Slide 77 text

@stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor - 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>(); } }

Slide 78

Slide 78 text

@stevejgordon www.stevejgordon.co.uk public class GitHubClient : IGitHubClient { // ctor - 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(); } }

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

@stevejgordon www.stevejgordon.co.uk • HttpClient (using IHttpClientFactory) – No effect • HttpClient (without IHttpClientFactory) – Generally never • HttpRequestMessage – Only has an effect (today) if sending StreamContent • HttpResponseMessage – No effect unless using ResponseHeadersRead

Slide 95

Slide 95 text

@stevejgordon www.stevejgordon.co.uk • Built on top of System.Net.Sockets • Managed code • Elimination of platform dependencies on libcurl (for Linux) and WinHTTP (for Windows) • Consistent behaviour across platforms. • Enabled by default in .NET Core 2.1+ (No HTTP/2 support until 3.0)

Slide 96

Slide 96 text

@stevejgordon www.stevejgordon.co.uk • PooledConnectionLifetime similar to ServicePointManager • Set the max lifetime for a connection • Also possible to set the PooledConnectionIdleTimeout • Re-use the same HttpClient and handler chain without DNS concerns

Slide 97

Slide 97 text

@stevejgordon www.stevejgordon.co.uk • HTTP/2 is supported (primarily for gRPC scenarios) • HTTP/2 is not enabled by default • HttpClient now includes a Version property which applies to all messages sent via that client (HTTP/1.1 by default)

Slide 98

Slide 98 text

@stevejgordon www.stevejgordon.co.uk • Use IHttpClientFactory in .NET Core 2.1+ • Use Polly for transient-fault handling • Beware content.ReadAsStringAsync and client.GetStringAsync • Expect and handle errors • Pass cancellation tokens • Dispose appropriately (generally not required) • Go enjoy talking HTTP! ☺

Slide 99

Slide 99 text

@stevejgordon www.stevejgordon.co.uk @stevejgordon | stevejgordon.co.uk http://bit.ly/dotnet-http https://www.meetup.com/dotnetsoutheast