patterncsharpMinor
Basic API wrapper around a Restful service
Viewed 0 times
aroundwrapperservicerestfulapibasic
Problem
I'm writing a basic wrapper around a restful service that returns a list of Stores. I'm using RestSharp which I'm injecting into the class.
Naturally, I want to make it completely testable (that's a major requirement!).
I'm also thinking that it could be more generic i.e.
Naturally, I want to make it completely testable (that's a major requirement!).
I'm also thinking that it could be more generic i.e.
GetStores but I'm struggling how to do as I need to specify things like the RootElement.public class TopManClient : ITopManClient
{
private readonly IRestClient _restClient;
private const string BaseUrl = "https://TopmanStores.Api.com/";
private const string AcceptTenant = "uk";
private const string AcceptLanguage = "en-GB";
public TopManClient(IRestClient restClient)
{
_restClient = restClient;
_restClient.BaseUrl = new Uri(BaseUrl);
_restClient.AddDefaultHeader("Accept-Tenant", AcceptTenant);
_restClient.AddDefaultHeader("Accept-Language", AcceptLanguage);
}
public IRestRequest GetRequest(string url)
{
return new RestRequest(url, Method.GET);
}
public List GetStores(string postCode)
{
var request = GetRequest("stores");
request.RootElement = "Stores";
request.AddQueryParameter("q", postCode);
var response = _restClient.Execute>(request);
if (response.ErrorException != null)
{
const string message = "Error retrieving response. Check inner details for more info.";
throw new ApplicationException(message, response.ErrorException);
}
return response.Data;
}
}Solution
I would personally prefer to use HttpClient directly as it provides full control and perfect testability - see here.
We can define our REST API as:
Where base class to simplify API definition is:
This code is fully testable.
We can define our REST API as:
// see https://jsonplaceholder.typicode.com/
// this site exposes some REST API example to automate
public interface ITypicode
{
Task GetPostsAsync();
Task GetPostAsync(int id);
}
public class Typicode : JsonRestApi, ITypicode
{
public Typicode(HttpMessageHandler handler = null)
: base("https://jsonplaceholder.typicode.com", handler)
{
}
public Task GetPostsAsync() => GetAsync("posts");
public Task GetPostAsync(int id) => GetAsync($"posts/{id}");
}
public class BlogPost
{
public int UserId { get; set; }
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}Where base class to simplify API definition is:
public abstract class JsonRestApi
{
static readonly JsonSerializerSettings Settings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new [] { new StringEnumConverter() }
};
protected JsonRestApi(string baseAddress, HttpMessageHandler handler = null)
{
Client = handler == null ? new HttpClient() : new HttpClient(handler);
Client.BaseAddress = new Uri(baseAddress);
Client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
protected async Task GetAsync(string uri) =>
await ParseJson(await Client.GetAsync(uri));
protected async Task PostAsync(string uri, object data = null) =>
await ParseJson(await Client.PostAsync(uri, JsonContent.From(data)));
protected HttpClient Client { get; }
async Task ParseJson(HttpResponseMessage response) =>
JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync(), Settings);
class JsonContent : StringContent
{
public static JsonContent From(object data) =>
data == null ? null : new JsonContent(data);
public JsonContent(object data)
: base(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")
{
}
}
}This code is fully testable.
Code Snippets
// see https://jsonplaceholder.typicode.com/
// this site exposes some REST API example to automate
public interface ITypicode
{
Task<BlogPost[]> GetPostsAsync();
Task<BlogPost> GetPostAsync(int id);
}
public class Typicode : JsonRestApi, ITypicode
{
public Typicode(HttpMessageHandler handler = null)
: base("https://jsonplaceholder.typicode.com", handler)
{
}
public Task<BlogPost[]> GetPostsAsync() => GetAsync<BlogPost[]>("posts");
public Task<BlogPost> GetPostAsync(int id) => GetAsync<BlogPost>($"posts/{id}");
}
public class BlogPost
{
public int UserId { get; set; }
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}public abstract class JsonRestApi
{
static readonly JsonSerializerSettings Settings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new [] { new StringEnumConverter() }
};
protected JsonRestApi(string baseAddress, HttpMessageHandler handler = null)
{
Client = handler == null ? new HttpClient() : new HttpClient(handler);
Client.BaseAddress = new Uri(baseAddress);
Client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
protected async Task<T> GetAsync<T>(string uri) =>
await ParseJson<T>(await Client.GetAsync(uri));
protected async Task<T> PostAsync<T>(string uri, object data = null) =>
await ParseJson<T>(await Client.PostAsync(uri, JsonContent.From(data)));
protected HttpClient Client { get; }
async Task<T> ParseJson<T>(HttpResponseMessage response) =>
JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync(), Settings);
class JsonContent : StringContent
{
public static JsonContent From(object data) =>
data == null ? null : new JsonContent(data);
public JsonContent(object data)
: base(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")
{
}
}
}Context
StackExchange Code Review Q#140412, answer score: 6
Revisions (0)
No revisions yet.