ASP. NET Базовая модельBindingContext использует только 1 поставщика значений (игнорирует RouteDataValueProvider) - PullRequest
1 голос
/ 02 марта 2020

В настоящее время я конвертирую библиотеку ASP. NET в ASP. NET Ядро, которое сильно связано с привязкой к модели, и одну проблему, которую я не могу решить, это то, что для случаев, когда требуется привязка к модели чтобы попытаться получить значения через провайдеров значений как из (From) Query, так и (From) Route (так как они имеют разные BindingSource), он может получить доступ только к одному, что приводит к сбою привязки, так как не удается найти целевое значение.

При отладке приложения я вижу, что ModelBindingContext имеет обоих провайдеров значений в своем частном OriginalValueProvider, но при попытке доступа к ValueProvider он возвращает только одного из провайдеров - в этом случае только QueryStringValueProvider, а не RouteValueProvider).

Похоже, что это не было обычным поведением ASP. NET, поскольку там существующее приложение с радостью использует обоих провайдеров значений для поиска своего значения - и делает это успешно. Это какое-то существенное изменение между System.Web.Http.ModelBinding -> Microsoft.AspNetCore. Mvc .ModelBinding? Есть ли какая-то опция, которая должна быть включена? Хотелось бы получить некоторые рекомендации.

Ниже приведены некоторые ограничения экрана из моей отладки:

System.Web.Http original: System.Web.Http original

Microsoft .AspNetCore. Mvc переделка: Microsoft.AspNetCore.Mvc rework

Microsoft.AspNetCore. Mvc ОригиналValueProvider (s) подтверждение: Microsoft.AspNetCore.Mvc OriginalValue proof

Редактировать: И здесь следует несколько фрагментов кода в соответствии с запросом:

Контроллер:

[HttpGet]
[Route("accounts/{accountId}/catalogs/{catalogId}/medias", Name = "GetCatalogMedias")]
[ProducesResponseType(typeof(IList<MediaResponse>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetCatalogMedias(
    [FromRoute] string accountId,
    [FromRoute] string catalogId,
    [FromQuery, SortingFieldDefaultValue("created", SortingDirection.Desc)] IEnumerable<SortingField> sort,
    [FromQuery] QueryTree<MediaResponse> q = null,
    [FromQuery, Range(0, int.MaxValue)] int offset = 0,
    [FromQuery, Range(1, 200)] int limit = 200)
{
    if (!ModelState.IsValid)
    {
        return await _responseFactory.CreateBadRequest(ModelState);
    }

Binder:

public class CatalogMediasQueryModelBinder<T> : BaseBinder, IModelBinder
{
    private readonly QueryModelBinder _queryModelBinder;

    public CatalogMediasQueryModelBinder(QueryModelBinder queryModelBinder)
    {
        _queryModelBinder = queryModelBinder;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var success = TryGetValueProviderResult(bindingContext, out var catalogId, "catalogId");

        if (!success)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Missing catalogId");
            return;
        }
        //...

Base Binder:

public class BaseBinder
{
    protected bool TryGetValueProviderResult(ModelBindingContext bindingContext, out string result, string findValue = null)
    {
        if (bindingContext?.ValueProvider == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }
        result = null;
        var modelName = findValue ?? bindingContext.ModelName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
        if (valueProviderResult == ValueProviderResult.None)
        {
            return false;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        result = value;
        return true;
    }

    protected Task SetModelBindingResult<T>(ModelBindingContext bindingContext, T value)
    {
        bindingContext.Result = ModelBindingResult.Success(value);
        return Task.CompletedTask;
    }
}
...