Skip to content

DenisZhukovski/WebRequest.Elegant

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WebRequest.Elegant

NuGet Downloads Stars License Hits-of-Code Lines of Code EO principles respected here PDD status

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

FkHttpMessageHandler

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

RoutedHttpMessageHandler can be used to fake a real server responses for particular URIs. All the requests' responses will be mocked by route map. The fundamental part of the handler is Route class that responsible for configuration appropriate response based on incoming request. Initially, the class can be initiated with different types of responses which are presented with IResponse interface.

Assert.AreEqual(
   "Hello world",
   await new Elegant.WebRequest(
      new Uri("http://reqres.in/api/users"),
      new RoutedHttpMessageHandler(
         new Route(
            new RouteResponse(
                "http://reqres.in/api/users", 
                "Hello world"
            ),
            new RouteResponse(
                "http://reqres.in/api/users", 
                () => "Hello world testing test." + _counter++
            ),
            new RouteResponse(
                "http://reqres.in/api/users", 
                httpRequestMessage => _responseBaseOn(httpRequestMessage)
            ),
            new ConditionalResponse(
                httpRequestMessage => httpRequestMessage.ContainsAsync("GetUsers"),
                new StringResponse("{ ""users"" : [...] }")
            ),
            new StringResponse("Response message as a text")
         )
      )
    ).ReadAsStringAsync()
);

Currently, three types of responses are introduced and can be used:

  • RouteResponse is a response that returns based on requested uri;
  • StringResponse - the most simple response that always return the specified string;
  • ConditionalResponse - the decorator helps set the condition under which a given response is returned. Can analyse incoming request message.

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

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
);

Build status

Quality Gate Status Coverage Duplicated Lines (%) Maintainability Rating

About

Object oriented web request

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages