Атрибут ValidationAttribute для пользовательской проверки не работает для проверки нулевого или пустого (на стороне сервера) - PullRequest
0 голосов
/ 18 марта 2020

Я обнаружил, что метод IsValid () пользовательского атрибута ValidationAttribute никогда не вызывается, когда атрибут не является частью вызывающего запроса, а является частью сигнатуры метода.

Итак, мне интересно, как может работать RequiredAttribute для определенных параметров QueryString. Я заглянул в исходный код RequiredAttribute, и у него возникла бы та же проблема, которую я опишу,

Например, со следующей сигнатурой метода действия:

    public ActionResult<IEnumerable<SpaceBody>> Get(
        [FromQuery]
        [SomeAttribute]string some,
        int minDistance, int maxDistance)

Метод IsValid будет вызывается с запросами как:

  • Api / SpaceBody? some = xxx

Но не будет вызываться с:

  • Api / SpaceBody
  • Api / SpaceBody? Some =

До тех пор, пока это не имеет некоторого смысла: если параметр отсутствует в запросе, как фреймворк узнает, какой фильтр применить? Что ж, он должен знать по сигнатуре метода действия

Если я изменю сигнатуру на:

    public ActionResult<IEnumerable<SpaceBody>> Get(
        [FromQuery]
        [Required, Some]string some,
        int minDistance, int maxDistance)

Метод IsValid пользовательского атрибута будет срабатывать также для:

  • Api / SpaceBody
  • Api / SpaceBody? Some =

Итак, что же делает RequiredAttribute для создания метода IsValid () другого атрибута для вызова?

Если я загляну в код github, то ничего не смогу заметить: https://github.com/microsoft/referencesource/blob/master/System.ComponentModel.DataAnnotations/DataAnnotations/RequiredAttribute.cs

На своем пользовательском атрибуте я делаю следующее:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class SomeAttribute : ValidationAttribute
{
    private const string SOME_REGEX = @"(?i)^\b(xx)\b$";

    private const string SOME_NOT_VALID_ERROR_MESSAGE = "Some is not valid.";
    private const string SOME_REQUIRED_ERROR_MESSAGE = "Some must have a value.";

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var some = Convert.ToString(value);

        if (string.IsNullOrWhiteSpace(some))
            return new ValidationResult(SOME_REQUIRED_ERROR_MESSAGE);

        if (!Regex.Match(some, SOME_REGEX, RegexOptions.IgnoreCase).Success)
            return new ValidationResult(SOME_NOT_VALID_ERROR_MESSAGE);

        return ValidationResult.Success;
    }
}

Он содержит лог c для отклонения пустых значений (как это делает RequiredAttribute), поэтому, если я использую [Обязательный, Некоторые], он будет дважды показывать любой пустой или пустой вызов параметра, который не является однопользовательским.

Если я оставьте только [Some], пустые или пустые ошибки будут пропущены, что является худшим случаем.

Единственное решение, которое я вижу сейчас, - это удалить пустые или пустые логики c из пользовательские атрибуты и использование [обязательно, некоторые]. Хорошо, это может быть решением, но из-за неправильной мотивации: «потому что я не могу создать собственный атрибут для запуска в случае нулевых или пустых значений». Это дизайн? И в таком случае, что за волхвы c стоят за RequiredAttribute для работы и запуска других атрибутов?

Чтобы завершить контекст, это мой Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore(o =>
        {
            //o.Filters.Add<ValidateModelAttribute>();
            o.EnableEndpointRouting = false;
        })
        .AddApiExplorer()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .AddJsonFormatters()
        .AddDataAnnotations()
        .AddControllersAsServices();
    }

Без SetCompatibilityVersion это не сработало бы вообще, но наш производственный код уже все равно его имеет.

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

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Извините за подробности, в конце я только хочу узнать, как я могу использовать только [Some] для выполнения всех «некоторых» связанных проверок?

1 Ответ

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

Вместо этого наследуется от RequiredAttribute, поскольку он наследуется от ValidationAttribute

public class SomeAttribute :RequiredAttribute
{
    private const string SOME_REGEX = @"(?i)^\b(xx)\b$";

    private const string SOME_NOT_VALID_ERROR_MESSAGE = "Some is not valid.";
    private const string SOME_REQUIRED_ERROR_MESSAGE = "Some must have a value.";

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var some = Convert.ToString(value);

        if (string.IsNullOrWhiteSpace(some))
            return new ValidationResult(SOME_REQUIRED_ERROR_MESSAGE);

        if (!Regex.Match(some, SOME_REGEX, RegexOptions.IgnoreCase).Success)
            return new ValidationResult(SOME_NOT_VALID_ERROR_MESSAGE);

        return ValidationResult.Success;
    }
}
...