Making HTTP Requests

Basic Download

If you just want to download a string/data/file from an unauthenticated endpoint, this is good enough:

(new System.Net.WebClient()).DownloadString("http://x.com");        //string
(new System.Net.WebClient()).DownloadData("http://x.com/file.bin"); //byte[]
(new System.Net.WebClient()).DownloadFile("http://x.com/file.zip", @"c:\file.zip"); //void

Which library do I use if need to do more?

There are many .NET libraries and classes that you may have heard of that can make an http request. The one that is recommended by Microsoft (as of 2019) is the HttpClient class.

This class abstracts away the implementation details when used on multiple platforms.

RestSharp

If you are ok with adding dependencies or you just want to do some quick automation, a great third party alternative is RestSharp (nuget package here).

Tip: (thanks to @sneakerhax) Postman can auto-generate your request with RestSharp API.

Click ‘code’ in the top right.

Then choose C# from the dropdown menu.

POST serialized JSON objects

To pass a C# object from a client to the server, it will first need to be serialized in JSON. So how do you use HttpClient to do this?

Let’s start with a POCO Person class:

public class Person {
    public string Name {get; set;}
    public string Email {get; set;}
}

Here is the server code (it’s an ASP.NET web app):

[Route("api/[controller]")]

public class ToolInvokeController : Controller
{
    [HttpPost]
    public void Post([FromBody]Person p)
    {
    }//In VS set breakpoint here to ensure that p is deserialized properly
}

Here is the client code with HttpClient:

using Newtonsoft.Json;

...
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(EndpointUri);

//if authn headers needed:
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "[jwt here]");

HttpResponseMessage response = client.PostAsync("/api/ToolInvoke", 
    new StringContent(
        JsonConvert.SerializeObject(person),
        Encoding.UTF8, "application/json")
    ).Result;

//Tip: If you don't want to serialize a POCO, you can create an anon. type: JsonConvert.SerializeObject(new { Name = "asdf", Email = "agafg" })

PostAsJsonAsync - don’t use it There are recommendations to use PostAsJsonAsync, but this requires adding a nuget package (Microsoft.AspNet.WebApi.Client) which is not compatable with the latest version of the Newtonsoft.Json library - you will see an error such as:

Could not load file or assembly 'Newtonsoft.Json, Version=[old version no.], Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies

However if you really want to get this to work, you can use this.

Make your own PostAsJsonAsync method

Instead, you can make an extension method:

public static class HttpClientExt
{
    public static async System.Threading.Tasks.Task<HttpResponseMessage> PostAsJsonAsync<T>(this HttpClient client, string requestUrl, T theObj)
    {
        var stringContent = new StringContent(
            Newtonsoft.Json.JsonConvert.SerializeObject(theObj),
            System.Text.Encoding.UTF8, "application/json");
        return await client.PostAsync(requestUrl, stringContent);
    }  //modified from: https://stackoverflow.com/a/40525794/2205372
}

Proper use of HttpClient

You should have just one instance of HttpClient. Here’s an easy way to do this:

public class HttpClientWrapper
{
    private HttpClient httpClient;
    public HttpClient Client => httpClient ?? (httpClient = new HttpClient());
}

but what if you want to cancel the http request (add a timeout)? and also see its error messages? What’s the most efficient way to deserialize?

Wouldn’t something like this be nice?

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:10329/TimeoutTest/Edit/1");
var response = client.SendAsync(request, timeoutInSec: 21).Result; //timeout param that will not cause an error to be thrown, rather it sets an appropriate error code + message in the HttpResponseMessage

var errors = response.GetErrorsAsync().Result;  //easy way to get errors.  
if (errors == null)
{
    var objects = response.DeserializeAsListAsync<Dictionary<string, string>>().Result; //efficient way of deserializing (using streams instead of reading entire string as memory)
}
client.Dispose();

If those features are helpful, just copy this code (class with extension methods for HttpClient and HttpResponseMessage).

See the code

public static class HttpClientExt
{
	public static async Task<HttpResponseMessage> PostAsJsonAsync(this HttpClient client, string requestUrl, object theObj, int timeoutInSec = 0)
	{
		return await SendAsync(client, new HttpRequestMessage(HttpMethod.Post, requestUrl), theObj, timeoutInSec);
	}
	public static async Task<HttpResponseMessage> SendAsync(this HttpClient client,
	   HttpRequestMessage request,

	   object objToSend = null,
	   int timeoutInSec = 0)
	{
		var cts = new CancellationTokenSource();
		if (timeoutInSec > 0)
		{
			if (client.Timeout.TotalMilliseconds != -1)
			{
				client.Timeout = new TimeSpan(0, 0, 0, 0, -1);
			}
			cts.CancelAfter(TimeSpan.FromSeconds(timeoutInSec));
		}
		if(objToSend != null)
		{
			request.Content = new StringContent(
				JsonConvert.SerializeObject(objToSend),
				System.Text.Encoding.UTF8, "application/json");
		}
		
		var responseAsTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token);
		HttpResponseMessage response;
		try
		{
			response = await responseAsTask.ConfigureAwait(false);
		}
		catch (TaskCanceledException)
		{
			response = new HttpResponseMessage { StatusCode = HttpStatusCode.RequestTimeout, ReasonPhrase = $"Manual Timed out set by client: {timeoutInSec} seconds" };
		}
		catch (HttpRequestException e)
		{//Server is down
			response = new HttpResponseMessage { StatusCode = HttpStatusCode.ServiceUnavailable, ReasonPhrase = e.InnerException.Message };
		}
			
		return response;
	}

	public static async Task<string> GetErrorsAsync(this HttpResponseMessage response)
	{
		if (!response.IsSuccessStatusCode) //success = [200-299]
		{
			if(response.Content == null)
			{
				return response.ReasonPhrase;
			}
			else
			{
				var stream = await response.Content.ReadAsStreamAsync();
				var content = await StreamToStringAsync(stream);
				return content;
			}
		}
		return null;
	}

	public static async Task<List<T>> DeserializeAsListAsync<T>(this HttpResponseMessage response)
	{
		response.EnsureSuccessStatusCode();
		var stream = await response.Content.ReadAsStreamAsync();
		return DeserializeJsonFromStream<List<T>>(stream);
	}

	private static async Task<string> StreamToStringAsync(Stream stream)
	{
		string content = null;

		if (stream != null)
			using (var sr = new StreamReader(stream))
				content = await sr.ReadToEndAsync();

		return content;
	}

	private static T DeserializeJsonFromStream<T>(Stream stream)
	{
		if (stream == null || stream.CanRead == false)
			return default(T);

		using (var sr = new StreamReader(stream))
		using (var jtr = new JsonTextReader(sr))
		{
			var js = new JsonSerializer();
			return js.Deserialize<T>(jtr);
		}
	}
}

Credit goes to: https://johnthiriet.com/efficient-api-calls/

get yourself on the email list

//Powered by MailChimp - low volume, no spam

comments powered by Disqus