Использование IModelBinder по умолчанию в пользовательском связывателе в Web API 2 - PullRequest
0 голосов
/ 28 июня 2018

Как вы называете связыватель модели по умолчанию в Web API в пользовательском IModelBinder? Я знаю, что у MVC есть связыватель по умолчанию, но я не могу использовать его с Web API. Я просто хочу использовать привязку веб-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 Ответ

0 голосов
/ 06 июля 2018

На тот случай, если другие натолкнутся на этот вопрос, мне пришлось реализовать привязку пользовательской модели с контекстом активации, поскольку повторно использовать из 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;
        }
    }
}
...