Основываясь на дальнейших исследованиях, поведение привязки модели по умолчанию в Web API не поддерживает атрибуты JsonProperty
или DataMember
, и наиболее вероятные решения, по-видимому, либо (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;
}
}
}