技术开发 频道

ASP .NET MVC实战:jQuery提交对象

        【IT168 技术】在某些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;
    }
}

      

  如预想中的一样,我们得到了下面的结果:


  OK,大功告成。

  得于某种目的,上面的代码中有两处需要说明一下:

ArrayValueProviderResult  类中的
                
if (elementType == typeof(object))
                {
                    
Array.Copy(_Elements, array, l);
                }

 

  当数组类型为object[]时,复制把原始的ValueProviderResult过去了,这里看个人需要可自己修改。

  PS:只是很肤浅的实现了这种直接使用jQuery来提交对象给asp.net mvc的支持,代码未做优化,未作任何合理性的设计。

  如果有需要的猴子,可以参考自己实现一个。

0
相关文章