JSendApiController
extends ASP.NET Web API 2's ApiController
and enables easy generation of JSend-formatted responses.
JSendClient
complements the controller, by wrapping around HttpClient
and providing an easy way to send HTTP requests and parse JSend-formatted responses.
JSend.WebApi
public class ArticlesController : JSendApiController
{
public IHttpActionResult Get(int id)
{
var article = _repo.Get(id);
if (article != null)
return JSendOk(article);
return JSendNotFound();
}
}
JSend.Client
using (var client = new JSendClient())
{
var getResponse = await client.GetAsync<Article>("http://localhost/articles/4");
var existingArticle = getResponse.GetDataOrDefault();
var postResponse = await client.PostAsync<Article>("http://localhost/articles/", article);
var newArticle = postResponse.Data;
var deleteResponse = await client.DeleteAsync("http://localhost/articles/4");
deleteResponse.EnsureSuccessStatus(); //throws if the response's status is not "success"
var putResponse = await client.PutAsync("http://localhost/articles/4", existingArticle);
if (! putResponse.IsSuccess)
Logger.Log(putResponse.Error);
}
The return value of a JSendApiController
action is converted to a HTTP response as follows:
Actions that don't return anything are converted to a 200 response with its status set to success
.
public class ArticlesController : JSendApiController
{
public void Post()
{
}
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"status" : "success",
"data" : null
}
IHttpActionResult
The JSendApiController
provides several helper methods to easily build JSend-formatted responses.
Here's a full list of these helpers methods and examples responses.
A simple example:
public class ArticlesController : JSendApiController
{
private readonly IRepository<Article> _repo = //...;
public IHttpActionResult Post(Article article)
{
if (!ModelState.IsValid)
return JSendBadRequest(ModelState); // Returns a 400 "fail" response
_repo.Store(article);
return JSendCreatedAtRoute("GetArticle", new {id = article.Id}, article); // Returns a 201 "success" response
}
[Route("articles/{id:int}", Name = "GetArticle")]
public IHttpActionResult Get(int id)
{
var article = _repo.Get(id);
return JSendOk(article); // Returns a 200 "success" response
}
}
The Post
action above will return one of the following HTTP responses:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: http://localhost/articles/5
{
"status" : "success",
"data" : {
"title" : "Ground-breaking study discovers how to exit Vim"
}
}
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"status" : "fail",
"data" : {
"article.Title" : "The Title field is required."
}
}
For all other return types (*), they'll be wrapped in a 200 response with its status set to success
,
public class ArticlesController : JSendApiController
{
public IEnumerable<Article> Get()
{
return GetAllArticlesFromDb();
}
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"status": "success",
"data": [
{
"title": "Why Are So Many Of The Framework Classes Sealed?"
},
{
"title": "C# Performance Benchmark Mistakes, Part One"
}
]
}
(*) Except HttpResponseMessage
, which is converted directly to an HTTP response.
Depending on the current IncludeErrorDetailPolicy
and on whether the client is local or remote,
exceptions thrown by JSendApiController
actions will be formatted as either:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json; charset=utf-8
{
"status": "error",
"message": "Operation is not valid due to the current state of the object.",
"data": "System.InvalidOperationException: Operation is not valid due to the current state of the object.
}
or as:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json; charset=utf-8
{
"status": "error",
"message": "An error has occurred."
}
The default behavior is to show exception details to local clients and hide them from remote clients.
JSendAuthorize
attribute is available and replaces the Authorize
attributeawait client.GetAsync<Article>(uri);
await client.PostAsync<Article>(uri, article); // If you expect an updated article back
await client.PostAsync(uri, article); // If you don't expect data back
await client.PutAsync<Article>(uri, article); // If you expect an updated article back
await client.PutAsync(uri, article); // If you don't expect data back
await client.DeleteAsync(uri);
If you expect the API to always return a "success response"...
//... and you don't need any data
response.EnsureSuccessStatus(); //(throws if response was not successful)
//... and you expect the response to always contain data
var article = response.Data; //(throws if response was not successful or did not contain data)
//... and you're not sure whether the response contains data
var article = response.GetDataOrDefault();
var article = response.GetDataOrDefault(new Article());
if (response.HasData) { ... }
If the API might return a "fail/error response" (e.g., because a resource was not found)...
//... and you don't need any data
if (response.IsSuccess) { ... }
//... and you need the data
var article = response.GetDataOrDefault();
var article = response.GetDataOrDefault(new Article());
if (response.HasData) { ... }
//... and you want to handle the error
if (! response.IsSuccess) { Logger.Log(response.Error); }
If you want to know more details about the response, such as its status code, you can use the JSendResponse.HttpResponse
property to access the original HTTP response message.
if (response.HttpResponse.StatusCode == HttpStatusCode.NotFound)
{
...
}
If you want the client to perform some additional work (e.g., add a "X-Message-Id" header to all requests, or log all exceptions) you can do so by extending MessageInterceptor
:
class MessageIdInterceptor : MessageInterceptor
{
public override void OnSending(HttpRequestMessage request)
{
request.Headers.Add("X-Message-Id", Guid.NewGuid().ToString());
}
}
class LoggerInterceptor : MessageInterceptor
{
public override void OnException(ExceptionContext context)
{
Logger.Log(context.Exception);
}
}
You can then configure the client like this:
var interceptor = new CompositeMessageInterceptor(
new MessageIdInterceptor(),
new LoggerInterceptor());
var settings = new JSendClientSettings
{
MessageInterceptor = interceptor,
SerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
}
};
var client = new JSendClient(settings);
To install JSend.WebApi, run the following command in the Package Manager Console
PM> Install-Package JSend.WebApi
To install JSend.Client, run the following command in the Package Manager Console
PM> Install-Package JSend.Client
Or download the binaries/source code from here.