【问题标题】:Custom model binding using reflection in ASP.NET Core在 ASP.NET Core 中使用反射的自定义模型绑定
【发布时间】:2020-07-14 19:00:34
【问题描述】:

我有以下型号

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

【问题讨论】:

  • 根据文档:(StringValues) Represents zero/null, one, or many strings in an efficient way. 因此,无法实现到单个字符串值的转换。也许浏览一下 MVC 源代码,看看它是如何完成的?
  • 嗯我可能错了 - 这个答案似乎指向存在的隐式转换 - stackoverflow.com/a/48189292/426894
  • 是的,我不知道我第一次是怎么错过的。 docs.microsoft.com/en-us/dotnet/api/…
  • 是的,但我遇到了异常

标签: asp.net-core asp.net-core-mvc model-binding asp.net-core-2.1 defaultmodelbinder


【解决方案1】:

“Microsoft.Extensions.Primitives.StringValues”类型的对象不能 转换为类型'System.String

val 属于 Microsoft.Extensions.Primitives.StringValues 类型,无法直接将其值分配给模型字段。

首先,可以直接通过val.ToString()获取对应值的字符串形式

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

更多详情,请参考这个演示:

  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); 

这是我的测试结果:

【讨论】:

  • 是的,这就是我最终要做的。我的更改类型代码是TypeConverter typeConverter = TypeDescriptor.GetConverter(p.PropertyType); object propValue = typeConverter.ConvertFromString(val);
  • @LP13,是的,您提供的方法会更简单,您可以添加回复并接受它作为答案,或者接受我的回复作为答案(我已经添加了您的想法),这将帮助其他有类似问题的人更轻松地找到解决方案。
  • 但是这不会绑定数组或复杂对象。这就是为什么我一直在寻找对已知属性使用默认绑定的选项,并将所有剩余的键添加到 JObject
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-11
  • 2017-02-09
  • 1970-01-01
  • 2019-05-10
  • 1970-01-01
  • 2020-09-04
  • 2018-05-07
相关资源
最近更新 更多