» » .Net Core Api: получение данных в запросе из разных источников

 

.Net Core Api: получение данных в запросе из разных источников

Автор: admin от 17-03-2020, 23:55, посмотрело: 162

В .Net Core есть встроенный механизм Model Binding, позволяющий не просто принимать входные параметры в контроллерах, а получать сразу объекты с заполненными полями. Это позволяет встроить в такой объект все нужные проверки с помощью Model Validation.



Вот только данные, нужные для работы API, приходят нам не только из Query или Body. Какие-то данные нужно получить из Headers (в моем случае там был json в base64), какие-то — из внешних сервисов или ActionRoute, если вы используете REST. Для получения данных оттуда можно использовать свой Binding. Правда и тут есть проблема: если вы решили не нарушать инкапсуляцию и инициализировать модель через конструктор, то придется пошаманить.



Для себя и для будущих поколений я решил написать что-то вроде инструкции по использованию Binding и шаманство с ним.

тут, за что автору большое спасибо.



private void ForceSetValue(PropertyInfo propertyInfo, object obj, object value)
{
    var propName = $"<{propertyInfo.Name}>k__BackingField";
    var propFlags = BindingFlags.Instance | BindingFlags.NonPublic;
            
    obj.GetType().GetField(propName, propFlags)?.SetValue(obj, value);
}

Ну и объединяем эти методы в едином вызове:

public Task BindModelAsync(ModelBindingContext bindingContext)
{
    var moreData = GetFromHeaderAndDecode(bindingContext.HttpContext.Request.Headers);
    if (moreData == null)
    {
        return Task.CompletedTask;
    }

    var modelType = bindingContext.ModelMetadata.UnderlyingOrModelType;
    if (NeedActivator(modelType))
    {
        var data = Activator.CreateInstance(modelType, moreData);
        bindingContext.Result = ModelBindingResult.Success(data);

        return Task.CompletedTask;
    }

    var model = bindingContext.HttpContext.Request.Method == "GET"
                            ? GetModelFromQuery(bindingContext, modelType)
                            : GetModelFromBody(bindingContext, modelType);

    if (model is null)
    {
        throw new Exception("Невозможно сериализовать запрос");
    }

    var ignoreCase = StringComparison.InvariantCultureIgnoreCase;
    var dataProperty = modelType.GetProperties()
                            .FirstOrDefault(p => p.Name.Equals(typeof(T).Name, ignoreCase));

    if (dataProperty != null)
    {
        ForceSetValue(dataProperty, model, moreData);
    }

    bindingContext.Result = ModelBindingResult.Success(model);
    return Task.CompletedTask;
}

Осталось поправить BinderProvider, чтобы он реагировал на любые классы с нужным свойством:

public class MoreDataBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
       var modelType = context.Metadata.UnderlyingOrModelType;
       if (HasDataProperty(modelType))
       {
           return new BinderTypeModelBinder(typeof(PrivateDataBinder<MoreData>));
       }
       return null;
    }
    private bool HasDataProperty(IReflect modelType)
    {
        var propFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
        var properties = modelType.GetProperties(propFlags);

        return properties.Select(p => p.Name) .Contains(nameof(MoreData));
    }
}


Вот собственно и все. Binder получился несколько сложнее чем в Easy Mode, зато теперь мы можем привязывать «внешние» свойства во всех методах всех контроллеров без дополнительных усилий. Из минусов:


  1. Нужно у конструктора объектов с приватными полями обязательно указывать атрибут [JsonConstrustor]. Но это вполне ложится в логику модели и никак не мешает ее восприятию.

  2. Где-то вам может потребоваться получить MoreData не из заголовка. Но это лечится созданием отдельного класса.

  3. Остальные члены команды должны быть в курсе наличия магии. Но документация спасет человечество.


Полный листинг получившегося Binder здесь:



Источник: Хабр / Интересные публикации

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *