Пользовательский связыватель модели не получает значение от провайдера значений - PullRequest
0 голосов
/ 12 марта 2020

У меня есть пользовательская привязка модели, которая преобразует проведенные значения в другую модель. Ошибка bindingContext.ValueProvider.GetValue(modelName) не возвращает ничего, даже если есть значения, отправленные от клиента.

Метод действия

[HttpPost]
public ActionResult Update([DataSourceRequest] DataSourceRequest request, 
                           [Bind(Prefix = "models")] AnotherModel items)
{
    return Ok();
}

Класс целевой модели

[ModelBinder(BinderType = typeof(MyModelBinder))]
public class AnotherModel
{
    IEnumerable<Dictionary<string, object>> Items { get; set; }
}

Подшивка модели Cutomer

public class MyModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;

        // ISSUE: valueProviderResult is always None
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }


        //here i will convert valueProviderResult to AnotherModel


        return Task.CompletedTask;
    }
}

Быстрый просмотр показывает, что ValueProvider имеет значения

enter image description here

UPDATE1

Внутри метода действия Update, когда я могу выполнить итерацию по IFormCollection, у Request.Form есть все пары ключ и значение. Не уверен, почему связыватель модели не может получить его.

foreach (var f in HttpContext.Request.Form)
{
    var key = f.Key;
    var v = f.Value;
}

Ответы [ 2 ]

0 голосов
/ 12 марта 2020

Мой пример

В моем клиенте я отправляю заголовок в запросе, этот заголовок Base64String (Json Сериализованный объект)

Объект -> Json -> Base64.

Заголовки не могут быть многострочными. С base64 мы получаем 1 строку.

Все это применимо к Body и другим источникам.

Класс заголовка

public class RequestHeader : IHeader
{
    [Required]
    public PlatformType Platform { get; set; } //Windows / Android / Linux / MacOS / iOS

    [Required]
    public ApplicationType ApplicationType { get; set; } 

    [Required(AllowEmptyStrings = false)]
    public string UserAgent { get; set; } = null!; 

    [Required(AllowEmptyStrings = false)]
    public string ClientName { get; set; } = null!; 

    [Required(AllowEmptyStrings = false)]
    public string ApplicationName { get; set; } = null!;

    [Required(AllowEmptyStrings = true)]
    public string Token { get; set; } = null!; 


    public string ToSerializedString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

Интерфейс IHeader

public interface IHeader
{
}

Модель Binder

public class HeaderParameterModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        StringValues headerValue = bindingContext.HttpContext.Request.Headers.Where(h =>
        {
            string guid = Guid.NewGuid().ToString();
            return h.Key.Equals(bindingContext.ModelName ?? guid) | 
                   h.Key.Equals(bindingContext.ModelType.Name ?? guid) | 
                   h.Key.Equals(bindingContext.ModelMetadata.ParameterName); 
        }).Select(h => h.Value).FirstOrDefault();
        if (headerValue.Any())
        {
            try
            {
                //Convert started
                bindingContext.Model = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)), bindingContext.ModelType);
                bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
            }
            catch
            {
            }
        }
        return Task.CompletedTask;
    }
}

Поставщик связывателей моделей

Мы можем работать с любым BindingSource.

  • Body
  • BindingSource Custom
  • BindingSource Form
  • BindingSource FormFile
  • BindingSource Header
  • BindingSource ModelBinding
  • BindingSource Path
  • BindingSource Query
  • BindingSource Services
  • BindingSource Special

public class ParametersModelBinderProvider : IModelBinderProvider
{
    private readonly IConfiguration configuration;

    public ParametersModelBinderProvider(IConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType.GetInterfaces().Where(value => value.Name.Equals(nameof(ISecurityParameter))).Any() && BindingSource.Header.Equals(context.Metadata.BindingSource))
        {
            return new SecurityParameterModelBinder(configuration);
        }

        if (context.Metadata.ModelType.GetInterfaces().Where(value=>value.Name.Equals(nameof(IHeader))).Any() && BindingSource.Header.Equals(context.Metadata.BindingSource))
        {
            return new HeaderParameterModelBinder();
        }
        return null!;
    }
}

In Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.ModelBinderProviders.Insert(0,new ParametersModelBinderProvider(configuration));
    });
}

Действие контроллера

ExchangeResult - мой класс результатов.

[HttpGet(nameof(Exchange))]
public ActionResult<ExchangeResult> Exchange([FromHeader(Name = nameof(RequestHeader))] RequestHeader header)
{
    //RequestHeader previously was processed in modelbinder.
    //RequestHeader is null or object instance.  
    //Some instructions
}
0 голосов
/ 12 марта 2020

Если вы изучите исходный код MVC CollectionModelBinder , вы заметите, что значения формы "name [index]" будут возвращать ValueProviderResult.None и должны обрабатываться отдельно.

Кажется, вы пытаетесь решить не ту проблему. Я бы предложил привязку к стандартному классу коллекции, например Dictionary.

Либо;

public ActionResult Update([DataSourceRequest] DataSourceRequest request, 
                           [Bind(Prefix = "models")] Dictionary<string, RecordTypeName> items)

Или;

public class AnotherModel : Dictionary<string, RecordTypeName> {}

Если вы не знаете, какой тип будет иметь каждое значение словаря во время компиляции, то здесь пользовательский связыватель пригодится.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...