How do I deserialize from JSON to an abstract parameter on an ASP.NET MVC 5 controller

zshift

I have an abstract object, BaseObject (example name), that has 2 country-specific implementations, UsaObject, and CanadaObject, both of which extend BaseObject.

public abstract class BaseObject...
public class UsaObject : BaseObject...
public class CanadaObject : BaseObject...

In my controller, I have an action

public void UpdateObject(BaseObject object) {
   ...
}

The action only calls methods on BaseObject, and doesn't need to cast.

I'm using Json.NET to perform serialization, and when I serialize objects, I am using JsonSerializationSettings.TypeNameHandling.Objects to add the type of my class to the objects. This allows me to easily send the data correctly in to the UI and change what is shown based on the country, without having to worry about the exact time until I need to.

However, I am having issues posting this data back in the UpdateObject method. It fails on deserialization with the following error (note that we are using Glimpse to perform performance analysis on the application, though I don't suspect this as the cause at all), which is caught on an IExceptionFilter.OnException call:

System.MissingMethodException: Cannot create an abstract class.
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)
   at System.Web.Mvc.DefaultModelBinder.CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
   at System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
   at System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
   at Castle.Proxies.DefaultModelBinderProxy.BindModel_callback(ControllerContext controllerContext, ModelBindingContext bindingContext)
   at Castle.Proxies.Invocations.DefaultModelBinder_BindModel.InvokeMethodOnTarget()
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Glimpse.Core.Extensibility.CastleInvocationToAlternateMethodContextAdapter.Proceed()
   at Glimpse.Core.Extensions.AlternateMethodContextExtensions.TryProceedWithTimer(IAlternateMethodContext context, TimerResult& timerResult)
   at Glimpse.Core.Extensibility.AlternateMethod.NewImplementation(IAlternateMethodContext context)
   at Glimpse.Core.Extensibility.AlternateTypeToCastleInterceptorAdapter.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.DefaultModelBinderProxy.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
   at System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
   at System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass1e.<BeginInvokeAction>b__16(AsyncCallback asyncCallback, Object asyncState)

Is there a way to modify how MVC 5 is handling deserialization, and if so, at which point in the page lifecycle should I accomplish this?

zshift

Turns out I made a bad assumption that MVC uses Json.NET for deserialization (shame on me). MVC has its own ModelBinder, which is DefaultModelBinder by default. Bear with me on the solution, it was a little rushed and needs testing. I was able to extend the DefaultModelBinder,

public class AbstractModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        if (modelType.IsAbstract)
        {
            var typeName = bindingContext.ValueProvider.GetValue("$type");
            if (typeName == null)
                throw new Exception("Cannot create abstract model");

            var type = Type.GetType(typeName);
            if (type == null)
                throw new Exception("Cannot create abstract model");

            if (!type.IsSubclassOf(modelType))
                throw new Exception("Incorrect model type specified");

            var model = Activator.CreateInstance(type);

            // this line is very important.
            // It updates the metadata (and type) of the model on the binding context,
            // which allows MVC to properly reflect over the properties
            // and assign their values. Without this,
            // it will keep the type as specified in the parameter list,
            // and only the properties of the base class will be populated
            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);

            return model;
        }

        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

and registered it as my default binder on startup.

// in Global.asax.cs on application start
ModelBinders.Binders.DefaultBinder = new AbstractModelBinder();

I only change the behavior when the model type is abstract, and default otherwise. If it's abstract, but I can't create an instance of the type, or the type name is unavailable, etc., then for now I'm throwing an exception, but that's a detail that I can figure out later. This way, I don't need to use any attributes on my Actions' parameters, and the Json.NET type specifiers are being used as best as I can figure out at the moment. It would be nice to just have Json.NET deserialize the model for me, but this is the best solution I've found thus far.

I'd like to reference posts that helped me find my answer. Change the default model binder in asp.net MVC, ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How do I pass DateTime parameters from View to Controller in ASP.net MVC5?

From Dev

How do I return a JSON object from controller in ASP.NET MVC 4?

From Dev

Failing to deserialize json into abstract class in ASP.Net MVC

From Dev

How do I pass data from an ASP.NET WebAPI ApiController to an ASP.NET MVC Controller?

From Dev

How do I pass data from an ASP.NET WebAPI ApiController to an ASP.NET MVC Controller?

From Dev

ASP.NET MVC How to pass JSON object from View to Controller as Parameter

From Dev

How to post JSON data from ASP.Net MVC controller?

From Java

How can I return camelCase JSON serialized by JSON.NET from ASP.NET MVC controller methods?

From Dev

How do I fill a Javascript var with Json data from a Controller in Asp.net Core

From Dev

How do I create the database from the code in ASP.NET MVC 5?

From Dev

How can I abstract this repeating pattern in ASP.NET MVC 5?

From Dev

How do I get the “Add Controller” and “Add View” menu options in my ASP.NET MVC 5 project?

From Dev

Return JSON from any ASP.NET MVC controller action based on a parameter

From Dev

How do I set up an ASP.NET MVC route in which the controller name is derived from multiple segments of the URL?

From Dev

How do I call a method that returns a Task<T> From an ASP.Net MVC4 Controller Method?

From Dev

How to convert milliseconds to DateTime in ASP.NET MVC controller parameter

From Dev

Inheritance from Controller in Asp.NET MVC 5

From Dev

How to return XML from an ASP.NET 5 MVC 6 controller action

From Dev

How to pass a List from a View to a Controller ASP.NET MVC5

From Dev

How to get access token in Asp.net MVC5 Controller action from OwinContext

From Dev

How to generate a view from a controller in VS 2015 ASP.NET 5 MVC6

From Dev

How to get data from a html5 web form on a controller asp.net mvc?

From Dev

How do I use a Custom JSON.NET Converter in RavenDB to deserialize into types from a dynamic DLL?

From Dev

How can I pass multiple Json objects to ASP.net MVC controller?

From Dev

How do I add Ninject Controller Factory to an ASP.NET MVC app with OWIN?

From Dev

How do I resolve collection error with my controller in ASP.NET MVC in C#?

From Dev

How do I add a controller to ASP.NET MVC with Visual Studio 2013 and Entity Framework?

From Dev

How do I add a partial view with its own controller to another view in asp.net mvc 4

From Dev

Asp.Net MVC 5 bind parameter exclusively from body

Related Related

  1. 1

    How do I pass DateTime parameters from View to Controller in ASP.net MVC5?

  2. 2

    How do I return a JSON object from controller in ASP.NET MVC 4?

  3. 3

    Failing to deserialize json into abstract class in ASP.Net MVC

  4. 4

    How do I pass data from an ASP.NET WebAPI ApiController to an ASP.NET MVC Controller?

  5. 5

    How do I pass data from an ASP.NET WebAPI ApiController to an ASP.NET MVC Controller?

  6. 6

    ASP.NET MVC How to pass JSON object from View to Controller as Parameter

  7. 7

    How to post JSON data from ASP.Net MVC controller?

  8. 8

    How can I return camelCase JSON serialized by JSON.NET from ASP.NET MVC controller methods?

  9. 9

    How do I fill a Javascript var with Json data from a Controller in Asp.net Core

  10. 10

    How do I create the database from the code in ASP.NET MVC 5?

  11. 11

    How can I abstract this repeating pattern in ASP.NET MVC 5?

  12. 12

    How do I get the “Add Controller” and “Add View” menu options in my ASP.NET MVC 5 project?

  13. 13

    Return JSON from any ASP.NET MVC controller action based on a parameter

  14. 14

    How do I set up an ASP.NET MVC route in which the controller name is derived from multiple segments of the URL?

  15. 15

    How do I call a method that returns a Task<T> From an ASP.Net MVC4 Controller Method?

  16. 16

    How to convert milliseconds to DateTime in ASP.NET MVC controller parameter

  17. 17

    Inheritance from Controller in Asp.NET MVC 5

  18. 18

    How to return XML from an ASP.NET 5 MVC 6 controller action

  19. 19

    How to pass a List from a View to a Controller ASP.NET MVC5

  20. 20

    How to get access token in Asp.net MVC5 Controller action from OwinContext

  21. 21

    How to generate a view from a controller in VS 2015 ASP.NET 5 MVC6

  22. 22

    How to get data from a html5 web form on a controller asp.net mvc?

  23. 23

    How do I use a Custom JSON.NET Converter in RavenDB to deserialize into types from a dynamic DLL?

  24. 24

    How can I pass multiple Json objects to ASP.net MVC controller?

  25. 25

    How do I add Ninject Controller Factory to an ASP.NET MVC app with OWIN?

  26. 26

    How do I resolve collection error with my controller in ASP.NET MVC in C#?

  27. 27

    How do I add a controller to ASP.NET MVC with Visual Studio 2013 and Entity Framework?

  28. 28

    How do I add a partial view with its own controller to another view in asp.net mvc 4

  29. 29

    Asp.Net MVC 5 bind parameter exclusively from body

HotTag

Archive