使用jQuery.ajax傳送物件陣列給ASP.NET MVC

在ASP.NET MVC裡,我們可以用物件集合當成Action的傳入參數,例如以下範例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace MvcMobileLab.Controllers
{
    public class AjaxPostController : Controller
    {
        public ActionResult Index(string viewName)
        {
            if (string.IsNullOrEmpty(viewName))
                return View();
            else
                return View(viewName);
        }
        /// <summary>
        /// 測試用資料類別
        /// </summary>
        public class Player
        {
            public string Id { get; set; }
            public string Name { get; set; }
        }
        ///測試用Action,前端接入List<Player>,轉JSON後傳回
        public ActionResult Update(List<Player> players)
        {
            return Json(players);
        }
    }
}

在以上的Controller裡,定義了一個極簡單的Player資料類別,只有Id跟Name兩個屬性。假設有一個Update動作,可接收List<Player>作為輸入參數,為了示範,並不真的把players資料更新到資料庫,而是轉為JSON格式後回傳,讓前端驗證資料是否已正確傳達。

在HTML Form裡要怎麼把List<Player>當成參數傳給Action呢? ASP.NET MVC內建的Model Binder已具備將特定規則參數對應成ICollection<T>的能力,方法可參考以下兩篇文章:

簡單來說,ASP.NET MVC的Model Binder會從POST的欄位中用players[0].Id, players[0].Name去找players陣列第一個物件的各屬性,用players[1].*去對應第二個物件... 以此類推。因此,如果我們想用jQuery.ajax()呼叫Update(List<Player> players) Action,只要用{ "players[0].Id": "P01", "players[0].Name": "Jeffrey", "players[1].Id": "P02" ... }當成參數傳送,ASP.NET MVC就能正確解析與接收,例如以下範例:

@{ Layout = null; }
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Test</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.js"></script>
    <script>
        $(function () {
            var data = {
                "players[0].Id": "P01",
                "players[0].Name": "Jeffrey",
                "players[1].Id": "P02",
                "players[1].Name": "Darkthread"
            };
            $.post("@Url.Content("~/AjaxPost/Update")", data, function(res) {
                alert(JSON.stringify(res));
            }, "json");
        });
    </script>
</head>
<body>
</body>
</html>

執行成功,前端可正確接回預期的List<Player>內容,驗證了用這種格式組合參數,ASP.NET MVC就能將我們傳送的內容解析成List<Player>!

不過,在大量使用Javascript的網頁中,通常會用陣列來保存Player物件,例如: var players = [ { Id:"P01", Name:"Jeffrey" }, { Id:"P02", Name: "Darkthread" } ],不太可能寫成<input type="text" name="players[0].Id" />的形式。因此,較直覺得的寫法應是$.post(the_action_url, { players: players_array }, function(r) { … }, "json");,這樣能成功嗎?

@{ Layout = null; }
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Test</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.js"></script>
    <script>
        $(function () {
            var array = [];
            array.push({ Id: "P01", Name: "Jeffrey" });
            array.push({ Id: "P02", Name: "Darkthread" });
            $.post(
                "@Url.Content("~/AjaxPost/Update")", 
                {  players: array }, 
                function(res) {
                    alert(JSON.stringify(res));
                }, 
                "json"
            );
        });
    </script>
</head>
<body>
</body>
</html>

結果很有趣,ASP.NET MVC判讀出它是個陣列,但是Player的屬性值沒抓到,都是null。

實際觀察$.post()傳送的內容,可看出一些端倪...

$.ajax()遇到傳送參數中有物件陣列時,也會拆解成palyers[0],players[1]的形式,但對於屬性值,並不是ASP.NET MVC所預期的players[0].Id,而是寫成players[0][Id]。追進jQuery原始碼,發現這是function buildParams( prefix, obj, traditional, add )被賦與的編碼邏輯,且沒有預留自訂規則的空間。由於players[0].Id與players[0][Id]只有格式上的些微差異,因此我想到一個做法是在jQuery.ajax()的beforeSend事件做手腳,將序列化完成字串中的players[0][Id]用Regular Expression置換成players[0].Id,於是順手寫了一個Plugin, $.postMvc(),攔截beforeSend事件,對buildParams()序列化後的字串進行陣列元素屬性名稱的置換,再傳送出去。要應用時,用$.post它來取代$.post()即可:

@{ Layout = null; }
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Test</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.js"></script>
    <script>
        (function ($) {
            $.extend($, {
                //Replace item[0][propName] to item[0].propName for MVC model binder
                postMvc: function (url, data, succ, dataType) {
                    return $.ajax({
                        url: url,
                        type: "POST", dataType: dataType,
                        data: data,
                        success: succ,
                        beforeSend: function (xhr, settings) {
                            settings.data = 
settings.data.replace(/%5D%5B(.+?)%5D=/g, "%5D.$1=");
                        }
                    });
                }
            });
        })(jQuery);
 
        $(function () {
            var array = [];
            array.push({ Id: "P01", Name: "Jeffrey" });
            array.push({ Id: "P02", Name: "Darkthread" });
            $.postMvc(
                "@Url.Content("~/AjaxPost/Update")", 
                {  players: array }, 
                function(res) {
                    alert(JSON.stringify(res));
                }, 
                "json"
            );
        });
    </script>
</head>
<body>
</body>
</html>

這樣子,就能很輕鬆地將Javascript物件陣列抛給ASP.NET MVC當List<T>參數囉~

歡迎推文分享:
Published 23 June 2012 08:19 AM 由 Jeffrey
Filed under: ,
Views: 58,942



意見

# David.Net said on 27 June, 2012 10:17 AM

黑大 好

這問題我之前也有遇到過,

我目前是用以下的方法解決,給大師參考看看,thanks ~ ^_^

www.dotblogs.com.tw/.../aspnetmvctippostjson.aspx

# 小黑 said on 21 October, 2012 03:42 AM

黑大您好,

想要請教個問題,關於 jquery ajax 是否可以直接指定呼叫外網 (ex: http://google.com.tw) 其他非站內資源(url),

因為剛好想使用 ajax 取得資源,一直無法直接取得站外資料(web api) ,所以想來請教一下

# Jeffrey said on 21 October, 2012 06:57 AM

to 小黑,基於Single Origin Policy(SOP)原則,瀏覽器原則上禁止XHR、JavaScript去取存來自其他網域的資源,以免有心人藉機搞出邪惡勾當。

要呼叫其他網域的JavaScript Library,目前常用解法是透過JSONP,但必須Web API本身支援。HTML5的CORS也能解決跨網域問題,但一樣必須Server端配合。這些都是基於安全所加諸的限制,Client很難突破。(若能簡單避開,駭客就笑開懷了)。

如果你有自己的Web Server,有種簡單做法是寫一個Proxy,Client把要呼叫WebAPI的請求交由該Proxy從Server端丟出Request到WebAPI,再將WebAPI傳回結果轉給Client。但要留意Proxy可能被惡意人士當作跳板(送出有害或邪惡的Request,由WebAPI角度是你的Server送出的,百口莫辯),不可不慎。

# 小黑 said on 08 October, 2014 01:27 AM

謝謝黑哥大心,

另外想請教,若欲送回後端的資料包含有物件陣列與一般字串陣

列,在後端MVC寫法 (List<Player> players,List<string> mylist) ,mylist 都無法接到值,不知有無方法可解 ?

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<June 2012>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
 
RSS
創用 CC 授權條款
【廣告】
twMVC

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication