You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET MVC Core:如何扩展模型绑定器处理空列表?

在ASP.NET Core中实现空集合替代null的模型绑定行为

默认模型绑定器在请求包含空数组时,会为可枚举属性返回null,我希望返回空集合,同时保留其他属性的默认行为。正从ASP.NET MVC迁移至ASP.NET MVC Core,旧项目中曾使用继承DefaultModelBinder的自定义模型绑定器,代码如下:

public class EmptyListModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var model = base.CreateModel(controllerContext, bindingContext, modelType);

        if (model == null || model is IEnumerable)
            return model;

        foreach (var property in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            object value = property.GetValue(model);
            if (value != null)
               continue;

            if (property.PropertyType.IsArray)
            {
                value = Array.CreateInstance(property.PropertyType.GetElementType(), 0);
                property.SetValue(model, value);
            }
            else if (property.PropertyType.IsGenericType)
            {
                Type typeToCreate;
                Type genericTypeDefinition = property.PropertyType.GetGenericTypeDefinition();
                if (genericTypeDefinition == typeof(IDictionary<,>))
                {
                    typeToCreate = typeof(Dictionary<,>).MakeGenericType(property.PropertyType.GetGenericArguments());
                }
                else if (genericTypeDefinition == typeof(IEnumerable<>) ||
                     genericTypeDefinition == typeof(ICollection<>) ||
                     genericTypeDefinition == typeof(IList<>) ||
                     genericTypeDefinition == typeof(List<>))
                {
                    typeToCreate = typeof(List<>).MakeGenericType(property.PropertyType.GetGenericArguments());
                }
                else
                {
                    continue;
                }

                value = Activator.CreateInstance(typeToCreate);
                property.SetValue(model, value);
            }
        }

        return model;
    }
}

但DefaultModelBinder在ASP.NET Core中已不存在。尝试扩展ComplexTypeModelBinder,但其已被弃用;替代它的ComplexObjectModelBinder是密封类,无法继承或组合使用。请问在ASP.NET Core中如何实现该行为?


解决方案:使用模型绑定器提供程序+自定义绑定逻辑

在ASP.NET Core中,无需继承密封的ComplexObjectModelBinder,可通过自定义模型绑定器提供程序结合默认绑定后处理的方式实现需求,既保留默认绑定行为,又能将null的可枚举属性替换为空集合。

步骤1:实现自定义模型绑定器

创建绑定器,先执行默认绑定逻辑,再遍历模型属性修正空可枚举类型:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;

public class EmptyCollectionModelBinder : IModelBinder
{
    private readonly IModelBinder _defaultBinder;

    public EmptyCollectionModelBinder(IModelBinder defaultBinder)
    {
        _defaultBinder = defaultBinder;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // 先执行默认模型绑定
        await _defaultBinder.BindModelAsync(bindingContext);

        if (!bindingContext.Result.IsModelSet || bindingContext.Result.Model == null)
        {
            return;
        }

        var model = bindingContext.Result.Model;
        var modelType = model.GetType();

        // 跳过本身为可枚举类型的模型(如直接绑定List<T>参数)
        if (model is IEnumerable && !(model is string))
        {
            return;
        }

        // 遍历所有公共实例属性
        foreach (var property in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var propertyValue = property.GetValue(model);
            if (propertyValue != null)
            {
                continue;
            }

            var propertyType = property.PropertyType;
            object emptyCollection = null;

            // 处理数组类型
            if (propertyType.IsArray)
            {
                var elementType = propertyType.GetElementType();
                emptyCollection = Array.CreateInstance(elementType, 0);
            }
            // 处理泛型集合/字典
            else if (propertyType.IsGenericType)
            {
                var genericDef = propertyType.GetGenericTypeDefinition();
                var genericArgs = propertyType.GetGenericArguments();

                if (genericDef == typeof(IDictionary<,>))
                {
                    emptyCollection = Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(genericArgs));
                }
                else if (genericDef == typeof(IEnumerable<>) ||
                         genericDef == typeof(ICollection<>) ||
                         genericDef == typeof(IList<>) ||
                         genericDef == typeof(List<>))
                {
                    emptyCollection = Activator.CreateInstance(typeof(List<>).MakeGenericType(genericArgs));
                }
            }

            // 为属性赋值空集合
            if (emptyCollection != null)
            {
                property.SetValue(model, emptyCollection);
            }
        }

        // 更新绑定结果
        bindingContext.Result = ModelBindingResult.Success(model);
    }
}

步骤2:实现模型绑定器提供程序

创建提供程序,指定哪些模型类型使用自定义绑定器,同时复用默认绑定器:

using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;

public class EmptyCollectionModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        // 仅处理复杂类型(排除值类型、字符串、可枚举类型)
        if (!context.Metadata.IsComplexType || 
            context.Metadata.ModelType.IsValueType || 
            context.Metadata.ModelType == typeof(string) ||
            (typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType) && context.Metadata.ModelType != typeof(string)))
        {
            return null;
        }

        // 获取默认复杂类型绑定器
        var defaultBinder = context.CreateBinder(context.Metadata);
        return new EmptyCollectionModelBinder(defaultBinder);
    }
}

步骤3:注册绑定器提供程序

在Program.cs中,将自定义提供程序插入到绑定器集合的最前面,确保优先执行:

builder.Services.AddControllers(options =>
{
    options.ModelBinderProviders.Insert(0, new EmptyCollectionModelBinderProvider());
});

方案说明

  • 完全保留默认模型绑定的所有行为,仅在绑定完成后对null的可枚举属性进行修正。
  • 支持数组、List<T>、IList<T>、ICollection<T>、IEnumerable<T>、IDictionary<TKey,TValue>等类型,自动创建对应空实例。
  • 跳过直接绑定可枚举类型的场景,避免不必要的处理。

内容的提问来源于stack exchange,提问作者wekso

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 06:50:07