Высокое время выполнения, когда свойство типа PropertyInfo устанавливается для модели в пользовательском связывателе модели - PullRequest
0 голосов
/ 30 октября 2018

Рассмотрим следующий пример:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace WebApiApp.Controllers
{
    public class TheModelFields
    {
        public int Id { get; set; }
    }

    [ModelBinder(typeof(TheModelBinder))]
    public class TheModel
    {
        public PropertyInfo PropInfo { get; set; }
        public PropertyInfo FieldPropInfo;
        public object BoxedPropInfo { get; set; }
    }

    enum TestMode
    {
        PropInfo,
        FieldPropInfo,
        BoxedPropInfo
    }

    public class TheModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext.HttpContext.Request.Query.TryGetValue("testMode", out var modeStr) && Enum.TryParse(typeof(TestMode), modeStr, true, out var mode))
            {
                var model = new TheModel();
                var propInfo = typeof(TheModelFields).GetProperty("Id");

                switch (mode)
                {
                    case TestMode.PropInfo:
                        model.PropInfo = propInfo;
                        break;
                    case TestMode.FieldPropInfo:
                        model.FieldPropInfo = propInfo;
                        break;
                    case TestMode.BoxedPropInfo:
                        model.BoxedPropInfo = propInfo;
                        break;
                }

                bindingContext.Result = ModelBindingResult.Success(model);
                Timer.Stopwatch.Restart();
                return Task.CompletedTask;
            }
            else
            {
                bindingContext.Result = ModelBindingResult.Failed();
                return Task.CompletedTask;
            }
        }
    }

    public static class Timer
    {
        public static Stopwatch Stopwatch = new Stopwatch();
    }

    [ApiController]
    public class TestController : ControllerBase
    {
        [HttpGet("test")]
        public IActionResult Test([FromQuery]TheModel model)
        {
            Timer.Stopwatch.Stop();
            if (model is null)
                return BadRequest("pass testMode=PropInfo|FieldPropInfo|BoxedPropInfo for test");
            else
                return Ok($"Time: {Timer.Stopwatch.ElapsedMilliseconds}");
        }
    }
}

Класс TheModel имеет собственный ModelBinder с именем TheModelBinder. В этом тесте TheModelBinder решает, какое свойство / поле установить, основываясь на значении параметра строки запроса с именем testMode.

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

Если testMode == PropInfo, то TheModelBinder устанавливает значение для свойства типа PropertyInfo с именем PropInfo.
(Это довольно медленно, около 800-1000 мс)

Если testMode == FieldPropInfo, то TheModelBinder устанавливает значение в поле типа PropertyInfo с именем PropInfoField.
(Этот занимает 0 мс)

Если testMode == BoxedPropInfo, то TheModelBinder устанавливает значение для свойства объекта типа с именем BoxedPropInfo.
(Этот тоже занимает 0 мс)

Теперь возникает вопрос: почему первый testMode (установка свойства PropInfo) увеличивает время выполнения (после успешного связывания модели) до 800-1000 миллисекунд?

Проверено на asp.net core 2.1 и 2.2 preview2

Чтобы проверить это самостоятельно, вы можете сделать dotnet new webapi и вставить содержимое семпла в новый файл. Если вы запустите приложение на порту 5000, вы можете проверить время выполнения, используя следующие URL:

  • http://localhost:5000/test?testMode=propInfo
  • http://localhost:5000/test?testMode=propInfoField
  • http://localhost:5000/test?testMode=boxedPropInfo

1 Ответ

0 голосов
/ 30 октября 2018

Если вы включили ведение журнала уровня отладки и внимательно следите за журналом во время обновления браузера, вы можете увидеть, где происходит пауза при использовании testMode=propInfo:

dbug: Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder[26]
      Attempting to validate the bound parameter 'model' of type 'Q53063808.Controllers.TheModel' ...
dbug: Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder[27]
      Done attempting to validate the bound parameter 'model' of type 'Q53063808.Controllers.TheModel'.

Это проверка модели связующего параметра . Проверка модели отвечает за проверку таких вещей, как атрибут проверки модели [Required].

Чтобы валидация поддерживала произвольные структуры модели, она по существу рекурсивно сканирует тип модели и пытается проверить каждое свойство. Поскольку PropertyInfo - довольно большой тип, проверка всех свойств занимает некоторое время, даже если проверять нечего.

Однако проверка всегда основана на объявленном типе модели 1016 *, поэтому свойство object не сканируется. И проверка также относится только к свойствам. Вот почему свойство PropertyInfo является единственным случаем, когда проверка действительно требует времени. Вы также можете подтвердить это, добавив другой тип для MemberInfo, который немного меньше, чем PropertyInfo. Проверка будет проходить немного быстрее, чем в случае PropertyInfo.


Вы не можете действительно выборочно отключить проверку модели (например, используя какой-либо атрибут SkipValidation). Однако вы можете указать из связывателя модели, что проверка не должна выполняться для модели. Это делается путем установки состояния проверки для подавления проверки:

bindingContext.ValidationState.Add(model, new ValidationStateEntry { SuppressValidation = true });
bindingContext.Result = ModelBindingResult.Success(model);

Это полностью пропустит проверку модели, поэтому время также должно упасть до нуля.

Кроме того, вы также можете настроить MVC для подавления проверки для дочерних элементов, когда он обнаруживает элемент PropertyInfo в типе вашей модели. Для этого вам нужно добавить следующую конфигурацию в ConfigureServices вашего стартапа:

services.AddMvc(options =>
{
    // suppress child validation for `PropertyInfo` members
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(PropertyInfo)));
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...