Introduction
In some WebAPI projects, I was asked to optimize the number of links that the API exposes. It means that we want to simplify the configuration on the client side.
To do this, polymorphism can be a good approach. It means that while designing the models, we have to think on having an abstraction for the common attributes of the data, then extend them according to the need. The controller will know what to do according to the type of payload provided by the client.
ASP.Net core gives the possibility to do a such thing. In this article, we will see how to send different structure of payload using the same controller in the WebAPI
What is Polymorphism?
Polymorphism is a term that comes from the ancient greek language: Polus (several) and Morphe (Form) and in Object Oriented Programming it means the ability of an object to have several forms.
This can be done by defining a base class and inheriting it from daughter classes. Then the base class can be instantiated in several ways according to the daughter classes. Our Data Model shows a real example of polymorphism.
Data Model
In our case, we will suppose that we have a base class called Device which is derived from two other classes Phone and Laptop.
Here we want to have only one controller that allows to Post a device independently from its type. The controller will know how to deserialize the object according to the attribute DeviceType.
Then in the Get method we will return a list of devices, here again, the controller will be able to deserialize and return the right format of JSON.
Prerequisite
- Visual Studio 2022
- Net Core 6.0
Implementation
Step 1 - Creation of the WebApi Project
You need to create Asp.Net Core Web API in Visual Studio
Step 2 - Nuget Packages
Ensure that you have the following nuget packages referenced in your solution
Step 3 - Definition of the Classes
Now you should define the model into classes, the first one to define is the base class Device
namespace PolymorphismInWebApi.Models.Abstract
{
public abstract class Device
{
public string SerialNumber { get; set; }
public string Brand { get; set; }
public string ScreenSize { get; set; }
public string RAM { get; set; }
public string Storage { get; set; }
public double Price { get; set; }
}
}
And then the two derived classes Phone and Laptop
Laptop
using PolymorphismInWebApi.Models.Abstract;
namespace PolymorphismInWebApi.Models
{
public class Laptop : Device
{
public int NumborOfUsbPort { get; set; }
public bool HasHdmiPort { get; set; }
public bool HasDvdReader { get; set; }
}
}
Phone
using PolymorphismInWebApi.Models.Abstract;
namespace PolymorphismInWebApi.Models
{
public class Phone : Device
{
public bool IsSmartPhone { get; set; }
public int NumberOfSimCard { get; set; }
public List<string> Sensors { get; set; }
}
}
You can notice here that in our model we defined an attribute DeviceType, but we didn't define it in any of our classes. This attribute is called the discriminator.
It's the one that will allow the controller to make the difference between the different forms of the object when it's received in the payload. We will see how to use the discriminator in the next section.
But at this level, we need to define this discriminator as an enumeration for example
namespace PolymorphismInWebApi.Models.Enumeration
{
public enum DeviceTypeEnum
{
Phone,
Laptop
}
}
Step 4 - Initialization of the WebApi
Now you've created your classes, you need to tell your WebApi that it will use the relation between them to receive request from the clients.
This is done during the initialization in Program.cs. It can be done by the following code (check the comments for details)
using PolymorphismInWebApi.Utils;
using JsonSubTypes;
using Microsoft.OpenApi.Models;
using PolymorphismInWebApi.Models.Abstract;
using PolymorphismInWebApi.Models;
using PolymorphismInWebApi.Models.Enumeration;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddNewtonsoftJson(options =>
{
//Register the subtypes of the Device (Phone and Laptop)
//and define the device Discriminator
options.SerializerSettings.Converters.Add(
JsonSubtypesConverterBuilder
.Of(typeof(Device),CommonData.DeviceDiscriminator)
.RegisterSubtype(typeof(Phone), DeviceTypeEnum.Phone)
.RegisterSubtype(typeof(Laptop), DeviceTypeEnum.Laptop)
.SerializeDiscriminatorProperty()
.Build()
);
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
//Add the features of Polymorphism to the swagger
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1",
new OpenApiInfo
{
Title = "PolymorphismInWebApi",
Version = "v1" }
);
c.UseAllOfToExtendReferenceSchemas();
c.UseAllOfForInheritance();
c.UseOneOfForPolymorphism();
c.SelectDiscriminatorNameUsing(type =>
{
return type.Name switch
{
nameof(Device) => CommonData.DeviceDiscriminator,
_ => null
};
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 5 - Implementation of the Controller
Now you can define your controller with only the base class Device as parameter in the payload.
You can pass the specific attributes of the derived classes Laptop or the Phone in the calls.
In the implementation of the Post Method, you can test the derived type of the passed parameter and check how to cast it and add it to the list.
I've used a static List to able to test the exchanged data
using PolymorphismInWebApi.Models;
using Microsoft.AspNetCore.Mvc;
using PolymorphismInWebApi.Models.Abstract;
namespace PolymorphismInWebApi.Controllers {
[ApiController]
[Route("[controller]")]
public class DeviceController: ControllerBase {
private readonly ILogger < DeviceController > _logger;
public DeviceController(ILogger < DeviceController > logger) {
_logger = logger;
}
private static List < Device > _devices = new List < Device > ();
[HttpGet()]
public List < Device > GetDevices() {
return _devices;
}
[HttpPost]
public void Post(Device device) {
if (device is Phone) {
_devices.Add((Phone) device);
}
if (device is Laptop) {
_devices.Add((Laptop) device);
}
}
}
}
Step 6 - Testing the WebApi using the Swagger
Now you can test the API using the swagger. But before starting to send the requests, let's see what's interesting about the specification of the current Implementation.
If you check the JSON specification of the swagger
You will notice that the polymorphism and the relationship between the objects is defined using the keyword OneOf (already called in Program.cs)
Now you can send two different structure of Payload using the same URL
Phone
{
"DeviceType": "Phone",
"serialNumber": "Ph123456789",
"brand": "Samsung",
"screenSize": "8",
"ram": "3Gb",
"storage": "64Gb",
"price": 500,
"IsSmartPhone": true,
"NumberOfSimCard": 2,
"Sensors": ["Gyroscope","Gps"]
}
Laptop
{
"DeviceType": "Laptop",
"serialNumber": "Lp123456789",
"brand": "Dell",
"screenSize": "17",
"ram": "16Gb",
"storage": "1To",
"price": 2000,
"numborOfUsbPort": 3,
"hasHdmiPort": true,
"hasDvdReader": false
}
You can add the previous Payloads and then check that you are able to receive them while getting the list
Conclusion
Finally, we can conclude that polymorphism can be a very interesting concept to make the web API more generic and the controllers more flexible in term of payloads.
But when it's implemented on the server side, it should be well communicated and specified to the client side. If the developer on the client side will use a proxy generator, for example, he needs to do a couple of things that we can see in a coming article.