Invoking HTTP REST APIs With a Single Line of C# Code
Let's see how to invoke HTTP REST APIs using a single line of C# code.
Join the DZone community and get the full member experience.
Join For FreeEvery now and then, I need to invoke HTTP REST APIs using C#. In my day job, for instance, I have a whole range of different systems that need to be integrated with each other for some reasons. Most of the code snippets I have seen related to this problem also tend to be scattered with repeating segments, and often, it requires some 50 lines of code to invoke a simple method in another system using C#.
This bothered me to the point where I realized I could create a tiny wrapper class around HttpClient, resulting in that I'd end up with what I refer to as "super DRY code." DRY, of course, means "Don't Repeat Yourself." The advantages of not repeating yourself in code should be obvious to most developers, so I won't even repeat the arguments.
However, if we imagine the average HTTP REST API invocation being some 25 lines of code to instantiate an HttpClient, WebClient, HttpWebRequest, etc., decorating it in the process, making sure we dispose all disposable objects correctly, converting from JSON to some domain type — and we are able to reduce these 25 lines of code to 1 simple line of code — we have reduced our LOC (Lines Of Code) by roughly 95 percent. Resulting in much more beautiful and easily maintained code and far fewer problems in general.
/*
* Example usage of my HTTP REST wrapper
*/
var client = new HttpClient();
var result = await client.PostAsync<RequestDTO, ResponseDTO>("https://foo.bar", input);
The conversion from "input" to JSON is taken care of automatically, and the resulting JSON from the invocation will be automatically converted into a ResponseDTO. And really, when you think about it, why should we need more lines of code to invoke an HTTP REST endpoint than that which we need to invoke any other method or function?
Below is the entire code for the class.
using System;
using net = System.Net.Http;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using magic.http.contracts;
namespace magic.http.services
{
public class HttpClient
{
static readonly net.HttpClient _client;
static HttpClient()
{
_client = new net.HttpClient();
_client.DefaultRequestHeaders.Add("Accept", "application/json");
}
public async Task<Response> PostAsync<Request, Response>(
string url,
Request input,
string token = null)
{
return await CreateRequest<Response>(url, net.HttpMethod.Post, input, token);
}
public async Task<Response> PutAsync<Request, Response>(
string url,
Request input,
string token = null)
{
return await CreateRequest<Response>(url, net.HttpMethod.Put, input, token);
}
public async Task<Response> GetAsync<Response>(
string url,
string token = null)
{
return await CreateRequest<Response>(url, net.HttpMethod.Get, token);
}
public async Task<Response> DeleteAsync<Response>(
string url,
string token = null)
{
return await CreateRequest<Response>(url, net.HttpMethod.Delete, token);
}
#region [ -- Private helper methods -- ]
async Task<Response> CreateRequest<Response>(
string url,
net.HttpMethod method,
string token)
{
return await CreateRequestMessage(url, method, token, async (msg) =>
{
return await GetResult<Response>(msg);
});
}
async Task<Response> CreateRequest<Response>(
string url,
net.HttpMethod method,
object input,
string token)
{
return await CreateRequestMessage(url, method, token, async (msg) =>
{
using (var content = new net.StringContent(JObject.FromObject(input).ToString()))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
msg.Content = content;
return await GetResult<Response>(msg);
}
});
}
async Task<Response> CreateRequestMessage<Response>(
string url,
net.HttpMethod method,
string token,
Func<net.HttpRequestMessage, Task<Response>> functor)
{
using(var msg = new net.HttpRequestMessage())
{
msg.RequestUri = new Uri(url);
msg.Method = method;
if (token != null)
msg.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await functor(msg);
}
}
async Task<Response> GetResult<Response>(net.HttpRequestMessage msg)
{
using (var response = await _client.SendAsync(msg))
{
using (var content = response.Content)
{
var responseContent = await content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new Exception(responseContent);
if (typeof(IConvertible).IsAssignableFrom(typeof(Response)))
return (Response)Convert.ChangeType(responseContent, typeof(Response));
return JToken.Parse(responseContent).ToObject<Response>();
}
}
}
#endregion
}
}
Using constructs such as the above makes your code more clear, easier to maintain, and significantly smaller.
Opinions expressed by DZone contributors are their own.
Comments