承接前文「簡單/複雜型別」與「陣列」繫結模擬,本文實作 IEnumerable<T> 集合與 IDictionary<TKey, TValue> 字典型別的繫結邏輯。
核心步驟:
IEnumerable<T>,取得元素型別 T。GetIndexes),支援數字索引(list[0])與字串鍵(list[key])。IEnumerable(如表單多同名欄位)。BindModel(elementType, prefix + "[" + index + "]")。break)。List<object>,最後呼叫 ReplaceHelper.ReplaceCollection 填入目標集合實例。private object BindCollectionModel(string prefix, object model, Type enumerableType)
{
var list = new List<object>();
bool numericIndex;
var indexes = GetIndexes(prefix, out numericIndex);
Type elementType = enumerableType.GetGenericArguments()[0];
// 1. 直接轉換同前綴值(多同名欄位)
if (!String.IsNullOrEmpty(prefix) && ValueProvider.ContainsPrefix(prefix))
{
var enumerable = ValueProvider.GetValue(prefix).ConvertTo(enumerableType) as IEnumerable;
if (enumerable != null) foreach (var v in enumerable) list.Add(v);
}
// 2. 索引式繫結
foreach (var index in indexes)
{
string indexPrefix = prefix + "[" + index + "]";
if (!ValueProvider.ContainsPrefix(indexPrefix) && numericIndex) break;
list.Add(BindModel(elementType, indexPrefix));
}
if (list.Count == 0) return null;
ReplaceHelper.ReplaceCollection(elementType, model, list);
return model;
}
透過反射呼叫泛型方法 ReplaceCollectionImpl<T>:
internal static class ReplaceHelper
{
private static MethodInfo replaceCollectionMethod = typeof(ReplaceHelper).GetMethod("ReplaceCollectionImpl", BindingFlags.Static | BindingFlags.NonPublic);
public static void ReplaceCollection(Type collectionType, object collection, object newContents)
{
replaceCollectionMethod.MakeGenericMethod(collectionType).Invoke(null, new[] { collection, newContents });
}
private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents)
{
collection.Clear();
if (newContents != null)
foreach (var obj in newContents)
collection.Add(obj is T ? (T)obj : default(T));
}
}
邏輯類似集合,差異在於:
IDictionary<TKey, TValue> 兩個泛型參數。prefix[Key]。IDictionary 實例。private object BindDictionaryModel(string prefix, object model, Type dictionaryType)
{
var args = dictionaryType.GetGenericArguments();
Type keyType = args[0], valueType = args[1];
var dict = (IDictionary)model;
bool numericIndex;
var indexes = GetIndexes(prefix, out numericIndex); // 這裡 indexes 實為字典的 Keys
foreach (var key in indexes)
{
string keyPrefix = prefix + "[" + key + "]";
if (!ValueProvider.ContainsPrefix(keyPrefix) && numericIndex) break;
object keyObj = ConvertTo(key, keyType); // 字串轉 TKey
object valObj = BindModel(valueType, keyPrefix);
dict[keyObj] = valObj;
}
return model;
}
從 ValueProvider 解析所有 prefix[xxx] 形式的鍵,提取 xxx 部分,判斷是否為純數字索引。
表單提交:
<input name="Tags[0]" value="C#" />
<input name="Tags[1]" value="MVC" />
<input name="Scores[Math]" value="90" />
<input name="Scores[English]" value="85" />
Action:
public ActionResult Save(List<string> Tags, Dictionary<string, int> Scores) { ... }
模擬繫結器正確產生 Tags = ["C#", "MVC"]、Scores = { {"Math", 90}, {"English", 85} }。
集合繫結支援「同名多值」與「索引式」兩種來源,字典繫結擴展為 Key-Value 配對。ReplaceHelper 以反射泛型方法實現型別安全填充。掌握此機制可自訂繫結器處理特殊集合/字典結構。