WebRequest.Elegant
The main idea is to wrap HttpClient type with more elegant and object oriented entity. The entity provides immutable objects by cloning itself and able to make requests to different end points. It's assumed that developers create the WebRequest entity only once in the app at startup and then clonning it in other places to complete the reuqest.
var server = new WebRequest("http://some.server.com"); // An application creates the WebRequest only once and then reuses it.
Once it has been created developers should inject it as a constructor argument to all the entities that may need it.
public class Users
{
private IWebRequest _server;
public Users(IWebRequest server)
{
_server = server;
}
public async Task<IList<User>> ToListAsync()
{
var usersRequest = server.WithRelativePath("/users"); // new WebRequest object will be created and
// refers to http://some.server.com/users
var usersResponse = await usersRequest.GetResponseAsync();
return ... // parse the resposne and create list of users
}
}
The main goal of this approach is to become a staple component for the SDKs that are built like tree structure.
Post Web Form
The example below shows how to generate web form post request and send it to the server.
await _webRequest
.WithMethod(System.Net.Http.HttpMethod.Post)
.WithBody(
new Dictionary<string, IJsonObject>
{
{ "testData", new SimpleString("some text data") },
{ "json", new JsonObject(...) }
})
.EnsureSuccessAsync();
// Short Form
await _webRequest.PostAsync(
new Dictionary<string, IJsonObject>
{
{ "testData", new SimpleString("some text data") },
{ "json", new JsonObject(...) }
})
);
Post extensions
To make the requets to be concise there are a couple PostAsync extension methods were introduced.
await _webRequest.PostAsync("Hello world");
await _webRequest.PostAsync(
new Dictionary<string, IJsonObject>
{
{ "testData", new SimpleString("some text data") },
{ "json", new JsonObject(...) }
})
);
Useful extension
This enxention method is useful when its needed to deserialize the response into an object. It was not added into the original package because JsonConvert class creates dependency on 3rd party component but it's been decided not to pin on any 3rd party libraries.
public static class WebRequestExtensions
{
public static async Task<T> ReadAsync<T>(this IWebRequest request)
{
string content = string.Empty;
try
{
content = await request
.ReadAsStringAsync()
.ConfigureAwait(false);
if (typeof(T) == typeof(string))
{
return (T)(object)content;
}
return JsonConvert.DeserializeObject<T>(content);
}
catch (Exception ex)
{
ex.Data["Content"] = content;
throw;
}
}
public static IWebRequest WithBody(this IWebRequest request, JObject body)
{
return request.WithBody(new SimpleString(body.ToString()));
}
public static Task PostAsync(this IWebRequest request, JObject body)
{
return request
.WithMethod(HttpMethod.Post)
.WithBody(body)
.EnsureSuccessAsync();
}
public static async Task<IList<T>> SelectAsync<T>(
this IWebRequest request,
Func<JObject, T> map)
{
var response = await request
.ReadAsync<List<JObject>>()
.ConfigureAwait(false);
return response.Select(map).ToList();
}
}
In Unit Tests
To help developers in writing unit tests WebRequest.Elegant package contains some useful classes to assist.
FkHttpMessageHandler can be used to fake all responses from the real server. All the requests' responses will be mocked with configured one.
await new Elegant.WebRequest(
"http://reqres.in/api/users",
new FkHttpMessageHandler("Response message as a text here")
).UploadFileAsync(filePath);
RoutedHttpMessageHandler can be used to fake a real server responses for particular URIs. All the requests' responses will be mocked by route map.
Important:
RoutedHttpMessageHandler will pass the request to original HttpClientHandler when no configured route is found.
Assert.AreEqual(
"Hello world",
await new Elegant.WebRequest(
new Uri("http://reqres.in/api/users"),
new RoutedHttpMessageHandler(
new Route(new Dictionary<string, string>
{
// It's key value pair where key is uri to be mocked and value is a message that will be responded.
{ "http://reqres.in/api/users", "Hello world" }
})
)
).ReadAsStringAsync()
);
ProxyHttpMessageHandler can be used to proxy all the requests/responses.
var proxy = new ProxyHttpMessageHandler();
await new Elegant.WebRequest(
new Uri("https://www.google.com/"),
proxy
).ReadAsStringAsync()
Assert.IsNotEmpty(proxy.RequestsContent);
Assert.IsNotEmpty(proxy.ResponsesContent);
Equal to
Despite the fact that WebRequest entity tries to encapsulate internal state it still provides smart Equal method:
Assert.AreEqual(
new Elegant.WebRequest(new Uri("http://reqres.in/api/users")),
new Uri("http://reqres.in/api/users")
);
Assert.AreEqual(
new Elegant.WebRequest(new Uri("http://reqres.in/api/users")),
"http://reqres.in/api/users"
);
Assert.AreEqual(
new Elegant.WebRequest(
new Uri("http://reqres.in/api/users")
).WithMethod(HttpMethod.Post),
HttpMethod.Post
);