简体   繁体   中英

ASP.NET Core Web API controller with generic params

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM