Dynamic API Extension in ASP.Net Core
Introduction
This article describes about extending API in asp.net core
dynamically at runtime. This means we do not have to recompile the code again
if there are any new API is introduced. We can develop the extension project in
a separate solution and deploy only the dll file to the server.
Some use cases of this project are,
1. Create a dynamic API at runtime and add the API to existing server.
2. When a third-party team want to extend the API for their own use.
3. A scenario in where we do not have to deploy the extended services as a separate microservices.
Some advantages of using this architecture,
1. Modularize the project, so that it is easy to maintain different parts of the project.
2. No need to restart the server for deployment.
3. Restrict the extension API instead of giving full control to the third-party team.
Architecture
This architecture uses the application part and assembly
part from the asp.net core to extend the controllers. An application part is
an abstraction over the resources of an app. Application Parts allow ASP.NET
Core to discover controllers, view components, tag helpers, Razor Pages, razor
compilation sources, and more. Assembly part is an application part. AssemblyPart encapsulates
an assembly reference and exposes types and compilation references.
Image 1 Plugin architecture diagram
From the above architecture diagram the Plugin Framework in
the interface between Base API and PluginOne project. This way the Base API
identifies the plugin and registers within. The Plugin are developed and deployed
independently regardless of the Base API. It then deployed to the Base API root
folder. The Base API now discovers all the APIs that is within the added plugin
and publish those API along with the Base API. Users and clients now can invoke
the API from PluginOne
Plugin interface
The IRegisterService interface is the base framework where
all the extended library should be implanted. The IRegisterService acts as a plugin
interface and bridges between our main application and plugin.
namespace PluginFramework
{
public interface IRegisterService
{
string ServiceName { get; set; }
}
}
Listing 1 IRegisterService interface
The IRegisterService interface is used as type to search for
plugin by main application. Listing 1 shows the interface where it has only
ServiceName as member variable. The service name is the name of the plugin and
used the main application to identify the plugin.
The below Listing 2 shows how the plugin got registered
itself by using the IRegisterService interface. The “PluginOne” name is used by
our main application to identify the plugin.
namespace PluginOne
{
public class RegisterService : IRegisterService
{
public string ServiceName
{
get => "PluginOne";
}
}
}
Listing 2 Concrete implementation of
IRegisterService interface by PluginOne
Registering plugin
The below piece of code extracted from ‘PluginStatusController.cs’
where we post the REST api to register the plugin with main API project. The
API takes the PluginStatus and the model contains the plugin name. By
default the plugin dll copied to the ‘Plugin’ folder within the root directory.
The LoadFromAssemblyPath load the dll module and attach to the running
process. Then the file is added to the application part which enables the process
to identify the controller built within the plugin.
[HttpPost]
[SwaggerOperation(Summary = "Upload a plugin to activate", Description = "Upload a plugin to activate")]
[SwaggerResponse(200, "All is OK")]
public PluginStatus Post([FromBody] PluginStatus plugin)
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugin");
if (Directory.Exists(path))
{
string assemblyPath = Path.Combine(path, plugin.Name + ".dll");
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
if (assembly != null)
{
_partManager.ApplicationParts.Add(new AssemblyPart(assembly));
// Notify change
ActionDescriptorChangeProvider.Instance.HasChanged = true;
ActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
}
}
return new PluginStatus
{
Name = "Plugin1",
Version = "1"
};
}
Listing 3 Registering the PluginOne with
BaseAPI
Extension controller
The extension controller implements the simple API
controller with the operations defined. In our example we had implemented OperationOne
API. The sample code is shown below,
namespace PluginOne.Controller
{
[ApiController]
[Route("[controller]")]
public class OperationOne : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet]
[SwaggerOperation(Summary = "OperationOne Get API", Description = "This method is from CustomerOne module")]
[SwaggerResponse(200, "All is OK")]
public IEnumerable<ModelOne> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new ModelOne
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
[SwaggerOperation(Summary = "OperationOne Post API", Description = "This method is from CustomerOne module")]
[SwaggerResponse(200, "All is OK")]
[HttpPost]
public ModelOne Post(ModelOne modelOne)
{
return new ModelOne { };
}
}
}
Listing 4: Sample PluginOne extension
controller
Running the extension service
To test the extension plugin, run the Base API service. The below screen shot shows the swagger documentation for Base API before the PluginOne integrated. As you can see the swagger doc contains only the API we defined in Base API.
Image 2: Swagger doc after PluginOne integrated
To see the extension API, copy the PluginOne.dll file from
the PluginOne project to BaseAPI root folder\Plugin folder. The call the POST method
of api/PluginStatus and pass the following json object in the body,
{
"name": "PluginOne.dll",
"version": "v1",
"activate": true
}
Listing 5 Json body object to be
passed in api/pluginstagus
This call enable the PluginOne attached to the BaseAPI
process. Refresh your page again and you will now see the OperationOne
APi listed in the swagger doc. The below after registration screen shot shows
the same.
Image 2: Swagger doc after PluginOne
integrated
Sample Code
The sample code for the above discussed project can be downloaded
from this github
repository.
Conclusion
Although in microservices architecture, an extension API can be installed in separate micro service. The above article is used in a particular case where API need an extension but still need to continue to be deployed as single monolithic application. This architecture is also helpful when security is considered where request and response is controlled.



Comments
Post a Comment