Best Practice in Creating Web API Services
This is the second part of Web API Series. In the first part, we’ve learnt how to create a RESTful API with Web API. In this tutorial, we gonna learn how we can enhance the code and provides more flexibility and simplicity to the code.
Overview
The following code snippet is the implementation of our Web API service:
[RoutePrefix("api/employee")] public class EmployeeController : ApiController { [Route("")] [HttpPost] public HttpResponseMessage Create(Employee employee) { try { var bc = new EmployeeBusiness(); var result = bc.Create(employee); return Request.CreateResponse(HttpStatusCode.Created, result); } catch (Exception ex) { return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message); } } [Route("{id}")] [HttpGet] public HttpResponseMessage Get(int id) { try { var bc = new EmployeeBusiness(); var result = bc.Get(id); return Request.CreateResponse(HttpStatusCode.OK, result); } catch (Exception ex) { return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message); } } [Route("")] [HttpGet] public HttpResponseMessage List() { try { var bc = new EmployeeBusiness(); var result = bc.List(); return Request.CreateResponse(HttpStatusCode.OK, result); } catch (Exception ex) { return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message); } } [Route("")] [HttpPut] public HttpResponseMessage Update(Employee employee) { try { var bc = new EmployeeBusiness(); bc.Update(employee); return Request.CreateResponse(HttpStatusCode.Accepted); } catch (Exception ex) { return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message); } } [Route("{id}")] [HttpDelete] public HttpResponseMessage Remove(int id) { try { var bc = new EmployeeBusiness(); bc.Remove(id); return Request.CreateResponse(HttpStatusCode.Accepted); } catch (Exception ex) { return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message); } } }
As you can see there all the methods are repeating the same thing. They all call a business method and return the result along with the status code. They also check for any exception and wrap the exception in the response object. The repeated task can be delegated to a base class which accepts an action, performs it and returns the result.
Download the EmployeeManagement project if you don’t have it already.
Create a Service Base Class
Let’s create the base class. In the EmployeeManagement.Service, right click on the Controller folder, Add, and then select Class. Name it ServiceBase.cs.
This class will be all services’ base class so it needs to inherit from ApiContoller.
public class ServiceBase : ApiController
We have two types of actions: those which return a result (like Get(), List() or etc. methods), and those which just perform an action (like Update() or Remove() methods). For this purpose, we need to create two methods in our base class. One to get a Func<> as parameter to service those methods returning a result and another one to get an Action as parameter which calls void methods.
Below is the implementation of these two methods:
protected internal HttpResponseMessage ServiceAction<TResult>(Func<TResult> action, HttpStatusCode successCode = HttpStatusCode.OK) { try { var result = action(); return Request.CreateResponse(successCode, result); } catch (Exception ex) { return Request.CreateResponse(HttpStatusCode.InternalServerError, ex); } } protected internal HttpResponseMessage ServiceAction(Action action, HttpStatusCode successCode = HttpStatusCode.OK) { try { action(); return Request.CreateResponse(successCode); } catch (Exception ex) { return Request.CreateResponse(HttpStatusCode.InternalServerError, ex); } }
The first method take Func<TResult> as parameter. In the method’s body, we call the action and hold the result in a variable. We then wrap the result in response object with the optional statusCode value (set to OK by default). The second method gets an Action rather than the Func<>. It does the same thing, except, the action yields no results. So we are just calling the action and return the status code as a response. Both methods wrap the execution in try-catch block and returns InternalServerError along with the exception.
Modify the Web API Methods
Next, we need to modify our Web API methods to use the base class. Remove the inheritance from ApiController and instead inherit from ServiceBase class.
[RoutePrefix("api/employee")]
public class EmployeeController : ServiceBase
Modify the service operations as below:
[RoutePrefix("api/employee")] public class EmployeeController : ServiceBase { EmployeeBusiness _bc; public EmployeeController() { _bc = new EmployeeBusiness(); } [Route("")] [HttpPost] public HttpResponseMessage Create(Employee employee) => ServiceAction(() => { return _bc.Create(employee); }); [Route("{id}")] [HttpGet] public HttpResponseMessage Get(int id) => ServiceAction(() => { return _bc.Get(id); }); [Route("")] [HttpGet] public HttpResponseMessage List() => ServiceAction(() => { return _bc.List(); }); [Route("")] [HttpPut] public HttpResponseMessage Update(Employee employee) => ServiceAction(() => { _bc.Update(employee); }, HttpStatusCode.Accepted); [Route("{id}")] [HttpDelete] public HttpResponseMessage Remove(int id) => ServiceAction(() => { _bc.Remove(id); }, HttpStatusCode.Accepted); }
You can see how our service implementation became much simpler, in fact, just one line.
We only pass the second parameter, i.e. status code, when the status code we want to return is something else than OK (200). For example, HttpStatusCode.Accepted (202) for Update and Delete operations, and HttpStatusCode.Created (201) for Create.