Best Practice for Using HttpClient-C#

Best Practice for Using HttpClient-C#

Almost every application nowadays is making HttpRequest get data from other services. In this article, I will share some best practices for using the .net core HttpClient API. To understand this, I have created a simple .net core application in LinqPad; By the end of the article, you will learn.

  • How to integrate IHttpClientFactory in .ner core application
  • How to handle user cancel request
void Main()
{

	CreateWebHostBuilder(new string[] { "" }).Build().Run();

}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
		WebHost.CreateDefaultBuilder(args)
			.UseStartup<Startup>();




[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
	private readonly IHttpClientFactory _factory;
	public ValuesController(IHttpClientFactory factory)
	{
		_factory = factory;
	}

	public async Task<IEnumerable<Todo>> GetHttpClient()
	{
		var todos = new List<Todo>();
		for (int i = 0; i < 100; i++)
		{
			using (var httpClient = new HttpClient())
			{
				var response = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos/1");
				var result = await response.Content.ReadAsStringAsync();
				todos.Add(JsonConvert.DeserializeObject<Todo>(result));
			}
		}
		return todos;
	}
	

	[HttpGet]
	public async Task<IEnumerable<Todo>> Get()
	{
		return await GetHttpClient();
	}

}
public class Todo
{
	public int userId { get; set; }
	public int id { get; set; }
	public string title { get; set; }
	public bool completed { get; set; }
}
public class Startup
{
	public Startup(IConfiguration configuration)
	{
		Configuration = configuration;
	}

	public IConfiguration Configuration { get; }


	public void ConfigureServices(IServiceCollection services)
	{
		
		services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
	}


	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{


		if (env.IsDevelopment())
		{
			app.UseDeveloperExceptionPage();
		}


		app.UseStaticFiles();
		app.UseRouting();
		app.UseCors();

		app.UseEndpoints(endpoints =>
		{
			endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}");
		});
	}
}

Above is all boilerplate code. The only code that we are going to discuss is the following.

public async Task<IEnumerable<Todo>> GetHttpClient()
	{
		var todos = new List<Todo>();
		for (int i = 0; i < 100; i++)
		{
			using (var httpClient = new HttpClient())
			{
				var response = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos/1");
				var result = await response.Content.ReadAsStringAsync();
				todos.Add(JsonConvert.DeserializeObject<Todo>(result));
			}
		}
		return todos;
	}

AS you can see in the above code, I am making a new instance of HttpClient and immediately disposing of it. If you run the application and hit the, you will get the result.No; issue until now. If you see the TCP connection, most of it will be TIME_WAIT because we have disposed of the connection.

netstat -n | where {$_ -like '*TIME_WAIT*'}

TIME_WAIT -The socket is waiting after close to handle packets still in the network.{alertError}

Opening a connection to a server is an expensive process because it goes through multiple steps like finding the IP address from the domain. To reuse the existing connection, Microsoft suggests using IHttpClientFactory use reuse the existing connection to improve the performance. Let's integrate the IHttpClientFactory

void Main()
{

	CreateWebHostBuilder(new string[] { "" }).Build().Run();

}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
		WebHost.CreateDefaultBuilder(args)
			.UseStartup<Startup>();

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
	 private readonly IHttpClientFactory _factory;
+	 public ValuesController(IHttpClientFactory factory)
	{
+		_factory = factory;
	}

-	 public async Task<IEnumerable<Todo>> GetHttpClient()
-	{
-		var todos = new List<Todo>();
-		for (int i = 0; i < 100; i++)
-		{
-			using (var httpClient = new H-ttpClient())
-			{
-				var response = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos/1");
-				var result = await -response.Content.ReadAsStringAsync();
-				todos.Add(JsonConvert.DeserializeObject<Todo>(result));
-			}
-		}
-		return todos;
-	}

+	public async Task<IEnumerable<Todo>> GetHttpClientFactory()
+	{
+		var todos = new List<Todo>();
+		for (int i = 0; i < 100; i++)
+		{

+			var response = await _factory.CreateClient().GetAsync("https://jsonplaceholder.typicode.com/todos/1");
+			var result = await response.Content.ReadAsStringAsync();
+			todos.Add(JsonConvert.DeserializeObject<Todo>(result));

+		}
+		return todos;
+	}


	[HttpGet]
	public async Task<IEnumerable<Todo>> Get()
	{
		return await GetHttpClient();
	}

}
public class Todo
{
	public int userId { get; set; }
	public int id { get; set; }
	public string title { get; set; }
	public bool completed { get; set; }
}
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_3_0);
	}


	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{


		if (env.IsDevelopment())
		{
			app.UseDeveloperExceptionPage();
		}


		app.UseStaticFiles();
		app.UseRouting();
		app.UseCors();

		app.UseEndpoints(endpoints =>
		{
			endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}");
		});
	}
}

Ass you can see, I have injected the IHttpClientFactory in the constructor of the control in the above code, and then I have registered the AddHttpClient method in configure services. If you run the application, you will see no TIME_WAT in the netstat output because the .net core is reusing the existing connection.

Always Pass Cancelation Token

By default, the .net core pass CancellationToken to every request. Therefore, always pass this to the method so that If the user cancels the request, then the code stops executing the pending task.

[HttpGet("/api/data")]
	public async Task<string> GetExpensiveData(CancellationToken ct)
	{

		await Task.Delay(3000);
		Console.WriteLine("Getting Data");
		await GetHttpClientFactory(ct);
		await Task.Delay(1000);
		Console.WriteLine("Getting another data");
		return "done";

	}
public async Task<IEnumerable<Todo>> GetHttpClientFactory(CancellationToken token = default)
	{
		try
		{
			token.ThrowIfCancellationRequested();
			var todos = new List<Todo>();
			for (int i = 0; i < 100; i++)
			{
				var response = await _factory.CreateClient().GetAsync("https://jsonplaceholder.typicode.com/todos/1", token);
				var result = await response.Content.ReadAsStringAsync();
				todos.Add(JsonConvert.DeserializeObject<Todo>(result));

			}
			return todos;
		}
		catch (OperationCanceledException ex)
		{
			Console.WriteLine("Operation Cancelled");
			//throw;
		}
		return null;
	}

Advantage of using IHttpClientFactory

Below are some out of the box advantages of using IHttpClientFactory

  • It automatically manages the lifetime of the HttpMessageHandler
  • Centralize place for managing the HttpClient and configuration
  • Easily add functionality for outgoing response by delegating handler
  • Improve diagnostics and logging
  • Easily add Polly for resiliency

Please do not post any spam link in the comment box😊

إرسال تعليق (0)
أحدث أقدم