Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Developing an Extensible Web API

0.00/5 (No votes)
2 Jul 2019 1  
Developing a very simple Web API architecture using ASP.NET Web API2 whose operations can be extended by just adding new operation types

  

Introduction

I have recently needed to prepare a Web API which had several small operations, and new ones would be added continuously. Instead of adding many controllers with a few actions in them, I decided to use an extensible architecture. So, new operations could be added only by adding new operation types.

Architecture

The architecture and the project are very simple. The project is created using Web API 2 project template of Visual Studio 2017. There is only one API controller named OperationsController with a single action named Process. There is a simple IOperation interface that an operation must implement and a base class named OperationBase for boilerplate code.

Autofac and Autofac WebAPI2 integration packages are used for automatically discovering operation types and injecting them into the controller.

Code

IOperation interface is as follows:

public interface IOperation
{
    string Name { get; }
    string Description { get; }
    Type ParameterClassType { get; }
    object Execute(object prm);
}

ParameterClassType is the type of the parameter object that Execute method needs. It is used when parsing the JSON data in request body. If Execute method returns a non-null value, it is returned as the result to the caller, otherwise just Ok response is returned.

OperationController and its Process action are as follows:

public class OperationsController : ApiController
{

   //Implemented operations are injected in the constructor
   public OperationsController(IEnumerable<IOperation> supportedOperations)
   {
      _supportedOperations = new List<IOperation>(supportedOperations);
   }

   //Single action that gets the operation name and 
   //reads the operation parameters as JSON from request body

   [HttpPost]
   public async Task<IHttpActionResult> Process(string operationName)
   {
      //Find the operation
      IOperation operation = _supportedOperations.FirstOrDefault(x => x.Name == operationName);
      if (operation == null)
           return BadRequest($"'{operationName}' is not supported.");

      //Get the request body as string
      string jsonBody = await Request.Content.ReadAsStringAsync();
      object operationParams = null;  

      try
      {
          //Parse the JSON data in the request body t construct operation's parameter object
          if (operation.ParameterClassType != null)
                operationParams = Newtonsoft.Json.JsonConvert.DeserializeObject
                                  (jsonBody, operation.ParameterClassType);

          object result = operation.Execute(operationParams);

          //Return the result value as JSON to the caller
          if (result != null)
              return Json(result);
      }
      catch(Exception ex)
      {
           return InternalServerError(ex);
      }

      //Return Ok if the operation has no return value
      return Ok();          
   }    
}

Routing configuration that only needs operationName value is as follows:

config.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{operationName}",
      defaults: new { controller = "operations", action = "process", operationName = "help" }
);

This routing configuration routes all the requests to OperationsControlller's Process action. If there is no operation name provided, help is executed.

Autofac dependency injection is configured and set as the dependency resolver for the Web API pipeline:

ContainerBuilder builder = new ContainerBuilder();

//Register controllers
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

//Register all operations
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
           .Where(t=> t.IsAssignableTo<IOperation>())
           .As<IOperation>();

var container = builder.Build();

//Set Autofac container as the dependency resolver
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

Testing with Postman

Testing parameterless operation "help".

Response returned from "help" operation.

Testing "sum" operation which expects an object with properties "Number1" and "Number2".

Response returned from "sum" operation.

Next Steps

To demonstrate only the idea, the code left is very simple. It is good to separate command processing operations from controller class.

Help command only displays the name and description of the operations but it is good to provide whether a particular operation needs parameters and their types with names. Because operations are not controller actions, Swagger will not help for parameter information.

There is also no automatic validation. Validation can be done before the Execute method of the operation, or every operation may perform its own validation logic.

Authentication and authorization operations should also be added for securing the Web API.

History

  • 3rd July, 2019: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here