3

So I scoured Stack Overflow as much as I possibly could and couldn't find an answer to this specific issue. Apologies if this has already been asked.

I've found answers to:

  • how to pass an object/class to an action
  • how to pass an object via the query string to an action
  • how to pass an object via json to an action
  • how to pass a polymorphic object to an action and have a custom model binder process it

Suppose you have the following code, how can you combine the above techniques into one solution. So I'd like to hit the action on the controller (using the jquery ajax call) with a json object, pass in a view model to the action, and have it determine the the correct polymorphic type (in this case, type of Notification) - possibly by using a custom model binder.

Note: this is example code used to illustrate the issue.

Models:

public class Notification
{
    public int ID { get; set; }
    public string ApplicationID { get; set; }
    public string Description { get; set; }
    public System.DateTime DateStamp { get; set; }
}

public class WarningNotification : Notification
{
    public string WarningText { get; set; }
}

public class AlertNotification : Notification
{
    public string AlertText { get; set; }
    public int AlertId { get; set; }
}

View Model:

public class SaveNotificationViewModel
{
    public Notification Notification { get; set; }
    public string Hash { get; set; }
    public List<int> Users { get; set; }
}

Controller action:

public ActionResult Save(SaveNotificationViewModel model)
{
    //code inside method irrelevant...
}

Model Binder:

public class NoticationModelBinder : 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(Notification).IsAssignableFrom(type))
        {
            throw new InvalidCastException("Bad type");
        }

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

Source of initial question that sent me down this rabbit hole and code largely borrowed from: Methodology for passing a mix of: list<>, object and primitives to an ASP MVC controller action

Community
  • 1
  • 1
longda
  • 9,445
  • 6
  • 44
  • 66

2 Answers2

4

So I'd like to hit the action on the controller (using the jquery ajax call) with a json object, pass in a view model to the action, and have it determine the the correct polymorphic type (in this case, type of Notification) - possibly by using a custom model binder.

I'd recommend NOT using a single action. First, although easy to implement, custom model binding combined with an action that takes a base type or interface typically becomes harder to debug, harder to unit test, and anyone who looks at the controller action can't figure out what is happening only by looking at the action itself.

I'd highly recommend creating action that are specific to the request being made.

I dummied these down to relevant code for the example.

public ProcessNotify(Notification model)
{
  this.SharedNotifyCode(model);

  // specific Notification only code
}

public ProcessWarningNotification(WarningNotification model)
{
  this.SharedNotifyCode(model);

  // specific WarningNotification only code
}

public ProcessAlertNotification(AlertNotification model)
{
  this.SharedNotifyCode(model);

  // specific AlertNotification only code
}

private SharedNotifyCode(Notification notification)
{
  // shared code
}

This is easy to maintain, debug, test and the code itself is Self-Documenting.

The same thing can be done with the javascript (jQuery) code:

function ProcessNotify()
{
  notify = {
    ID = 1,
    ApplicationID = 2,
    Description = "description",
    DateStamp = new Date() // something like this.. forgot JS datetimes
    };

  SendNotify(notify, "ProcessNotify");
}

function ProcessWarning()
{
  notify = {
    ID = 1,
    ApplicationID = 2,
    Description = "description",
    DateStamp = new Date(), // something like this.. forgot JS datetimes
    WarningText = "Oh noes!"
    };

  SendNotify(notify, "ProcessWarningNotification");
}

function ProcessAlert()
{
  notify = {
    ID = 1,
    ApplicationID = 2,
    Description = "description",
    DateStamp = new Date(), // something like this.. forgot JS datetimes
    AlertText = "Oh noes!",
    AlertId = 3
    };

  SendNotify(notify, "ProcessAlertNotification");
}

function SendNotify(notify, action)
{
  var jqXHR = $.ajax({
    url: '/' + Controller + '/' + action,
    data: notify,
    type: "POST",
    success: function(result)
    {
    }
    // etc
  });
}
Erik Philips
  • 48,663
  • 7
  • 112
  • 142
  • 1
    Was this posted twice? Might consider deleting the second answer. – Mike Beeler Jan 05 '14 at 06:40
  • @Erik Philips - Thanks for taking the time to post this. Although a valid alternative, this isn't quite an answer to the question I asked. I'll take this approach into consideration, however. – longda Jan 06 '14 at 18:09
0

So here's what I came up with... I included code for both a WarningNotification and an AlertNotification.

The key for me that I had overlooked were:

  1. to stick "ModelType" at the root level in the JSON and not underneath the notification prefix (as in "Notification.ModelType" - this is wrong... don't do it that way).
  2. all fields go under the root type (Notification in this case) so fields for specialized types are still prefixed with "Notification." - see "Notification.AlertId"

Javascript code to tie it all together:

// this is an example warning notification
var data =
    {
        "Notification.Id": '21',
        "Notification.ApplicationId": 'TARDIS',
        "Notification.Description": "The most important warning notification ever!",
        "Notification.WarningText": "I gave them the wrong warning. I should have told them to run, as fast as they can. Run and hide, because the monsters are coming - the human race.",
        "ModelType": 'Models.WarningNotification',
        "Hash": '27ad5218-963a-4df8-8c90-ee67c5ba9f30'
    };

// this is an example alert notification
var data =
    {
        "Notification.Id": '21',
        "Notification.ApplicationId": 'TARDIS',
        "Notification.Description": "The most important alert notification ever!",
        "Notification.AlertText": "Rose,before I go, I just want to tell you, you were fantastic. Absolutely fantastic. And you know what....so was I.",
        "Notification.AlertId": "1024",
        "ModelType": 'Models.AlertNotification',
        "Hash": '27ad5218-963a-4df8-8c90-ee67c5ba9f32'
    };

$.ajax({
    type: 'POST',
    url: '/notification/save',
    dataType: 'json',
    data: data,
    success: function (result) {
        console.log('SUCCESS:');
        console.log(result);
    },
    error: function (xhr, ajaxOptions, thrownError) {
        console.log('ERROR:');
        console.log(thrownError);
    }
});
longda
  • 9,445
  • 6
  • 44
  • 66
  • Assist for this answer goes to @Darin Dimitrov! – longda Jan 06 '14 at 19:55
  • References: http://stackoverflow.com/questions/7222533/polymorphic-model-binding and http://stackoverflow.com/questions/12116972/methodology-for-passing-a-mix-of-list-object-and-primitives-to-an-asp-mvc-co – longda Jan 06 '14 at 19:56