When creating new content, applications, and/or other custom data and services within Telligent Community, you can expose those services for external integration via REST. This offers immense flexibility in allowing your custom data to be accessed by external integrations.
[toc]
When Should I Use REST Endpoints?
Anytime you want to enable applications outside of Telligent Community to interact with your custom data, you should expose the data via REST. This can also be used in the Telligent Community UI for AJAX operations within widgets. Additionally you can wrap one or more In-Process APIs into a new REST endpoint if you wanted to provide a broader response or even limit the response to specific fields.
Our example will illustrate our standard recommended REST endpoint set for entities: Create, Update, Delete, Get, and List. You are not required to use them all if your scenario doesn't need them.
Required DLL references:
- Telligent.Evolution.Components
- Telligent.Evolution.Rest
Sample Content
The first step in the process is to have custom data or service written to create REST endpoints against. While this article focuses on custom data, you could also create a custom object to store community data that is retrieved from one or more in-process APIs and return that. In this example we will focus on a custom class called "Dog".
using System; namespace Samples { public class Dog { public Guid Id { get; set; } public string Name { get; set; } public string Breed { get; set; } } }
For the sake of this demo, we assume a supporting service exists to perform CRUD operations on this object. We refer to it as DogService and assume its methods take all required parameters individually, and group all optional parameters into an Dog<MethodName>Options class. This is the same pattern used for our [In-Process API]].
Registering REST Routes
Implementing IRestEndpoints requires a Register() method that provides you with a controller with which to register your REST routes. Routes will always start with the standard Telligent Community REST URL (<siteurl> + "/api.ashx/{version}"); we will define what the rest of the url should look like inclduing the version for each desired method. IRestEndpoints is also derived from IPlugin, so you will need to implement the basic IPlugin interface as well.
The declaration of the endpoint is actually fairly simple. The parameters are:
- int version: This allows you to have multiple versions of an endpoint at the same URL. This allows you to change an API but maintain some backwards compatibility if you so choose. This value is also used in the root of the url after api.ashx. So for example if you set this to 1, then the base url for your endpoint is http://<siteurl>/api.ashx/v1, if its 2 then /api.ashx/v2 and so forth.
- string relativeUrl: This is the addition to the base REST URL(<siteurl> + "/api.ashx/{version}") to identify this method to the handler. Together with the HTTP method and any parameter constraints, this makes the url unique.
- object parameterDefaults: Specifies default settings about the routes, such as what the REST resource name is and what REST action is being taken. It can also be used to set default values for any parameters that are optional.
- object parameterConstraints: Constraints would be used to specify things like the required format of parameters in the route url.
- HttpMethod method: Corresponds to the standard HTTP methods (GET, POST, PUT, DELETE). Here we use the HttpMethod enum.
- Func<IRestRequest, IRestResponse> handler: This is the delegate that will be invoked to handle the request and issue a response. It can either be a defined method or an inline Func<> declaration.
public void Register(IRestEndpointController restRoutes) { restRoutes.Add(1, "dogs", new { resource = "dog", action = "create" }, null, HttpMethod.Post, CreateDog); restRoutes.Add(1, "dog/{id}", new { resource = "dog", action = "update" }, null, HttpMethod.Put, UpdateDog); restRoutes.Add(1, "dog/{id}", new { resource = "dog", action = "delete" }, null, HttpMethod.Delete, DeleteDog); restRoutes.Add(1, "dog/{id}", new { resource = "dog", action = "show" }, null, HttpMethod.Get, GetDog); restRoutes.Add(1, "dogs", new { resource = "dog", action = "list" }, null, HttpMethod.Get, ListDogs); }
Each Rest method accepts an IRestRequest object and returns an object that implements IRestResponse. The request object is expected to have all parameters contained in either the PathParameters dictionary or Form collection. The contents of PathParameters are what is parsed from the url path; the contents of Form are parsed from the request url query string. Typically, required parameters will be in the PathParameters object while optional parameters will be in Form. If a required parameter is missing or does not fulfill the requirements, an error response can be returned. If all required parameters are included, each can be parsed and then used with the underlying internal service to perform the operation requested.
The functions for performing each action are declared separately. First, we must create a Response object so that we can send the data in a known format.
[XmlRoot(ElementName = "Response")] [RestAction(RestAction.Show, "achievement")] public class DogResponse : IRestResponse { private Dog _dog; public Dog Dog { get { return _dog; } set { _dog = value; } } public string Name { get { return "Dog"; } } public object Data { get { return _dog; } } public string[] Errors { get; set; } }
Create is the first time this object is known to your service, so it needs all required parameters to create the object. In this example, the Id creation is handled internally so the only required parameter is Name. To check for a required parameter, we just check if it exists in the PathParameters dictionary and either return early if missing, or assign to a variable if present. Additional checks could be made here if, for instance, a required parameter needed to be an integer. Checking for optional parameters is easier; we only need to assign it to the options object if it does exist in PathParameters. Once all the information you need is collected, you can make a call to your internal service, perform the necessary operations, and return relevant information in the REST response. It is important to wrap these steps in a try...catch block so you can ensure you return a REST response to the request, and not an ASP.net error.
public IRestResponse CreateDog(IRestRequest request) { var response = new DogResponse(); if (!request.PathParameters.ContainsKey("Name")) { response.Errors = new[] { "Name is required" }; return response; } var name = request.PathParameters["Name"].ToString(); var options = new DogCreateOptions(); if (request.PathParameters.ContainsKey("Breed")) options.Breed = request.PathParameters["Breed"].ToString(); try { var createdDog = DogService.Create(name, options); response.Dog = createdDog; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
Update uses the Id originally returned from Create to find the object, which allows all other parameters to be optional and only change if specified.
public IRestResponse UpdateDog(IRestRequest request) { var response = new DogResponse(); if (!request.PathParameters.ContainsKey("Id")) { response.Errors = new[] { "Id is required" }; return response; } var id = Guid.Parse(request.PathParameters["Id"].ToString()); var options = new DogUpdateOptions(); if (request.PathParameters.ContainsKey("Name")) options.Name = request.PathParameters["Name"].ToString(); if (request.PathParameters.ContainsKey("Breed")) options.Breed = request.PathParameters["Breed"].ToString(); try { var createdDog = DogService.Update(id, options); response.Dog = createdDog; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
Delete only needs the Id of the object to remove it from the database.
public IRestResponse DeleteDog(IRestRequest request) { var response = new DefaultRestResponse(); if (!request.PathParameters.ContainsKey("Id")) { response.Errors = new[] { "Id is required" }; return response; } var id = Guid.Parse(request.PathParameters["Id"].ToString()); try { DogService.Delete(id); } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
Get also needs only the Id to return the full object.
public IRestResponse GetDog(IRestRequest request) { var response = new DogResponse(); var options = new DogGetOptions(); if (!request.PathParameters.ContainsKey("Id")) { response.Errors = new[] { "Id is required" }; return response; } options.Id = Guid.Parse(request.PathParameters["Id"].ToString()); try { var dog = DogService.Get(options); response.Dog = dog; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
List is the most flexible method in terms of what can be returned from it, and therefore includes no required parameters but many additional parameters for customizing the returned list.
public IRestResponse ListDogs(IRestRequest request) { var response = new DefaultRestResponse(); var options = new DogListOptions(); if (request.PathParameters.ContainsKey("Name")) options.Name = request.PathParameters["Name"].ToString(); if (request.PathParameters.ContainsKey("Breed")) options.Breed = request.PathParameters["Breed"].ToString(); if (request.PathParameters.ContainsKey("SortBy")) options.SortBy = request.PathParameters["SortBy"].ToString(); if (request.PathParameters.ContainsKey("SortOrder")) options.SortOrder = request.PathParameters["SortOrder"].ToString(); if (request.PathParameters.ContainsKey("PageSize")) options.PageSize = Int32.Parse(request.PathParameters["PageSize"].ToString()); if (request.PathParameters.ContainsKey("PageIndex")) options.PageIndex = Int32.Parse(request.PathParameters["PageIndex"].ToString()); try { var dogs = DogService.List(options); response.Name = "Dogs"; response.Data = dogs; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
Using the Registered Routes
For information on making calls to REST end points, see REST API: Making Requests.