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


在某些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 });
    }
}

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

1_thumb18

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

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

2_thumb19

我們發現,表單名稱被設置成為了 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;
        }
    }

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

3_thumb

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

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

    $.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; }
    }

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

4_thumb

OK,大功告成。

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

ArrayValueProviderResult  類中的

                if (elementType == typeof(object))

                {

                    Array.Copy(_Elements, array, l);

                }

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM