Restract is a contract based easy to use rest client .
PM> Install-Package Restract
- .Net Core support
- Interceptor support (for customising requests/response before request sent or after response received)
- Async support
- DI friendly
You just need to create the service contract and use RestClientFactory to create the http client.
A contract is an interface describing a resource information with all actions signature. Resource information is represented by IResourceDescriptor that has tow important properties: ResourceUrl and Paramters . Action signature is represented by IResourceActionDescriptor that four important properties: ActionPath, Method, Parameters, ResultDataType Parameters can be of type Uri, Body, Header, Cookie, CancellationToken.
[Header("HeaderItem1", "FixedValue")] // a header parameter in resource with a fix value
[Resource("api/customers")] // ResourceUrl
public interface ICustomersService
{
//describing Get action for api/customers resource
[Header("HeaderItem2", "FixedValue")] // a header parameter in action with a fix value
IEnumerable<Customer> Get();
// GET api/customers?id=5
Customer Get(int id); // Get action with id paramter as url parameter (parameters with primitive type will be considered as url parameter by default)
// POST api/customers
void Post(Customer customer); // Post action with Customer object as body url (parameters with complex type will be considered as body parameter by default)
// PUT api/customers?id=5
void Put(int id, Customer customer);
[ResourceAction(Method.Put, "/updateName/{id}")]
void UpdateName(int id, [Body]string name); // Put action with id parameter in url and string paramter in body
// DELETE api/customers
[ResourceAction(Method.Delete)]
void DeleteAll([Header]string secret); // Delete action with secret parameter in header
}
By using RestClientFactory you would be able to create http client from contracts.
var customerServiceFactory = new RestClientFactory("http://localhost/testapi"); // create a client factory with "http://localhost/testapi" as baseUrl.
var client = customerServiceFactory.CreateClient<ICustomersService>(); // create the http client object
var customers = client.Get(); // Do actual http call and receive customers. Easy ;-)
Interceptors can have multiple purpose, modify the http request or response, logging, mocking, exception hadling, ... An Intercetor is basically a DelegatingHandler used by HttpClient internally,
//using RestClientConfiguration object for more configuration options
var config = new RestClientConfiguration("http://localhost/testapi", new MyAuthorizationInterceptor()); // Adding first interceptor using constructor
// Adding more interceptors
config.AddInterceptor<MyAuthInterceptor>(); // Adding interceptor by type
config.AddInterceptor(new DummyInterceptor()); // Adding interceptor by instance
config.AddInterceptor(() => new DummyInterceptor2()); // Adding interceptor by factory method
var customerServiceFactory = new RestClientFactory(config);
Once you have created the client by using factory.CreateClient() you can use all contract methods.
var customers = client.Get();
var myCustomer = client.Get(1);
client.Post(new Customer()
{
Id = 1000,
Name = "New",
Family = "2",
BirthDate = new DateTime(2000, 1, 1)
});
You can use HttpResponseMessage or HttpResponseMessage<> in contract for accessing http response object on the consumer side
[Resource("api/customers")]
public interface ICustomersService
{
IEnumerable<Customer> Get();
// GET api/customers
HttpResponseMessage<IEnumerable<Customer>> GetWithResponse();
// POST api/customers
HttpResponseMessage Post(Customer customer);
// GET api/customers/5
HttpResponseMessage<Customer> GetWithResponse(int id);
}
You can use Task or Task<> in contract for calling rest endpoint in async manner also you can add CancellationToken as parameter to your action.
[Resource("api/customers")]
public interface ICustomersService
{
// GET api/customers
IEnumerable<Customer> Get();
// GET api/customers
Task<IEnumerable<Customer>> GetAsync();
// GET api/customers
[ResourceAction(Method.Get)]
Task<HttpResponseMessage<IEnumerable<Customer>>> GetWithResponseAsync();
// GET api/customers/5
Task<RestResponse<Customer>> GetAsync(int id, CancellationToken token);
// POST api/customers
Task Post(Customer customer);
// POST api/customers
Task<HttpResponseMessage> Post(Customer customer);
}
public class MyAuthorizationInterceptor : HttpMessageInterceptor
{
public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, IResourceActionDescriptor resourceActionDescriptor, MethodCallInfo methodCallInfo, CancellationToken cancellationToken)
{
request.Headers.Add("my-auth", "123");
var resp = await base.SendAsync(request, resourceActionDescriptor, methodCallInfo, cancellationToken);
Console.WriteLine("Response:" + resp.StatusCode);
return resp;
}
}
You can define custom parameters in resource/action level and use ParameterValueResolver to get the values in every call
class Program {
void main() {
var config = new RestClientConfiguration("http://localhost/test/");
var customerServiceFactory = new RestClientFactory(config);
var client = customerServiceFactory.CreateClient<ICustomersService>();
...
}
}
[Resource("api/subscriptions/{subscriptionId}/customers/{customerId}/accounts")]
[Uri("subscriptionId", ValueResolver = typeof(AppSettingsValueResolver))]
[Header("HeaderItem", "StaticValue")]
public interface IAccountsService
{
IEnumerable<Account> Get(string customerId, [Header]string correlationId);
}
public class AppSettingsValueResolver : ParameterValueResolver
{
public override object GetValue(string parameterName, MethodCallInfo callInfo)
{
return ConfigurationManager.AppSettings[parameterName];
}
}
Restract uses an internal service(ITypeActivator) for activating Intercetors and ParamterValueResolvers. You can replace this service with your own version by using RectClientFactory.UerTypeActivator() extension method. This way you can use your own DI (Autofac, Ninject, AspnetCoreDI, ...) to activate the type.
private RestClientFactory factory;
public void ConfigureServices(IServiceCollection services)
{
...
//configuring restclientfactory
factory = new RestClientFactory("http://localhost/api");
factory.Configuration.AddInterceptor<MyAuthInterceptor>();
//register IConfigReader as service
services.AddTransient<IConfigReader, ConfigReader>();
//register MyAuthInterceptor as service
services.AddTransient<MyAuthInterceptor, MyAuthInterceptor>();
services.AddTransient(p => factory.CreateClient<ICustomerService>());
services.AddMvc();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
...
factory.UseTypeActivator(type => serviceProvider.GetService(type)); //using serviceProvider to activate MyAuthInterceptor.
...
app.UseMvcWithDefaultRoute();
}
public class MyAuthInterceptor : HttpMessageInterceptor
{
private IConfigReader _configReader;
public MyAuthInterceptor(IConfigReader configReader)
{
_configReader = configReader;
}
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, IResourceActionDescriptor resourceActionDescriptor, MethodCallInfo methodCallInfo, CancellationToken cancellationToken)
{
request.Headers.Add("MyAuth", _configReader["MyAuthValue"]);
return base.SendAsync(request, resourceActionDescriptor, methodCallInfo, cancellationToken);
}
}
You can replace internal services with your own custom service using RestClientFactory.DependencyResolver property
using Restract;
class Program {
void main() {
var customerServiceFactory = new RestClientFactory("http://localhost/test/");
customerServiceFactory.DependencyResolver.AddSingletone<IResourceDescriptorResolver, MyResourceDescriptorResolver>();
var client = customerServiceFactory.CreateClient<ICustomersService>();
...
}
}
Here are some useful internal service that you might want to replace them with your own implementation. To see full list of internal services hafve a look at InitDependencyResolver() method in RestClientFactory class.
This service is responsible for resolving ResourceDescriptor object from resource type.
Default implementation : ResourceDescriptorResolver
public interface IResourceDescriptorResolver
{
IResourceDescriptor Resolve(Type resrouceType, RestClientConfiguration configuration);
}
This service is responsible for resolving ResourceActionDescriptor object from action MethodInfo.
Default implementation : ResourceActionDescriptorResolver
public interface IResourceActionDescriptorResolver
{
ResourceActionDescriptor Resolve(MethodInfo methodInfo, RestClientConfiguration configuration, ResourceDescriptor resourceDescriptor);
}
This service is responsible for creating HttpRequestMessage object by using resourceActionDescriptor and invocation.
Default implementation : HttpRequestMessageFactory
public interface IHttpRequestMessageFactory
{
HttpRequestMessage GetHttpRequest(ResourceActionDescriptor resourceActionDescriptor, IMethodCallMessage invocation, RestClientConfiguration restClientConfiguration);
}
This service is responsible for serialzing/deserializing request content/response content.
Default implementation : NewtonsoftJsonSerializer
public interface ISerializer
{
string Serialize(object obj);
T Deserialize<T>(string objString) where T : new();
object Deserialize(string objString, Type objectType);
}
- Configuration file support