讓asp.net mvc的Action支援jQuery直接提交的javascript對象

來源:互聯網
上載者:User

在某些ajax應用中,我們可能會用到如下的情境:

$.post('/Test/PostTest', { values: [1, 2, 3, 4] }, function(result){    //TODO:}, 'json' );

我們希望提交一個數組給伺服器。

於是我們建立了一個如下的Controller,來負責處理上面的ajax請求:

public class TestController : Controller{    [HttpPost]    public JsonResult PostTest( int[] values )    {        //TODO:        return Json( new { success = true });    }}

可是當我們充滿期待的去測試我們剛才的代碼時,卻發現了一個問題。

值並沒有被正確的傳過來。

於是我們開啟了瀏覽器的開發人員工具,來看看到底jQuery提交了什麼內容給我們的伺服器。

我們發現,表單名稱被設定成為了 values[],而不是values。莫非是是mvc不能將values[]看成一個數組並自動轉化嗎?

於是我們開啟ILSpy,找到了System.Web.Mvc.FormValueProviderFactory的原始碼,並將它複製出來,作了一些擴充,以支援我們想要的功能。

    public sealed class FormValueProviderFactoryEx         : ValueProviderFactory    {        private readonly UnvalidatedRequestValuesAccessor _unvalidatedValuesAccessor;        public FormValueProviderFactoryEx()            : this(null)        {        }        internal FormValueProviderFactoryEx(UnvalidatedRequestValuesAccessor unvalidatedValuesAccessor)        {            if (unvalidatedValuesAccessor == null)            {                unvalidatedValuesAccessor = ((ControllerContext cc) => new UnvalidatedRequestValuesWrapper(cc.HttpContext.Request.Unvalidated()));            }            this._unvalidatedValuesAccessor = unvalidatedValuesAccessor;        }        public override IValueProvider GetValueProvider(ControllerContext controllerContext)        {            if (controllerContext == null)            {                throw new ArgumentNullException("controllerContext");            }            return new FormValueProviderEx(controllerContext, this._unvalidatedValuesAccessor(controllerContext));        }    }

下面這幾個是原來的FormValueProviderFactory用到的,但在System.Web.Mvc.dll中被聲明為internal,所以不得已複製了出來。

    internal interface IUnvalidatedRequestValues{NameValueCollection Form { get; }NameValueCollection QueryString {get;}string this[string key]{ get; }}    internal delegate IUnvalidatedRequestValues UnvalidatedRequestValuesAccessor(ControllerContext controllerContext);    internal sealed class UnvalidatedRequestValuesWrapper : IUnvalidatedRequestValues    {        private readonly UnvalidatedRequestValues _unvalidatedValues;        public NameValueCollection Form        {            get            {                return this._unvalidatedValues.Form;            }        }        public NameValueCollection QueryString        {            get            {                return this._unvalidatedValues.QueryString;            }        }        public string this[string key]        {            get            {                return this._unvalidatedValues[key];            }        }        public UnvalidatedRequestValuesWrapper(UnvalidatedRequestValues unvalidatedValues)        {            this._unvalidatedValues = unvalidatedValues;        }    }

下面是用於支援FormValueProviderFactoryEx的另外幾個對象的定義

    public sealed class FormValueProviderEx : NameValueCollectionValueProvider    {        public FormValueProviderEx(ControllerContext controllerContext)            : this(controllerContext, new UnvalidatedRequestValuesWrapper(controllerContext.HttpContext.Request.Unvalidated()))        {        }        internal FormValueProviderEx(ControllerContext controllerContext, IUnvalidatedRequestValues unvalidatedValues)            : base(controllerContext.HttpContext.Request.Form, unvalidatedValues.Form, CultureInfo.CurrentCulture)        {        }        public override ValueProviderResult GetValue(string key, bool skipValidation)        {            var result = base.GetValue(key, skipValidation);            if (result == null)            {                var subKeys = base.GetKeysFromPrefix(key);                if (subKeys.Count > 0)                {                    var firstItem = subKeys.First();                    if (subKeys.Count == 1 && firstItem.Value == key + "[]")                    {                        return GetValue(firstItem.Value, skipValidation);                    }                    int n;                    if( int.TryParse(firstItem.Key, out n) )                    {                        var indexList = new List<int>(subKeys.Count);                        if (subKeys.Keys.All(v =>                        {                            if (int.TryParse(v, out n))                            {                                indexList.Add(n);                                return true;                            }                            return false;                        }))                        {                            var arraySize = indexList.Max() + 1;                            var elements = new ValueProviderResult[arraySize];                            foreach (var i in indexList)                            {                                elements[i] = GetValue(subKeys[i.ToString()]);                            }                            return new ArrayValueProviderResult(elements);                        }                    }                    var properties = new Dictionary<string, ValueProviderResult>(StringComparer.OrdinalIgnoreCase);                    foreach (var item in subKeys)                    {                        properties[item.Key] = GetValue(item.Value);                    }                    return new ObjectValueProviderResult(properties);                }            }            return result;        }    }    public class ArrayValueProviderResult        : ValueProviderResult    {        private ValueProviderResult[] _Elements;        public ArrayValueProviderResult(ValueProviderResult[] elements)        {            _Elements = elements;            base.RawValue = elements.Select( v => v.RawValue ).ToArray();            base.AttemptedValue = "[" + string.Join(", ", elements.Select(v => v.AttemptedValue)) + "]";        }        public override object ConvertTo(Type type, CultureInfo culture)        {            if (type.IsArray)            {                var elementType = type.GetElementType();                var array = Array.CreateInstance(elementType, _Elements.Length);                int l = _Elements.Length;                if (elementType == typeof(object))                {                    Array.Copy(_Elements, array, l);                }                else                {                    for (int i = 0; i < l; i++)                    {                        var v = _Elements[i];                        if (v != null)                        {                            try                            {                                array.SetValue(v.ConvertTo(elementType, culture), i);                            }                            catch                            {                            }                        }                    }                }                return array;            }            return null;        }    }    public class ObjectValueProviderResult        : ValueProviderResult    {        private IDictionary<string, ValueProviderResult> _Properties;        public ObjectValueProviderResult(IDictionary<string, ValueProviderResult> properties)        {            _Properties = properties;            base.RawValue = properties.ToDictionary(v => v.Key, v => v.Value.RawValue);            base.AttemptedValue = "{" + string.Join(", ", properties.Select(v => string.Format("{0}: {1}", v.Key, v.Value.AttemptedValue ))) + "}";        }        public override object ConvertTo(Type type, CultureInfo culture)        {            if (!type.IsPrimitive && !type.IsArray)            {                var constructor = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance).OrderBy(v => v.GetParameters().Length).FirstOrDefault();                if (constructor != null)                {                    var args = constructor.GetParameters()                        .Where(v => !v.IsOptional)                        .Join(_Properties.DefaultIfEmpty(), v => v.Name, v => v.Key, (l, r) => r.Value).ToArray();                    var obj = Activator.CreateInstance(type, args);                    foreach( var property in type.GetProperties( BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty ))                    {                        if (property.GetIndexParameters().Length > 0) continue;                        ValueProviderResult propertyValue;                        if (_Properties.TryGetValue(property.Name, out propertyValue) && propertyValue != null )                        {                            try                            {                                if (property.PropertyType == typeof(object))                                {                                    property.SetValue(obj, propertyValue.RawValue, null);                                }                                else                                {                                    property.SetValue(obj, propertyValue.ConvertTo(property.PropertyType, culture), null);                                }                            }                            catch                            {                            }                        }                    }                    return obj;                }            }            return null;        }    }

在做完上面的事情之後,我們就可以考慮把FormValueProviderFactory替換成為FormValueProviderFactoryEx了。

於是我們在Application_Start中,添加如下的代碼:

    for (int i = 0; i < ValueProviderFactories.Factories.Count; i++)    {        if (ValueProviderFactories.Factories[i] is FormValueProviderFactory)        {            ValueProviderFactories.Factories[i] = new FormValueProviderFactoryEx();            break;        }    }

現在我們再來測試之前的代碼:

很高興的看到,我們的值,已經正確的解析出來了!

我們再來測試傳遞一個對象:

    $.post('/Test/PostTest', { obj: { Id: 1, Values: ['aa', 'bb', 'cc']} }, function (result) {        //TODO:    }, 'json');

我們把Action的代碼也稍作修改:

        [HttpPost]        public JsonResult PostTest( MyObject obj )        {            //TODO:            return Json( new { success = true });        }

MyObject的定義如下:

    public class MyObject    {        public int Id { get; set; }        public string[] Values { get; set; }    }

如預想中的一樣,我們得到了下面的結果:

OK,大功告成。

得於某種目的,上面的代碼中有兩處需要說明一下:

ArrayValueProviderResult  類中的

                if (elementType == typeof(object))

                {

                    Array.Copy(_Elements, array, l);

                }

當數群組類型為object[]時,複製把原始的ValueProviderResult過去了,這裡看個人需要可自己修改。

PS:只是很膚淺的實現了這種直接使用jQuery來提交對象給asp.net mvc的支援,代碼未做最佳化,未作任何合理性的設計。

如果有需要的猴子,可以參考自己實現一個。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.