如何使用相同的 MVC 控制器方法处理数组或单个对象

本文关键字:数组 处理 单个 对象 方法 控制器 何使用 MVC | 更新日期: 2023-09-27 18:30:23

我有一个接受列表作为参数的ActionResult方法:

[HttpPost]
public ActionResult MyMethod (List<ClassA> json)
{
   ...
}

这会将进入的 json 字符串绑定到填充的 ClassA 对象的通用列表。

问题是有时传入的 json 只是一个 json 对象,而不是一个 json 对象数组。

有没有办法抢占这一点,以便我可以直接绑定到 A 类与列表? 或者我可以使用其他技术吗?

以下是 JSON 的发送方式(作为数组):

var myjsonarray = [{
  "ID": "1",
  "FirstName": "John",
  "LastName": "Doe",
}, {
  "ID": "2",
  "FirstName": "Jane",
  "LastName": "Doe",
}];
$.ajax({
 type: "POST",
 contentType: "application/json; charset=utf-8",
 url: "/Home/MyPage",
 data: JSON.stringify(myjsonarray),
 dataType: 'json'
});

以上过程很好。 这也适用于:

var myjsonarray = [{
  "ID": "1",
  "FirstName": "John",
  "LastName": "Doe",
}];

但是当我作为未包装在数组中的单个对象发送时:

var myjsonarray = {
  "ID": "1",
  "FirstName": "John",
  "LastName": "Doe",
};

我的操作结果方法参数为空:

json == null

如何使用相同的 MVC 控制器方法处理数组或单个对象

虽然从您的问题中不清楚,但从其他答案的评论中似乎您无法控制向您发送此 JSON 的客户端代码。 (如果你这样做了,最简单的解决方案是在发送之前将单个对象包装在一个数组中,正如其他人已经建议的那样。

如果 MVC 允许我们在控制器中添加方法重载来处理这种情况,那就太好了,例如:

[HttpPost]
public ActionResult MyMethod (List<ClassA> list)
{
   ...
}
[HttpPost]
public ActionResult MyMethod (ClassA single)
{
   ...
}

虽然这编译得很好,但不幸的是,当您在运行时命中该方法时,它会导致System.Reflection.AmbiguousMatchException

因此,看起来您需要创建自定义IModelBinder来解决问题。 虽然我以前没有实现过自定义模型绑定器,但我把它当作一个挑战,并提出了以下内容。 您显然必须根据自己的需要对其进行调整,但它似乎适用于您的示例。

这是代码:

public class CustomModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        IValueProvider provider = bindingContext.ValueProvider;
        // Check whether we have a list or a single object.  If we have a list
        // then all the properties will be prefixed with an index in square brackets.
        if (provider.ContainsPrefix("[0]"))
        {
            // We have a list.  Since that is what the controller method is 
            // expecting, just use the default model binder to do the work for us.
            return ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
        }
        else
        {
            // We have a single object.
            // Bind it manually and return it in a list.
            ClassA a = new ClassA();
            a.ID = GetValue<int>(provider, "ID");
            a.FirstName = GetValue<string>(provider, "FirstName");
            a.LastName = GetValue<string>(provider, "LastName");
            return new List<ClassA> { a };
        }
    }
    private T GetValue<T>(IValueProvider provider, string key)
    {
        ValueProviderResult result = provider.GetValue(key);
        return (result != null ? (T)result.ConvertTo(typeof(T)) : default(T));
    }
}

若要将此自定义绑定程序插入 MVC 管道,请将此行添加到Global.asax.cs中的 Application_Start() 方法。

ModelBinders.Binders.Add(typeof(List<ClassA>), new CustomModelBinder());

现在,显然这不是一个通用的解决方案;它是专门为处理ClassA而量身定制的,没有别的。 我必须相信有可能为任何类型的列表制定一个通用解决方案来处理这种"单一或列表"情况,但这要复杂得多,超出了本答案的范围。 为此,您可能还需要创建自定义IModelBinderProvider。 如果你想把它提升到那个水平,你需要自己调查。 下面是一篇 MSDN 文章,可能会让您更深入地了解绑定在后台的工作方式。

你最好保持你的API不变,并将这个逻辑添加到你的JavaScript代码中,以始终传递与列表相同的类型。它将易于维护和测试。

if(typeof(myjsonarray) === 'object'){
    myjsonarray = [myjsonarray];
}

因为你接受集合,所以你需要通过 js 中的数组发送集合:

您甚至可以为一个元素创建数组

您可以创建另一个操作方法来接受元素而不是数组