Creating a Custom API

With the Client Extension, you can create custom APIs. For example, you can create an API that involves custom tables and schema to generate custom reports. To see all the features available to you, we will create a custom API that pulls data from the Database. For this example, we're going to use a query that gets the top x Associates over the last y days.

At the end of this tutorial, we'll have an Extension structure like this:

Demo\Api\GetHighestEarnersApi.cs
Demo\Api\GetHighestEarnersRequest.cs
Demo\Api\GetHighestEarnersResponse.cs
Demo\Api\HighEarner.cs

📘Note

Replace any instance of Demo with your client ID.



Building Models

There are three models we need to build:

1. GetHighestEarnersRequest

namespace Demo.Api
{
    public class GetHighestEarnersRequest
    {
        public int NumberOfAssociates { get; set; }
        public int NumberOfDays { get; set; }
    }
}

2. HighEarners

namespace Demo.Api
{
    public class HighEarner
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string BackofficeID { get; set; }
        public int HighestRank { get; set; }
        public double Amount { get; set; }
    }
}

3. GetHighestEarnersResponse

namespace Demo.Api
{
   public class GetHighestEarnersResponse
   {
       public HighEarner[] HighestEarners { get; set; }
   }
}



Building the Endpoint (Simplified)

1. Declare proper using directives:

using Dapper;
using DirectScale.Disco.Extension.Api;
using DirectScale.Disco.Extension.Services;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;

2. Implement the Simplified Interface:

public class GetHighestEarnersApi : ApiEndpoint<GetHighestEarnersRequest>
    {

ApiEndpoint<TRequest> is a simplified interface that lets you skip a few steps and define an object. The HTML request body is automatically parsed as JSON into the specified model (GetHighestEarnersRequest).

The JSON API request, in this case, would look like this:

{ 
"NumberOfAssociates": 1, 
"NumberOfDays": 30 
}

3. Create a method to Define the API

private readonly IDataService _dataService;

public GetHighestEarnersApi(IDataService dataService, IRequestParsingService requestParsingService) : base("demo/GetHighestEarners", AuthenticationType.V1, requestParser: requestParsingService)
{
    _dataService = dataService;
}
base("demo/GetHighestEarners"

This is the API pathname. Replace Demo with your client ID.

AuthenticationType.V1

AuthenticationType sets the authentication type to use for the endpoint. In this example, we've set it to V1. The alternate option is None.

❗Warning

If set to None, your API will be publically exposed and can be called by anyone. Furthermore, you will have to do authentication manually (see Headers). DirectScale's authentication procedures do not verify authorization. If your custom API is doing something for a specific Associate, make sure that only the Associate initiates the request.

3. Declare the HTTP response:

public override IApiResponse Post(GetHighestEarnersRequest request)

The interface IApiResponse provides the ability to specify the HTTP response content of a custom API call. Implement Post(ApiRequest) to create a custom API endpoint.

4. Connect to the Database

Connect to the Database to allow the SQL query to get data.

using (var _dbConnection = new SqlConnection(_dataService.ConnectionString.ConnectionString))

ConnectionString is a read-only connection string for accessing DirectScale and client custom tables. Use ClientConnectionString to access only client custom tables.

5. Build the SQL Query

string query = @"SELECT top @NumberOfAssociates d.FirstName, d.LastName, d.BackofficeID, s.HighestRank, SUM(h.Amount) AS Amount
                      FROM CRM_CommissionHistory h
                      LEFT JOIN CRM_Distributors d ON h.AssociateID = d.recordnumber
                      LEFT Join CRM_Stats s on h.AssociateID = s.AssociateID
                      WHERE h.PostDate >= DATEADD(DAY, -@NumberOfDays, GETDATE())
                      GROUP BY d.BackofficeID, d.LastName, d.FirstName, s.HighestRank
                      ORDER BY Amount DESC;";

6. Connect the Query to the HighEarners Object

var associates = _dbConnection.Query<IEnumerable<HighEarner>>(query, request);
return new Ok(associates);

The method returns Ok, which is a class that provides a default implementation for a typical response of 200 OK, with an optional JSON object in the body.

Full Endpoint Example

using Dapper;
using DirectScale.Disco.Extension.Api;
using DirectScale.Disco.Extension.Services;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;

namespace Demo.Api
{
    public class GetHighestEarnersApi : ApiEndpoint<GetHighestEarnersRequest>
    {
        private readonly IDataService _dataService;

        public GetHighestEarnersApi(IDataService dataService, IRequestParsingService requestParsingService)
            : base("demo/GetHighestEarners", AuthenticationType.V1, requestParser: requestParsingService)
        {
            _dataService = dataService;
        }

        public override IApiResponse Post(GetHighestEarnersRequest request)
        {
            using (var _dbConnection = new SqlConnection(_dataService.ConnectionString.ConnectionString))
            {
                string query = @"SELECT top @NumberOfAssociates d.FirstName, d.LastName, d.BackofficeID, s.HighestRank, SUM(h.Amount) AS Amount
                      FROM CRM_CommissionHistory h
                      LEFT JOIN CRM_Distributors d ON h.AssociateID = d.recordnumber
                      LEFT Join CRM_Stats s on h.AssociateID = s.AssociateID
                      WHERE h.PostDate >= DATEADD(DAY, -@NumberOfDays, GETDATE())
                      GROUP BY d.BackofficeID, d.LastName, d.FirstName, s.HighestRank
                      ORDER BY Amount DESC;";
                var associates = _dbConnection.Query<IEnumerable<HighEarner>>(query, request);
                return new Ok(associates);
            }
        }
    }
}


Building the Endpoint (Expanded)

The expanded version of this endpoint will parse the same request the same way with the same response. It is a little bit different to set up. The main difference is that the Post method provides you with the ApiRequest object instead of the GetHighestEarnersRequest object.

The ApiRequest will have the JSON body and can be parsed into the GetHighestEarnersRequest object, but it also has way more information about the request headers. If you need access to any of that other information or receive a request that is not JSON, you have to use the expanded IApiEndpoint interface. Otherwise, the simplified interface is easier and will work most of the time.

Full Expanded Endpoint Example

using Dapper;
using DirectScale.Disco.Extension.Api;
using DirectScale.Disco.Extension.Services;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;

namespace Demo.Api
{
    public class GetHighestEarnersApi : IApiEndpoint
    {
        private readonly IDataService _dataService;
        private readonly IRequestParsingService _requestParsingService;

        public GetHighestEarnersApi(IDataService dataService, IRequestParsingService requestParsingService)
        {
            _dataService = dataService;
            _requestParsingService = requestParsingService;
        }

        public ApiDefinition GetDefinition()
        {
            return new ApiDefinition
            { 
                Route = "demo/GetHighestEarners",
                Authentication = AuthenticationType.V1
            };
        }

        public IApiResponse Post(ApiRequest request)
        {
            GetHighestEarnersRequest getHighestEarnersRequest = _requestParsingService.ParseBody<GetHighestEarnersRequest>(request);
            using (var _dbConnection = new SqlConnection(_dataService.ConnectionString.ConnectionString))
            {
                string query = @"SELECT top @NumberOfAssociates d.FirstName, d.LastName, d.BackofficeID, s.HighestRank, SUM(h.Amount) AS Amount
                      FROM CRM_CommissionHistory h
                      LEFT JOIN CRM_Distributors d ON h.AssociateID = d.recordnumber
                      LEFT Join CRM_Stats s on h.AssociateID = s.AssociateID
                      WHERE h.PostDate >= DATEADD(DAY, -@NumberOfDays, GETDATE())
                      GROUP BY d.BackofficeID, d.LastName, d.FirstName, s.HighestRank
                      ORDER BY Amount DESC;";
                var associates = _dbConnection.Query<IEnumerable<HighEarner>>(query, getHighestEarnersRequest);
                return new Ok(associates);
            }
        }
    }
}

You'll see that we declared an ApiDefinition class:

        public ApiDefinition GetDefinition()
        {
            return new ApiDefinition
            { 
                Route = "demo/GetHighestEarners",
                Authentication = AuthenticationType.V1
            };
        }

We declare the Route that will trigger the custom API to be called. The URL path will be prefixed with /Command/ClientAPI/ to call this custom endpoint. Do not set Route to a value starting with /.

For example, setting Route to demo/GetHighestEarners can be called from your Corporate Admin domain + /Command/ClientAPI/demo/GetHighestEarners.



Register the endpoint in ExtentionEntry.cs:

namespace Demo
{
    public class ExtensionEntry : IExtension
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IApiEndpoint, GetHighestEarnersApi>();

DirectScale unusually consumes client registrations. Because of that, the order of the dependency registrations matters. When you register an API, it resolves to an instance. If the API has dependencies that you didn't register yet, you'll get an error when trying to deploy.