На тот случай, если другие натолкнутся на этот вопрос, мне пришлось реализовать привязку пользовательской модели с контекстом активации, поскольку повторно использовать из Web API нечего. Вот решение, которое я использую для своих ограниченных сценариев, которые необходимо было поддерживать.
Использование
Реализация ниже позволяет мне разрешить любой модели дополнительно использовать 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;
}
}
}