Developing an Extensible Web API






4.80/5 (4 votes)
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