IDictionary<,> 跟php的关联数组,或者 javascript 的 object,有着相似的作用。字典类型也算是很常用的基本数据结构,奇怪的是 .NET 虽然有这个数据结构,但真正用的时候会很现 MS 对它的支持实在是有限。Entity Framework 不支持映射,MVC 绑定也不完善!爹不疼娘不爱的赶脚啊。
假定我有一个 SortedDictionary<int, decimal> 类型的 MyDictionary 属性(这里只讨论简单的值类型,复杂的类型用 IEnumerable 去映射更合适)
如果我记得没错 php 绑定关联数据应该是这样就可以了:
1
2
3
4
5
6
7
|
<form action=“” method=“post”>
<input name=“MyDictionary[5]” value=“400” />
<input name=“MyDictionary[10]” value=“700” />
<input name=“MyDictionary[20]” value=“1300” />
<input name=“MyDictionary[40]” value=“2500” />
<button>提交</button>
</form>
|
简洁明了。但是在 MVC 5抱歉这个无法正确绑定,这段优雅的代码只能绑定到 IDictionary<string, string> 的类型上!想要绑定上我们想要泛类型你只能用下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<form action=“” method=“post”>
<input name=“MyDictionary.Index” value=“5” />
<input name=“MyDictionary[5].Key” value=“5” />
<input name=“MyDictionary[5].Value” value=“400” />
<input name=“MyDictionary.Index” value=“10” />
<input name=“MyDictionary[10].Key” value=“10” />
<input name=“MyDictionary[10].Value” value=“700” />
<input name=“MyDictionary.Index” value=“20” />
<input name=“MyDictionary[20].Key” value=“20” />
<input name=“MyDictionary[20].Value” value=“1300” />
<input name=“MyDictionary[40].Index” value=“40” />
<input name=“MyDictionary[40].Key” value=“40” />
<input name=“MyDictionary[40].Value” value=“2500” />
<button>提交</button>
</form>
|
又臭又长,代码足足多了3倍,看到眼睛都花掉!string 可以绑定但其它值类型不行真是相当“光腚肿菊”的设定。所以我们需要稍微修正下 DefaultModelBinder 的行为,让它回到正确轨道上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public class YourBinder : DefaultModelBinder
{
// 截停绑定事件
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelType = bindingContext.ModelType;
var dictionaryType = modelType.GetInterface("IDictionary`2");
if (modelType.IsGenericType && dictionaryType != null)
{
// 特别对 IDictionary<,> 类型进行处理
// 一般来讲客户端会提交所有的数据,最合适的猜测是重新创建一个新的实例替换掉原来,如果想要追加的行为去掉注释内的内容
var model = /*bindingContext.Model ?? */this.CreateModel(controllerContext, bindingContext, modelType);
var addMethod = dictionaryType.GetMethod("Add");
var argTypes = dictionaryType.GetGenericArguments();
var keyType = argTypes[0];
var valueType = argTypes[1];
IEnumerableValueProvider enumerableValueProvider = bindingContext.ValueProvider as IEnumerableValueProvider;
if (enumerableValueProvider != null)
{
IDictionary<string, string> keysFromPrefix = enumerableValueProvider.GetKeysFromPrefix(bindingContext.ModelName);
foreach (KeyValuePair<string, string> current in keysFromPrefix)
{
try
{ // 转换失败的条目直接跳过,适用多数场景
var key = Convert.ChangeType(current.Key, keyType);
var value = Convert.ChangeType(bindingContext.ValueProvider.GetValue(current.Value).AttemptedValue, valueType);
addMethod.Invoke(model, new[] { key, value });
}
catch(Exception ex)
{
}
}
}
return model;
}
else // 非 IDictionary<,> 返还给 mvc 处理
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
|
最后别忘了在 Global.axax 中 Application_Start
将这个新的Binder设为默认:
1
|
ModelBinders.Binders.DefaultBinder = new EmmolaBinder();
|