简体   繁体   中英

trouble sending interface implementation as json from client

I have one interface and two classes that implements it.

namespace FirebaseNet.Messaging
{
    public interface INotification
    {
        string Title { get; set; }
    }

    public class AndroidNotification : INotification
    {
        public string Title { get; set; }
    }

    public class IOSNotification : INotification
    {
        public string Title { get; set; }
    }
}

Now I have another class like this.

public class Message
{
    public INotification Notification { get; set; }
}

The Message parameter is passed to the class like this

[HttpPost]
public async Task<JsonResult> SendMessage(Message message)

This parameter can be either

var message = new Message()
{
    Notification = new AndroidNotification()
    {
        Title = "Portugal vs. Denmark"
    }
};

or

var message = new Message()
{
    Notification = new IOSNotification()
    {
        Title = "Portugal vs. Denmark"
    }
};

So far, all works. Now I want to AJAX POST to SendMessage . I tried using this DTO.

JavaScript Code

var message = {
    Notification : {
        Title : "Portugal vs. Denmark"
    }
};

This obviously failed with

Cannot create an instance of an interface.

What would be an ideal workaround for it?

PS: Thought of changing the Message class to

public class Message
{
    public INotification Notification { get; set; }
    public AndroidNotification AndroidNotification { get; set; }
    public IOSNotification IOSNotification { get; set; }
}

It's a third party DLL and I don't wanna touch it ideally.

One way to achieve that would be to write a custom model binder:

public class NotificationModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)),
            true
        );

        if (!typeof(INotification).IsAssignableFrom(type))
        {
            throw new InvalidOperationException("Bad Type");
        }

        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}

that you would register when bootstrapping your application:

ModelBinders.Binders.Add(
    typeof(INotification), 
    new NotificationModelBinder()
);

this would now allow the client to specify the concrete type of the notification:

var message = {
    ModelType: "WebApplication1.Models.AndroidNotification",
    Notification : {
        Title : "Portugal vs. Denmark"
    }
};

or:

var message = {
    ModelType: "WebApplication1.Models.IOSNotification",
    Notification : {
        Title : "Portugal vs. Denmark"
    }
};

Of course you could tweak the model binder for the exact property name that indicates the concrete type and the possible values. In my example the fully qualified type name should be used but you could have some mapping with friendlier names.

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