Adding Custom Routes to the REST API
- 1 Overview
- 2 Plugin Structure
- 2.1 Configure
- 2.2 Requests
- 2.2.1 Request class
- 2.3 DTO request classes cannot be shared across routes/services
- 2.4 Responses
- 2.4.1 Response class
- 2.5 Services
- 2.5.1 Service class
- 2.5.2 Alternate Method - Using Jiwa business logic
- 2.5.3 Alternate Request and Response
- 2.5.4 Configure
- 2.6 Requires Business Logic JiwaDebtors
- 3 Restarting After Plugin Changes
- 4 Consuming the new routes
- 5 Related articles
Overview
It is possible to add your own custom routes to the Jiwa 7 REST API. This is achieved via a plugin.
A sample plugin is installed but disabled - "REST API Custom Routes Example". This plugin demonstrates how you can add your own custom routes without altering the standard Jiwa REST API plugin.
What follows in this article is an analysis of the plugin "REST API Custom Routes Example" and how it adds a route for consumers of the REST API to retrieve contacts for a nominated debtor.
Plugin Structure
The plugin consists of four distinct components - Configure, Requests, Responses and Services.
Configure
The plugin should have a class which implements the JiwaFinancials.Jiwa.JiwaApplication.IJiwaRESTAPIPlugin interface. It includes a Configure public method. The method is invoked when your implemented service starts (e.g. self hosted Jiwa 7 API or IIS Site).
You can have multiple plugins which have implementations of IJiwaRESTAPIPlugin - each plugin will be invoked in turn by order of the Execution Order of the plugin.
The Configure method is also where the routes are defined.
Upon examining the plugin "REST API Custom Routes Example", you will see the following class implementing the IJiwaRESTAPIPlugin interface:
Class implementing IJiwaRESTAPIPlugin
namespace MyCustomNamespace
{
public class RESTAPIPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaRESTAPIPlugin
{
public void Configure(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin, ServiceStack.ServiceStackHost AppHost, Funq.Container Container, JiwaFinancials.Jiwa.JiwaApplication.Manager JiwaApplicationManager)
{
AppHost.RegisterService<CustomServices>();
AppHost.Routes.Add(typeof(MyDebtorContactGetRequest), "/Debtors/{DebtorID}/MyContacts", "GET", "Retrieves a list of contacts for a debtor.", "");
}
}
}Plugins extending the API should always have a namespace around their classes.
Failure to place DTO classes (request and response classes) in a namespace will result in those classes being excluded from code generation for strongly typed clients.
Let's analyse what is going on here.
We have provided a Configure method as required by the interface.
The AppHost.RegisterService<CustomServices>() is telling ServiceStack to register the class CustomServices (defined further down in the plugin). This is so ServiceStack can autowire things up on the next line where we add a route:
AppHost.Routes.Add(typeof(MyDebtorContactGetRequest), "/Debtors/{DebtorID}/MyContacts", "GET"...
In the above, we are adding a single route: /Debtors/{DebtorID}/MyContacts. We're also telling ServiceStack that the route will use a request DTO (Data Transfer Object - another term for a simple class or POCO) type of MyDebtorContactGetRequest - which is defined later in the plugin. Now that ServiceStack knows what DTO type the route wants as a request, it will look at all the public methods of all registered services for one that accepts this DTO type as a parameter - it will find the Get method of the CustomServices class in this case. That will be the method invoked when the route is called.
The {DebtorID} part of the route refers to a route parameter - in this case it is a variable placeholder parameter.
For example, calling /Debtors/0000000061000000001V/MyContacts will create a new instance of the MyDebtorContactGetRequest class, populate the public property DebtorID with 0000000061000000001V and invoke the Get method of a the CustomServices class, passing this new instance of the request DTO MyDebtorContactGetRequest as a parameter.
Requests
The request DTO is simply a class with some public properties. We specify what response DTO is returned by the service with the IReturn<MyDebtorContactGetResponse> implementation. This is an empty implementation, it's simply used to signify to ServiceStack what response DTO is expected from this request purely for consumers of the API which choose to use ServiceStack clients.
We only have one property in this DTO in this example: DebtorID. Because it's named the same as the placeholder variable {DebtorID} when we defined the route - it gets automatically set for us. So, ServiceStack will create an instance of our request DTO below, set the DebtorID property and then invoke the method on the CustomServices class which has a request type of MyDebtorContactGetRequest, passing the newly created object as the request parameter.
Request class
[ApiResponse(200, "Contacts read OK")]
[ApiResponse(401, "Not authenticated")]
[ApiResponse(403, "Not authorised")]
[ApiResponse(404, "No debtor with the DebtorID provided was found")]
public class MyDebtorContactGetRequest : IReturn<MyDebtorContactGetResponse>
{
public string DebtorID { get; set; }
}The [ApiResponse] attributes decorating the request DTO indicate what possible HTTP status codes might be expected. This is used for generating the Open API Specification document.
DTO request classes cannot be shared across routes/services
You must have a unique request DTO class for each route. The name of the class needs to be unique, otherwise there is ambiguity as to which service method should handle the request.
This is necessary because some clients (such as javascript) do not have a namespace concept.
Responses
The response DTO is also simply a class with some public properties.
Response class
public class MyDebtorContactGetResponse
{
public List<CN_Contact> Contacts {get; set;}
}This class will get serialised to JSON, XML or CSV depending on the route requested, the Accept headers and the DefaultContentType configured:
If a client calls the route /Debtors/0000000061000000001V/MyContacts, then the result is returned as defined as the DefaultContentType configured (JSON is configured as the DefaultContentType in the Jiwa 7 REST API plugin)
If a client calls the route /Debtors/0000000061000000001V/MyContacts, and the client has set a HTTP Accept header of application/json then the result is returned as JSON
If a client calls the route /Debtors/0000000061000000001V/MyContacts.json, then the result is returned as JSON
If a client calls the route /Debtors/0000000061000000001V/MyContacts?format=json, then the result is returned as JSON
If a client calls the route /Debtors/0000000061000000001V/MyContacts, and the client has set a HTTP Accept header of application/xml then the result is returned as XML
If a client calls the route /Debtors/0000000061000000001V/MyContacts.xml, then the result is returned as XML
If a client calls the route /Debtors/0000000061000000001V/MyContacts?format=xml, then the result is returned as XML
If a client calls the route /Debtors/0000000061000000001V/MyContacts, and the client has set a HTTP Accept header of application/csv then the result is returned as a CSV File
If a client calls the route /Debtors/0000000061000000001V/MyContacts.csv, then the result is returned as a CSV File.
If a client calls the route /Debtors/0000000061000000001V/MyContacts?format=csv, then the result is returned as a CSV File.