在 ASP.NET Core 中使用反射的自定义模型绑定

Custom model binding using reflection in ASP.NET Core

我有以下型号

public class MyModel
{
   public string Name {get;set;}
   public int? Age {get;set;}
   public string City {get;set;}
   public decimal? Salary {get;set;}
   public JObject ExtraFields {get;set;}
}

我正在尝试实施自定义模型绑定器。如果提交的表单具有与模型的 属性 匹配的 key,则设置模型的 属性 值,否则将键和值添加到 ExtraFields。 注意 ExtraFields 是 JObject

public class MyModelBinder: IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }


        MyModel model = new MyModel()
        {
            ExtraFields = new JObject()
        };

        var form = bindingContext.HttpContext.Request.Form;

        var properties = typeof(MyModel).GetProperties();
        foreach (var key in form.Keys)
        {
            var p = properties.FirstOrDefault(x => x.Name == key);
            var val = form[key];
            if (p != null)
            {
                p.SetValue(model, val); // throws exception
            }
            else
            {
                var v = StringValues.IsNullOrEmpty(val) ? null : val.ToString();
                model.ExtraFields.Add(key, v);
            }
        }

        bindingContext.Model = model;
        bindingContext.Result = ModelBindingResult.Success(model);

        return Task.CompletedTask;
    }
}

问题
设置模型值时出现异常

Object of type 'Microsoft.Extensions.Primitives.StringValues' cannot be converted to type 'System.String

如果可能,我想避免检查模型目标的类型 属性,然后将值转换为目标类型。这应该隐式发生。

基本上,对于所有匹配的键调用 ASP.NET 的默认绑定器,并为所有剩余的键添加值 ExtraFields

Object of type 'Microsoft.Extensions.Primitives.StringValues' cannot be converted to type 'System.String

val 是类型 Microsoft.Extensions.Primitives.StringValues 不能直接将其值分配给模型字段。

首先可以直接通过val.ToString().

得到对应值的字符串形式

由于每个字段的类型不同,您可以创建自定义Convert.ChangeType方法动态转换类型通过传递 val.ToString()p.PropertyType.

更多细节,参考这个demo:

  public class MyModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }
 
            MyModel model = new MyModel()
            {
                ExtraFields = new JObject()
            };

            var form = bindingContext.HttpContext.Request.Form;

            var properties = typeof(MyModel).GetProperties();
            foreach (var key in form.Keys)
            {
                var p = properties.FirstOrDefault(x => x.Name == key);
                var val = form[key];
                if (p != null)
                {
                   // call custom method ChangeType and pass two parameters.
                    p.SetValue(model, ChangeType(val.ToString(),p.PropertyType)); 

                }
                else
                {
                    var v = StringValues.IsNullOrEmpty(val) ? null : val.ToString();
                    model.ExtraFields.Add(key, v);
                }
            }

            bindingContext.Model = model;
            bindingContext.Result = ModelBindingResult.Success(model);

            return Task.CompletedTask;
        }

        // custom method
        public static object ChangeType(object value, Type conversion)
        {
            var t = conversion;

            if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                if (value == null)
                {
                    return null;
                }

                t = Nullable.GetUnderlyingType(t);
            }

            return Convert.ChangeType(value, t);
        }
    }

更新

@LP13 提供了更简单的解决方案:

TypeConverter typeConverter = TypeDescriptor.GetConverter(p.PropertyType); 
object propValue = typeConverter.ConvertFromString(val); p.PropertyType));  
p.SetValue(model, propValue); 

这是我的测试结果: