HttpClient
Using a specific REST library like Refit is the recommended way. Even though I have personally experienced that Refit would not have been allowed for any of my customers, this may well be the case.
.NET provides everything to create a JSON or XML-based REST client without dependencies. The disadvantage here is, of course, that you have to program out everything yourself - even the special cases.
Therefore, you must first create a wrapper that handles the HTTP communication. For simple Get or Post this is relatively simple, but can also become very complex.
Sample
public abstract class MyJsonHttpClient
{
protected HttpClient HttpClient { get; }
private string BaseAddress { get; }
public MyJsonHttpClient(HttpClient httpClient, string baseAddress)
{
HttpClient = httpClient;
BaseAddress = baseAddress;
}
protected async Task<T?> InternalGetAsync<T>(string uri)
{
using HttpResponseMessage response
= await HttpClient.GetAsync(BaseAddress + uri).ConfigureAwait(false);
return await DeserializeResponse<T>(response).ConfigureAwait(false);
}
protected async Task<TResponse?> InternalPostAsync<TBody, TResponse>(string uri, TBody body)
{
string jsonContent = JsonSerializer.Serialize(body, s_jsonSerializerOptions);
using StringContent httpContent = new(jsonContent, Encoding.UTF8, "application/json");
using HttpResponseMessage response
= await HttpClient.PostAsync(BaseAddress + uri, httpContent).ConfigureAwait(false);
return await DeserializeResponse<TResponse>(response).ConfigureAwait(false);
}
protected JsonSerializerOptions s_jsonSerializerOptions
= new() { PropertyNameCaseInsensitive = true };
protected async Task<T?> DeserializeResponse<T>(HttpResponseMessage responseMessage)
{
Stream bodyStream
= await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false);
return await JsonSerializer
.DeserializeAsync<T>(bodyStream, s_jsonSerializerOptions)
.ConfigureAwait(false);
}
}
This abstract base class can now be used to build your own API. The abstraction keeps the actual API implementation lean.
public class MyTaskAPI : MyJsonHttpClient
{
public MyTaskAPI(HttpClient httpClient, string baseAddress)
: base(httpClient, baseAddress)
{ }
public Task<TaskApiModel?> GetTask(int id)
=> InternalGetAsync<TaskApiModel>($"/tasks/{id}");
public Task<TaskApiModel[]?> GetAllTasks()
=> InternalGetAsync<TaskApiModel[]>("/tasks");
public Task<TaskApiModel?> AddTask(TaskApiCreateModel newTask)
=> InternalPostAsync<TaskApiCreateModel, TaskApiModel>("/tasks", newTask);
}
The implementation can then - as with the other libraries - simply be used to query, create or manipulate the corresponding resources.
HttpClient httpClient = new HttpClient(); // single instance or use HttpClientFactory
MyTaskAPI myApi = new MyTaskAPI(httpClient, "https://my-rest-api.com");
// Get single task
TaskApiModel? task = await myApi.GetTask(123);
// Get all tasks
TaskApiModel[]? tasks = await myApi.GetAllTasks();
// Add new task
TaskApiCreateModel newTask = new TaskApiCreateModel { Title = "Buy flowers" };
TaskApiModel? addedTask = await myApi.AddTask(newTask);
- No dependency
- Full implementation control
- Very time-consuming, especially for optimizations
- Very high test effort (unit tests + integration tests)
- High risk of error
- High maintenance effort
Full Sample
See the full sample on GitHub: https://github.com/BenjaminAbt/dotnet.rest-samples