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
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.
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.
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.
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
}
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
comments powered by Disqus