代码之家  ›  专栏  ›  技术社区  ›  Jason W

在web api 2的自定义绑定器中使用默认的imodelbinder

  •  1
  • Jason W  · 技术社区  · 6 年前

    如何在自定义的web api中调用默认的模型绑定器 IModelBinder 是吗?我知道mvc有一个默认的绑定器,但是我不能将它与web api一起使用。我只想使用默认的web api绑定器,然后运行一些自定义逻辑(以避免重新发明轮子)。

    public class CustomBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            // Get default binding (can't mix Web API and MVC)
            var defaultMvcBinder = System.Web.ModelBinding.ModelBinders.Binders.DefaultBinder;
            var result = defaultMvcBinder.BindModel(actionContext, bindingContext); // Won't work
            if (result == false) return false;
            // ... set additional model properties
            return true;
        }
    }
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   Jason W    6 年前

    如果其他人遇到这个问题,我必须用激活上下文实现自定义模型绑定器,因为从web api没有什么可重用的。下面是我为我的有限场景使用的需要支持的解决方案。

    用法

    下面的实现允许我任选地使用任何模型 JsonProperty 对于模型绑定,但如果未提供,则将默认为仅使用属性名。它支持来自标准.NET类型(字符串、int、double等)的映射。还没有完全准备好生产,但它已经满足了我的用例。

    [ModelBinder(typeof(AttributeModelBinder))]
    public class PersonModel
    {
        [JsonProperty("pid")]
        public int PersonId { get; set; }
    
        public string Name { get; set; }
    }
    

    这允许在请求中映射以下查询字符串:

    /api/endpoint?pid=1&name=test
    

    实施

    首先,解决方案定义了一个映射属性,用于跟踪模型的源属性和从值提供程序设置值时要使用的目标名称。

    public class MappedProperty
    {
        public MappedProperty(PropertyInfo source)
        {
            this.Info = source;
            this.Source = source.Name;
            this.Target = source.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? source.Name;
        }
        public PropertyInfo Info { get; }
        public string Source { get; }
        public string Target { get; }
    }
    

    然后,定义一个自定义模型绑定器来处理映射。它缓存反射的模型属性,以避免在后续调用中重复反射。它可能还没有完全准备好生产,但初步测试是有希望的。

    public class AttributeModelBinder : IModelBinder
    {
        public static object _lock = new object();
        private static Dictionary<Type, IEnumerable<MappedProperty>> _mappings = new Dictionary<Type, IEnumerable<MappedProperty>>();
    
    
        public IEnumerable<MappedProperty> GetMapping(Type type)
        {
            if (_mappings.TryGetValue(type, out var result)) return result; // Found
            lock (_lock)
            {
                if (_mappings.TryGetValue(type, out result)) return result; // Check again after lock
                return (_mappings[type] = type.GetProperties().Select(p => new MappedProperty(p)));
            }
        }
    
        public object Convert(Type target, string value)
        {
            try
            {
                var converter = TypeDescriptor.GetConverter(target);
                if (converter != null)
                    return converter.ConvertFromString(value);
                else
                    return target.IsValueType ? Activator.CreateInstance(target) : null;
            }
            catch (NotSupportedException)
            {
                return target.IsValueType ? Activator.CreateInstance(target) : null;
            }
        }
    
        public void SetValue(object model, MappedProperty p, IValueProvider valueProvider)
        {
            var value = valueProvider.GetValue(p.Target)?.AttemptedValue;
            if (value == null) return;
            p.Info.SetValue(model, this.Convert(p.Info.PropertyType, value));
        }
    
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            try
            {
                var model = Activator.CreateInstance(bindingContext.ModelType);
                var mappings = this.GetMapping(bindingContext.ModelType);
                foreach (var p in mappings)
                    this.SetValue(model, p, bindingContext.ValueProvider);
                bindingContext.Model = model;
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }
    }