【问题标题】:Customize attribute names with Web API default model binder?使用 Web API 默认模型绑定器自定义属性名称?
【发布时间】:2018-12-07 19:00:50
【问题描述】:

我有一个请求模型类,我正在尝试使用默认的 Web API 2 模型绑定 (.NET 4.6.1)。一些查询字符串参数与模型属性匹配,但有些不匹配。

public async Task<IHttpActionResult> Get([FromUri]MyRequest request) {...}

示例查询字符串:

/api/endpoint?country=GB

示例模型属性:

public class MyRequest
{
    [JsonProperty("country")] // Did not work
    [DataMember(Name = "country")] // Also did not work
    public string CountryCode { get; set; }
    // ... other properties
}

有没有办法在我的模型上使用属性(比如您可能使用[JsonProperty("country")])来避免实现自定义模型绑定?还是最好的方法只是使用为 QueryString 创建一个特定的模型来绑定,然后使用 AutoMapper 来定制差异?

【问题讨论】:

  • 您是否尝试过使用JsonProperty...?
  • 我做到了,但我无法让它与[FromUri]一起工作
  • 使用 DataMemberAttribute 否则你必须为这个类写modelbinder
  • [DataMember(Name="country")] 也不起作用。 Web API 默认模型绑定器似乎没有考虑 JsonPropertyAttribute 或 DataMemberAttribute 装饰......不幸的是:)

标签: c# asp.net-web-api2 model-binding .net-4.6.1


【解决方案1】:

迟到的答案,但我最近也遇到了这个问题。您可以简单地使用BindProperty 属性:

public class MyRequest
{
    [BindProperty(Name = "country")]
    public string CountryCode { get; set; }
}

在 .NET Core 2.1 和 2.2 上测试

【讨论】:

  • 虽然我的 OP 是 4.6.1,但我们自己已经迁移到 .net core,这就是我们使用的方法。
【解决方案2】:

基于进一步的研究,Web API 中的默认模型绑定行为不支持JsonPropertyDataMember 属性,最可能的解决方案似乎是(1)自定义模型绑定器或(2)维护 2 组模型以及它们之间的映射。

我选择了自定义模型绑定器(下面的实现),因此我可以重复使用它而不必复制所有模型(并维护每个模型之间的映射)。

用法

下面的实现允许我让任何模型有选择地使用JsonProperty 进行模型绑定,但如果未提供,将默认为仅属性名称。它支持来自标准 .NET 类型(string、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;
        }
    }
}

【讨论】:

    猜你喜欢
    • 2012-04-24
    • 2018-08-22
    • 1970-01-01
    • 1970-01-01
    • 2017-08-08
    • 1970-01-01
    • 2021-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多