Настроить имена атрибутов с помощью связующего устройства модели Web API по умолчанию? - PullRequest
0 голосов
/ 28 июня 2018

У меня есть класс модели запросов, который я пытаюсь использовать по умолчанию для привязки модели 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 для настройки различий?

Ответы [ 2 ]

0 голосов
/ 20 февраля 2019

Поздний ответ, но я недавно столкнулся с этой проблемой. Вы можете просто использовать атрибут BindProperty:

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

Протестировано на .NET Core 2.1 и 2.2

0 голосов
/ 28 июня 2018

Основываясь на дальнейших исследованиях, поведение привязки модели по умолчанию в 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;
        }
    }
}
...