I have an ASP.NET Core Web API communicating with a flutter mobile app.
The feature I am adding is a notification service. The issue is I have more than one notification type.
Here is the code:
public class NotificationSuper
{
public string Title { get; set; }
public string Body { get; set; }
public string Token { get; set; }
public string Type { get; set; }
}
public class UnitNotification :NotificationSuper
{
public String Renter_Key { get; set; }
public String Owner_Key { get; set; }
public String Building_Key { get; set; }
public String Unit_Key { get; set; }
}
public class MaintenanceNotification : UnitNotification
{
public DateTime RequestData { get; set; }
}
and so on.
I wrote a controller for the notification using a super generic type in its params
[HttpPost]
public async Task<IActionResult> Post([FromBody] NotificationSuper notification)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
bool success = await Notify.Send(notification);
if (success)
{
return Ok();
}
return StatusCode(500);
}
The problem is when I retrieve the JSON data from the flutter app, I only get the properties of the NotificationSuper
class which are:
public String Renter_Key { get; set; }
public String Owner_Key { get; set; }
public String Building_Key { get; set; }
I want to have a flexible way to get every property if I passed UnitNotification
or MaintenanceNotification
. Should I have multiple controllers, one for each type of notification?
Thanks in advance
You can combine your UnitNotification
and MaintenanceNotification
It should look like this
public class CombinedNotification
{
public UnitNotification unitNotification { get; set; }
public MaintenanceNotification maintenanceNotification{ get; set; }
}
Then your controller code should look like this:
[HttpPost]
public async Task<IActionResult> Post([FromBody] CombinedNotification notification)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
bool success;
if (notification.UnitNotification != null)
{
bool success = await Notify.Send(notification.UnitNotification);
}
if (notification.MaintenanceNotification != null)
{
bool success = await Notify.Send(notification.MaintenanceNotification);
}
if (success)
{
return Ok();
}
return StatusCode(500);
}
The important thing is your post data now must be changed from: unitNotification
to {"unitNotification": {}}
Here is a more flexible method, I use Polymorphic model binding
, You can refer to this simple demo:
Model
public abstract class Device
{
public string Kind { get; set; }
public string Name { get; set; }
}
public class Laptop : Device
{
public string CPUIndex { get; set; }
public string Price { get; set; }
}
public class SmartPhone : Device
{
public string ScreenSize { get; set; }
}
Model Binding Code
public class DeviceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Laptop")
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (modelTypeValue == "SmartPhone")
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
Demo:
Notice: This demo needs data from form , If you need data from request body , You need to change some code in DeviceModelBinder
And parent class needs a property to specify the class name of the subclass.
Refer to doc .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.