0

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

2
  • From what I know the JSON is mapped to the class in action method but there are many way for the action to receive Dynamic type too but ,imo I will just overload the POST method to receive other classes then call to one private method that doing the operation. Commented Jun 15, 2022 at 4:25
  • @user3682728 I thought of making separate controllers for each model but I don't feel this way is the best way so I wanted some generic solution. Commented Jun 15, 2022 at 5:38

2 Answers 2

1

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": {}}

Sign up to request clarification or add additional context in comments.

3 Comments

thanks for you fast reply. but i dont get the last part The important thing is your post data now must be changed from: unitNotification to {"unitNotification": {}}
@quang when i send a Json like that {"Title": "Renting", "Body": "Someone wants to rent a unit", "Token":"fn9DlgYFTL2ueRVGXxS-BT:APA91bGVZtas4VwmxICgeyuwRbU-0qet8WTyXg3ct_cVB__OOQ1se9P3W70fy6dYfEAXu4lItN9Za9MzcC_s2A6AAqYMlkyAXsD36gQX2X93h9ciXHhSlt0hJHAYibEDrdzH5n6a2RNX", "Type": "Renting", "Renter_Key":"[email protected]", "Owner_Key": "[email protected]", "Building_Key": "13st.2212345678", "Unit_Key": "3333334200" } it does not instantiate any combinedNotification Object so all of them are null
Your post data should like this, { "UnitNotification": { "Title": "Renting", "Body": "Someone wants to rent a unit", "Token": "token", "Type": "Renting", "Renter_Key": "[email protected]", "Owner_Key": "[email protected]", "Building_Key": "13st.2212345678", "Unit_Key": "3333334200" }, "MaintenanceNotification": { "Title": "Renting", "Body": "", "Token": "token", "Type": "Renting", "RequestData": "2022-06-15 12:00:00" } } Replace UnitNotification or MaintenanceNotification by null if you don't have it.
0

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:

enter image description here

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.

1 Comment

seriously copied here the original doc?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.