Я обнаружил, что метод 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] для выполнения всех «некоторых» связанных проверок?