Click here to Skip to main content
15,887,251 members
Articles / Programming Languages / C# 4.0
Tip/Trick

REST API Made Easy with Supido

Rate me:
Please Sign up or sign in to vote.
4.69/5 (4 votes)
6 Sep 2015MIT5 min read 8.4K   38   10  
In this tip, we will see how you make quick REST API from database defining the API in a configuration file.

Introduction

WCF is really powerful, but usually developers spend so many hours developing services in order to create REST APIs exposing their database. In this tip, we'll see how to build REST APIs based on WCF, without having to code the services neither the business objects, using Supido.

Defining API with XML File

This is the example XML using for this tip:

XML
<?xml version="1.0" encoding="utf-8" ?
<configuration
  <security sessionManager="Supido.Demo.Service.Security.SessionManager" 
	securityManager="Supido.Demo.Service.Security.SecurityManager"</security
  <service cors="true" apiPath="api"
    <api path="client" dtoName="ClientDto" parameterName="ClientId"
      <api path="project" dtoName="ProjectDto" parameterName="ProjectId"
        <api path="service" dtoName="ServiceDto" parameterName="ServiceId"
          <api path="task" dtoName="TaskDto" parameterName="TaskId"</api
        </api
      </api
    </api
    <api path="department" dto="DepartmentDto" parameterName="DepartmentId"</api
  </service
  </configuration>

As we can see, we have two sections:

  • One to define the security behaviour of our application
  • Other to define our service API:
    • if the service allow CORS
    • the API prefix path
    • The API tree. For each node, we define the route path, the name of the DTO object and the name of the keyparameter

The previous XML will automatically traduce to a service API, without coding the services, only the DTOs are needed:

  • GET /api/client
  • GET /api/client/{clientId}
  • POST /api/client/query (it's a get but receiving a query object)
  • POST /api/client/{clientId}/query (it's a get but receiving a query object)
  • POST /api/client
  • PUT /api/client
  • DELETE /api/client/{clientId}
  • GET /api/client/{clientId}/project
  • GET /api/client/{clientId}/project/{projectId}
  • POST /api/client/{clientId}/project/query (it's a get but receiving a query object)
  • POST /api/client/{clientId}/project/{projectId}/query (it's a get but receiving a query object)
  • POST /api/client/{clientId}/project
  • PUT /api/client/{clientId}/project
  • DELETE /api/client/{clientId}/project/{clientId}
  • GET /api/client/{clientId}/project/{projectId}/service
  • GET /api/client/{clientId}/project/{projectId}/service/{serviceId}
  • POST /api/client/{clientId}/project/{projectId}/service/query (it's a get but receiving a query object)
  • POST /api/client/{clientId}/project/{projectId}/service/{serviceId}/query (it's a get but receiving a query object)
  • POST /api/client/{clientId}/project/{projectId}/service
  • PUT /api/client/{clientId}/project/{projectId}/service
  • DELETE /api/client/{clientId}/project/{projectId}/service/{serviceId}
  • GET /api/client/{clientId}/project/{projectId}/service/{serviceId}/task
  • GET /api/client/{clientId}/project/{projectId}/service/{serviceId}/task/{taskId}
  • POST /api/client/{clientId}/project/{projectId}/service/{serviceId}/task (it's a get but receiving a query object)
  • POST /api/client/{clientId}/project/{projectId}/service/{serviceId}/task/{taskId} (it's a get but receiving a query object)
  • POST /api/client/{clientId}/project/{projectId}/service/{serviceId}/task
  • PUT /api/client/{clientId}/project/{projectId}/service/{serviceId}/task
  • DELETE /api/client/{clientId}/project/{projectId}/service/{serviceId}/task/{taskId}
  • GET /api/department
  • GET /api/department/{departmentId}
  • POST /api/department/query (it's a get but receiving a query object)
  • POST /api/department/{departmentId}/query (it's a get but receiving a query object)
  • POST /api/department
  • PUT /api/department
  • DELETE /api/department

Security

All the services require a sessionToken in order to work. Supido provides two base classes to build your security system:

  • BaseSessionManager: controls all the sessions of the users, its able to create new sessions and to recover session data from the sessionToken. Example:
    C#
    public class SessionManager : BaseSessionManager
    {
        public SessionManager()
            : base(typeof(Session), typeof(SessionDto))
        {
        }
    
        protected override object GetSessionByToken(OpenAccessContext context, string sessionToken)
        {
            return context.GetAll<Session>().Where(p => p.SessionToken == sessionToken).FirstOrDefault();
        }
    
        protected override IList GetAllSessionsOfUser(OpenAccessContext context, long userId)
        {
            return context.GetAll<Session>().Where(p => p.UserId == userId).ToList();
        }
        protected override object NewSession(long userId, string sessionToken)
        {
            Session session = new Session();
            session.SessionToken = sessionToken;
            session.UserId = Convert.ToInt32(userId);
            session.CreationDttm = DateTime.UtcNow;
            session.UpdateDttm = DateTime.UtcNow;
            return session;
        }
    
        protected override void UpdateSessionAccess(object session)
        {
            (session as Session).UpdateDttm = DateTime.UtcNow;
        }
    }
    
  • BaseSecurityManager
    C#
    public class SecurityManager : BaseSecurityManager
    {
        public SecurityManager(string fileName)
            : base(typeof(EntitiesModel), typeof(UserDto), typeof(User), fileName)
        {
        }
    
        protected override object GetUserByLoginPass
        (OpenAccessContext context, string login, string password)
        {
            return context.GetAll<User>().Where
            (p => p.Email == login && p.Password == password).FirstOrDefault();
        }
    
        protected override object GetUserById(OpenAccessContext context, long userId)
        {
            return context.GetAll<User>().Where(p => p.UserId == userId).FirstOrDefault();
        }
    
        #endregion
    }
    

Queryable from the frontend

Supido creates two special operations for the POST verb: one for the GetAll and one for the GetOne, both with the url finished with /query. This is made in order to recibe a Query Information serialized object from the frontend, that Supido will convert to the lambda expression for the query to the database.

This Query Information is based on facets:

JavaScript
{
    "Facets": [
        {
            "Name": "Position",
            "Values": [
                {
                    "Operation": "GreaterThan",
                    "Value": "10"
                }
            ]
        },
        {
            "Name": "Priority",
            "Values": [
                {
                    "Operation": "Equal",
                    "Value": "1"
                },
                {
                    "Operation": "Equal",
                    "Value": "3"
                }
            ]
        }
    ],
    "Orders": [
        {
            "Name": "Detail",
            "IsAscending": true
        }
    ],
    "SkipRecords": 100,
    "TakeRecords": 50
}

The example query will traduce the facets to: WHERE (Position > 10) AND (Priority = 1 OR Priority = 3). Also will add the order and bring only 50 records skipping 100 records (pagination).

Change the Behavior

Sometimes, we want to change the behavior of our business objects. In this case, we have two options:

  • Use filters, based on the BaseContextBOFilter abstract class. We can specify that a class is a Filter with the [Filter] attribute. This attribute will resolve automatically the DTO from the name, or we can pass as parameter the DTO type. The filters allow us to interact with the business objects in two ways:
    • Adding security to the queries, with the method ApplySecurity. We receive the information about the user that is executing the operation, so we can modify the query in order to contextualize it with the security information of the user.
    • Modifying the way of using the QueryInfo with the method ApplyFilter. By default, the method ApplyFilter is able to resolve the where, order by and pagination information. We can change this behaviour to add our own way to query the information.
  • Inherit from ContextBO<TDto> and build our own BO, changing the behaviour of the operations that we want overriding them, or subscribing to the events that happen before and after each operation and that allow us to intercept and modify the information. When we write our own BO, we must use the Attribute [BO], that will resolve the DTO type by the name of the BO, or we can specify the DTO type as parameter to the attribute.

How It Works?

When the service start, the application is scanned searching for the [DTO], [Filter] and [BO] attributes, and build the information inside a Metamodel. An ISessionManager and an ISecurityManager are created. After that, the API defined in the XML are created: for each one if a BO or Filter is defined by the user, then uses the user ones, otherwise it creates generic Filter and BO based on the default behaviour. Then, a RestService class is created for the high level nodes of the API, defining the base operations for all the URI templates. When an operation is called, internally it resolves the path and the query parameters from the Message, and search internally the correct BO to use, passing the URL path parameters as a build query.

So, when we call:

GET /api/client/{clientId}/project/{projectId}/service/{serviceId}/task

GET /api/client/7/project/2/service/3/task

Internally, a query is created with the parameters:

  • Service.ServiceId = 3
  • Service.Project.ProjectId = 2
  • Service.Project.Client.ClientId = 7

And knows that the call is a GetAll because there is no last parameter, and that the DTO is TaskDto, as defined in the XML, so the correct BO and Filter are resolved for the TaskDto.

Using the Code

There is a demo application showing how to use the code. The code works using Telerik Data Access for the data layer. You can open the project and tell telerik to generate the database (right-click over the model, update database from model), or use the database script provided.

This article was originally posted at https://github.com/jseijas/supido

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Architect
Spain Spain
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --