[转载]让asp.net mvc的Action支持jQuery直接提交的javascript对象

[转载]让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(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(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 _Properties;

public ObjectValueProviderResult(IDictionaryproperties)
{
_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的支持,代码未做优化,未作任何合理性的设计。

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

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏