【问题标题】:Can abstract class be a parameter in a controller's action?抽象类可以作为控制器动作中的参数吗?
【发布时间】:2011-05-02 19:01:02
【问题描述】:

我在控制器内部有一个动作函数,它是用 AJAX 调用的。该动作采用 1 个参数。客户端,我构造了一个 JSON 对象,该对象应该序列化为该 1 参数。我遇到的问题是参数类被声明为抽象的。因此,它不能被实例化。

当 AJAX 命中该操作时,我得到以下信息:

无法创建抽象类。

堆栈跟踪:

[MissingMethodException:无法创建 一个抽象类。]
System.RuntimeTypeHandle.CreateInstance(RuntimeType 类型,布尔型 publicOnly,布尔型 noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal&ctor, 布尔值& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(布尔 publicOnly,布尔型 skipCheckThis, 布尔填充缓存)+98
System.RuntimeType.CreateInstanceDefaultCtor(布尔 publicOnly,布尔值 skipVisibilityChecks,布尔值 skipCheckThis,布尔填充缓存)+241 System.Activator.CreateInstance(类型 类型,布尔非公共)+69 ......

有没有办法在不创建不同的参数对象、将参数对象“取消声明”为抽象对象或深入研究 MVC 机制的情况下实现这样的场景?

更新

我目前正在与后端开发人员一起调整他们的对象。无论哪种方式,我认为这将是最终的解决方案。谢谢大家的回答。

【问题讨论】:

  • 我认为参数必须是具体类型。不允许抽象类型或接口类型。

标签: c# ajax serialization asp.net-mvc-3


【解决方案1】:

更新:示例现在使用 AJAX JSON POST

如果您必须使用抽象类型,您可以提供custom model binder 来创建具体实例。示例如下:

模型/模型绑定器

public abstract class Student
{
    public abstract int Age { get; set; }
    public abstract string Name { get; set; }
}
public class GoodStudent : Student
{
    public override int Age { get; set; }
    public override string Name { get; set; }
}
public class BadStudent : Student
{
    public override int Age { get; set; }
    public override string Name { get; set; }
}
public class StudentBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var values = (ValueProviderCollection) bindingContext.ValueProvider;
        var age = (int) values.GetValue("Age").ConvertTo(typeof (int));
        var name = (string) values.GetValue("Name").ConvertTo(typeof(string));
        return age > 10 ? (Student) new GoodStudent { Age = age, Name = name } : new BadStudent { Age = age, Name = name };
    }
}

控制器操作

public ActionResult Index()
{
    return View(new GoodStudent { Age = 13, Name = "John Smith" });
}
[HttpPost]
public ActionResult Index(Student student)
{
    return View(student);
}

查看

@model AbstractTest.Models.Student

@using (Html.BeginForm())
{
    <div id="StudentEditor">
        <p>Age @Html.TextBoxFor(m => m.Age)</p>
        <p>Name @Html.TextBoxFor(m => m.Name)</p>
        <p><input type="button" value="Save" id="Save" /></p>
    </div>
}

<script type="text/javascript">
    $('document').ready(function () {
        $('input#Save').click(function () {
            $.ajax({
                url: '@Ajax.JavaScriptStringEncode(Url.Action("Index"))',
                type: 'POST',
                data: GetStudentJsonData($('div#StudentEditor')),
                contentType: 'application/json; charset=utf-8',
                success: function (data, status, jqxhr) { window.location.href = '@Url.Action("Index")'; }
            });
        });
    });

    var GetStudentJsonData = function ($container) {
             return JSON.stringify({
                 'Age': $container.find('input#Age').attr('value'),
                 'Name': $container.find('input#Name').attr('value')
             });
         };
</script>

添加到 Global.asax.cs

protected void Application_Start()
{
    ...
    ModelBinders.Binders.Add(new KeyValuePair<Type, IModelBinder>(typeof(Student), new StudentBinder()));
}

【讨论】:

  • 这太棒了,帮我搞定了。
  • 感谢您在 Application_Start() 中建议 ModelBinders.Binders.Add。
【解决方案2】:

框架无法知道您想要哪种具体实现,也不会承担此类决定的责任。所以你有两种可能:

  1. 使用具体类型作为动作参数
  2. 为这个抽象类编写一个自定义模型绑定器,它会根据一些请求参数返回一个特定的实例。

【讨论】:

    【解决方案3】:

    您必须创建抽象类的子类并传递它。抽象类基本上不允许自己实例化。但是,如果您有一个 C# 方法,例如:

    protected void Foo(MyAbstractClass param1)
    

    ...那么您仍然可以将派生自 MyAbstractClass 的类型的实例传递给 Foo。因此,您可以创建一个具体的子类 MyChildClass : MyAbstractClass 并将其传递给您的方法,它应该仍然可以工作。您不必更改 Foo 方法,但您需要访问 C# 代码,以便创建 MyChildClass

    如果您正在使用泛型 - 例如,如果您的方法签名是:

    protected void Foo(IEnumerable<MyAbstractClass> param1)
    

    ...然后它变得更加复杂,您将需要查看 C# 泛型中的 covariance and contravariance

    【讨论】:

    • 对我的想法,所以我或多或少地写了一个重复的答案,但是 +1 :)
    • 我实际上有一个 List,是的,我确实可以访问 C#,但只能在我的 MVC 项目内部。我无法触摸业务/DAL 层。
    • @Dimskiy - 在这种情况下,您仍然可以传入一个填充有ThatDerivedClass 对象的List&lt;ThatAbstractClass&gt;。如果该方法允许协方差,您也许可以传入List&lt;ThatDerivedClass&gt;,但我不确定。如果您可以编写一个 C# 包装器来捕获 JSON 数据并在将其发送到您的业务/DAL 层之前将其重新打包,那就太好了。
    • 谁反对这个,请解释原因。我已经阅读了一遍又一遍,我看不出它为什么不正确。
    【解决方案4】:

    如果您可以访问控制器,是否可以添加另一个继承抽象类而不指定任何成员的类,并使用它来序列化反序列化,然后将其基返回到另一层?

    我知道这不是一个好习惯,但是有些技巧,但是我不知道抽象类是否可以以某种方式序列化。

    【讨论】:

      【解决方案5】:

      不——尝试将 JSON 反序列化为抽象类的对象是没有意义的。你不能让它成为一个合适的类吗?

      【讨论】:

      • 这些对象来自应用程序的另一层,它们几乎是给我的。我对他们没有任何权力。所以,我想我只是制作自己的对象,然后用我自己的数据填充抽象对象:(
      • @Dimskiy - 你总是可以从抽象对象继承你的类。
      猜你喜欢
      • 2012-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-04
      • 1970-01-01
      相关资源
      最近更新 更多