【问题标题】:How do I deserialize from JSON to an abstract parameter on an ASP.NET MVC 5 controller如何从 JSON 反序列化为 ASP.NET MVC 5 控制器上的抽象参数
【发布时间】:2013-12-31 16:06:12
【问题描述】:

我有一个抽象对象 BaseObject(示例名称),它有 2 个特定于国家/地区的实现,UsaObject 和 CanadaObject,它们都扩展了 BaseObject。

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

在我的控制器中,我有一个动作

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

动作只调用 BaseObject 上的方法,不需要强制转换。

我使用 Json.NET 执行序列化,当我序列化对象时,我使用 JsonSerializationSettings.TypeNameHandling.Objects 将我的类的类型添加到对象中。这使我可以轻松地将数据正确地发送到 UI 并根据国家/地区更改显示的内容,而无需担心确切的时间,直到我需要。

但是,我在将这些数据发回 UpdateObject 方法时遇到了问题。它在反序列化时失败并出现以下错误(请注意,我们正在使用 Glimpse 对应用程序执行性能分析,尽管我根本不怀疑这是原因),这在 IExceptionFilter.OnException 调用中被捕获:

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)

有没有办法修改 MVC 5 处理反序列化的方式,如果有,我应该在页面生命周期的哪个时间点完成这项工作?

【问题讨论】:

  • 能否展示您发送的 JSON 示例?
  • 基本 JSON 结构看起来像 {"$type":"Namespace.UsaObject, AssemblyName","UsaProperty1":"Value","UsaProperty2":"Value2"} 但是,我已经想通了解决方案,我稍后会发布。

标签: c# json.net deserialization asp.net-mvc-5


【解决方案1】:

原来我做了一个错误的假设,即 MVC 使用 Json.NET 进行反序列化(我感到羞耻)。 MVC 有自己的 ModelBinder,默认为DefaultModelBinder。请耐心等待解决方案,它有点匆忙,需要测试。我能够扩展 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);
    }
}

并在启动时将其注册为我的默认活页夹。

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

我只在模型类型是抽象的时候改变行为,否则默认。如果它是抽象的,但我无法创建该类型的实例,或者类型名称不可用等,那么现在我正在抛出异常,但这是我以后可以弄清楚的细节。这样,我就不需要在我的 Actions 参数上使用任何属性,并且目前我可以尽可能地使用 Json.NET 类型说明符。让 Json.NET 为我反序列化模型会很好,但这是迄今为止我找到的最佳解决方案。

我想参考帮助我找到答案的帖子。 Change the default model binder in asp.net MVC, ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

【讨论】:

    猜你喜欢
    • 2011-02-26
    • 1970-01-01
    • 2016-12-18
    • 2016-08-31
    • 2016-01-31
    • 1970-01-01
    • 2014-05-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多