Allows asp.net mvc Action to support javascript objects directly submitted by jQuery.

Source: Internet
Author: User

In some ajax applications, we may use the following scenarios:

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

We want to submit an array to the server.

So we created the following Controller to process the preceding ajax request:

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

However, when we were looking forward to testing our code, we found a problem.

The value is not passed in correctly.

So we opened the browser's developer tool to see what jQuery submitted to our server.

We found that the form name is set to values [] instead of values. Does mvc regard values [] as an array and convert it automatically?

So we opened ILSpy, found the source code of System. Web. Mvc. FormValueProviderFactory, copied it, and made some extensions to support the functions we wanted.

    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));        }    }

The following are used by the original FormValueProviderFactory, but it is declared as internal in System. Web. Mvc. dll, so it has to be copied.

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

The following are definitions of several other objects that support 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;        }    }

After completing the above steps, we can consider replacing FormValueProviderFactory with FormValueProviderFactoryEx.

In Application_Start, add the following code:

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

Now let's test the previous Code:

We are glad to see that our values have been correctly parsed!

Let's test and pass an object:

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

We also slightly modified the Action Code:

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

The definition of MyObject is as follows:

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

As expected, we get the following results:

OK.

For some purpose, the above Code has two points to describe:

In the ArrayValueProviderResult class

If (elementType = typeof (object ))

{

Array. Copy (_ Elements, array, l );

}

When the array type is object [], copy and pass the original ValueProviderResult. You can modify the result as needed.

PS: It is just superficial to implement the support for submitting objects to asp.net mvc using jQuery directly. The code is not optimized and no rational design is made.

If you need a monkey, you can refer to your own implementation.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

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.