.NET 6中ConcurrentDictionary属性模型绑定返回异常值问题
Razor Pages中ConcurrentDictionary模型绑定失败的问题解决
问题原因
.NET内置模型绑定系统对ConcurrentDictionary这类特殊集合的适配性不足:
- 当直接给
ConcurrentDictionary标记[BindProperty]时,绑定逻辑会错误地遍历PageModel的公共属性,将这些属性的键值对填充到字典中,而非解析表单提交的目标数据。 - 模型绑定默认更适配
Dictionary<TKey,TValue>这类标准字典类型,对线程安全集合的解析逻辑没有针对性实现。
可行解决办法
方案一:用普通字典做中间绑定,同步到ConcurrentDictionary
这是最简单的兼容方案,借助内置对Dictionary的良好支持完成绑定,再手动同步到目标集合:
public class YourPageModel : PageModel { // 用于接收表单绑定的普通字典 [BindProperty] public Dictionary<string, string> TempBindDict { get; set; } = new(); // 业务逻辑使用的ConcurrentDictionary public ConcurrentDictionary<string, string> TargetDict { get; set; } = new(); public void OnPost() { // 将绑定后的内容同步到ConcurrentDictionary foreach (var kvp in TempBindDict) { TargetDict.AddOrUpdate(kvp.Key, kvp.Value, (_, __) => kvp.Value); } } }
页面表单字段需对应中间字典的命名规则:
<input type="text" name="TempBindDict[ProductName]" value="Laptop" /> <input type="text" name="TempBindDict[Price]" value="999" />
方案二:自定义ConcurrentDictionary模型绑定器
通过自定义绑定器告诉系统如何正确解析表单数据到ConcurrentDictionary:
- 实现绑定器和绑定器提供类:
// 自定义绑定器 public class ConcurrentDictBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var modelPrefix = context.ModelName; var dict = new ConcurrentDictionary<string, string>(); // 遍历所有前缀匹配的表单字段 foreach (var formKvp in context.ValueProvider) { if (formKvp.Key.StartsWith($"{modelPrefix}[")) { // 提取字典的键(比如从TargetDict[Key]中取出Key) var keyStartIndex = modelPrefix.Length + 1; var keyEndIndex = formKvp.Key.IndexOf(']'); var dictKey = formKvp.Key.Substring(keyStartIndex, keyEndIndex - keyStartIndex); dict.TryAdd(dictKey, formKvp.Value.FirstValue); } } context.Result = ModelBindingResult.Success(dict); return Task.CompletedTask; } } // 绑定器提供类,用于注册 public class ConcurrentDictBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context.Metadata.ModelType == typeof(ConcurrentDictionary<string, string>)) return new ConcurrentDictBinder(); return null; } }
- 在Program.cs中注册绑定器:
builder.Services.AddRazorPages(options => { options.ModelBinderProviders.Insert(0, new ConcurrentDictBinderProvider()); });
- PageModel中直接使用
ConcurrentDictionary并标记[BindProperty]:
public class YourPageModel : PageModel { [BindProperty] public ConcurrentDictionary<string, string> TargetDict { get; set; } = new(); public void OnPost() { // 直接使用绑定好的TargetDict } }
页面表单字段命名规则和普通字典一致:
<input type="text" name="TargetDict[ProductName]" value="Laptop" />
方案三:手动解析表单数据
跳过模型绑定系统,直接从Request.Form中提取数据并填充:
public class YourPageModel : PageModel { public ConcurrentDictionary<string, string> TargetDict { get; set; } = new(); public void OnPost() { // 假设表单字段以Dict_为前缀,比如Dict_ProductName foreach (var formKey in Request.Form.Keys.Where(k => k.StartsWith("Dict_"))) { var dictKey = formKey.Substring(5); // 去除前缀 TargetDict.TryAdd(dictKey, Request.Form[formKey]); } } }
页面表单字段命名示例:
<input type="text" name="Dict_ProductName" value="Laptop" /> <input type="text" name="Dict_Price" value="999" />
内容的提问来源于stack exchange,提问作者JED
相关产品推荐
相关产品推荐

