Aug 04

Since starting the project in Jan 2015 one of the most asked for features has been full static support (you have always been able to cast responses via duck typing). Inspired by libraries written in dynamic languages like JavaScript and Python – the idea behind RestClient was to reduce the fiction and ceremony when working with Rest API’s, and later on HTTP in general.

I’ve always wanted to add static typing to be more inline with what is expected from a C# library, but wanted to do it in such way that the original concept remained, and that all code is backwards compatible. I’ve now spent the time required to change the package, and this is the result:

DalSoft Rest Client https://github.com/DalSoft/DalSoft.RestClient

 

Getting Started with static types in 4.0

There is one fundamental change you need to make in your code to take the static typing Blue pill: you need to declare the Rest Client as RestClient or var, rather than dynamic.

// Red pill dynamic rest client  
dynamic restClient = new RestClient("https://jsonplaceholder.typicode.com");

// Blue pill static rest client    
var restClient = new RestClient("https://jsonplaceholder.typicode.com");
// Or 
restClient restClient = new RestClient("https://jsonplaceholder.typicode.com");

So far nothing looks radically different, but check out the fully chained intellisense in Visual Studio.

Resources

The only limitation with the statically typed client is you can’t use dynamic chaining to create the the url, instead you use the Resource method. The Resource method works exactly the same way as it does when using the dynamic Rest Client, it takes a string representing the resource. When using the statically typed client it also takes an expression more on this later.

var restClient = new RestClient("https://jsonplaceholder.typicode.com");

await restClient.Resource("users/1").Get();

// You can also chain Resource methods to make code more readable
await restClient.Resource("users").Resource("1").Get();

Headers

Headers work exactly the same way as the dynamic Rest Client, you either pass an anonymous object or a Header dictionary to the Headers method.

var restClient = new RestClient("https://jsonplaceholder.typicode.com");

// Header dictionary
await restClient
   .Headers(new Headers { { "Content-Type", "text/html" } })
   .Resource("users/1").Get();

// Or anonymous object
await restClient
   .Headers(new { ContentType = "text/html" } )
   .Resource("users/1").Get();

Query String

Again Query Strings work exactly the same way as the dynamic Rest Client, you pass an anonymous object to the Query method. Like the dyanmic Rest Client arrays are supported.

var restClient = new RestClient("https://jsonplaceholder.typicode.com");

// Header dictionary
await restClient
   .Query(new { id = 1 })
   .Resource("users/1").Get();

Casting Request body / Responses

The cool thing with the static Rest Client is you can take advantage of the dynamic Rest Client’s duck typing which gives you the best of both worlds.

var restClient = new RestClient("https://jsonplaceholder.typicode.com");

// Posting and casting using duck typing
User user = await restClient
   .Resource("users")
   .Post(new { id = 1, name = "William Windsor" });

You can also use more traditional generics.

var restClient = new RestClient("https://jsonplaceholder.typicode.com");

// Posting and casting using generics
var user = await restClient
   .Resource("users")
   .Post<User, User>(new { id = 1, name = "William Windsor" });

// For completeness http Get using generics
var user = await restClient
   .Resource("users/1")
   .Get<User>();

Or mix generics and duck typing

var restClient = new RestClient("https://jsonplaceholder.typicode.com");

// Post using generics and cast the response using duck typing
User user = await restClient
   .Resource("users")
   .Post(new User { Id = 1, Name = "William Windsor" });

// Cast the response to HttpResponseMessage using duck typing
HttpResponseMessage response = await restClient
   .Resource("users")
   .Post(new User { Id = 1, Name = "William Windsor" });

Extensions

You have always been able to add extensions using the dynamic Rest Client like so:

public static class RestClientExtensions
{
  // Dynamic Rest Client extension
   public static async Task GetUserByIdDynamic(this RestClient restClient, int id) => await ((dynamic)restClient).users(id).Get();
}

This isn’t exactly obvious as you have to cast back to a dynamic. Using the static version of the Rest Client creating extensions has been formalized and simplified. This is done using Resource classes…

Extending Using Resource Classes

A resource class is just a POCO class that has methods and properties returning strings. The string represents a resource that is appended to base url. You pass the Resource class as a generic to the Resource method, and then pass expression returning the Resource you want to act on.

public class UsersResources
{
   public string Departments => $"users/{nameof(Departments)}";
        
   public string GetUser(int id)
   {
      return $"users/{id}";
    }
}

var restClient = new RestClient("https://jsonplaceholder.typicode.com");

// GET /users/departments
var user = await restClient.Resource<UsersResources>
(
   resource => resource.Departments
)
.Get();

// GET /users/1
var user = await restClient.Resource<UsersResources>
(
   resource => resource.GetUser(1)
)
.Get();

As you can see it’s trivial to create maintainable and readable REST SDK’s, so you don’t have to resort to generating horrible code. Any expression that that returns a string should work, and nesting is supported.

Bonus Items

In version 4.0 I added a couple of bonus items:

HttpClient

Using either a dynamic or static Rest Client you can cast back to an HttpClient.

var restClient = new RestClient(BaseUri);
HttpClient httpClient = restClient;

var httpClientResponseString = await restClient.HttpClient.GetStringAsync(client.BaseUri + "/users/1");

Or access using the HttpClient property

var restClient = new RestClient(BaseUri);

var httpClientResponseString = await restClient.HttpClient.GetStringAsync(client.BaseUri + "/users/1");
Authorization method

I also added an Authorization method that adds the Authorization header for Basic or Bearer Authorization Schemes. This works with dynamic or static clients.

var restClient = new RestClient(BaseUri);
// Bearer Token
await restClient.Authorization(AuthenticationSchemes.Bearer, "MyBearerToken").Get();

// Basic Authorization 
await client.Authorization(AuthenticationSchemes.Basic, "MyUserName", "MyPassword").Get();

Leave a Reply

preload preload preload