Проверка согласованности между маршрутными и основными данными во время проверки .NET Core

#c# #asp.net-core #validation #.net-core #asp.net-core-webapi

#c# #asp.net-core #проверка #.net-core #asp.net-core-webapi

Вопрос:

Я пытаюсь найти «лучший» способ проверить согласованность между данными в URL-адресе запроса и данными в теле запроса.

Например, допустим, у нас есть эта конечная точка PUT:

 https://host:port/foo/bar/{carId}
 

где я могу передать объект Car как тело:

 class Car {
   string CarId { get; set; }
   string Brand { get; set; }
   int MaxSpeed { get; set; }
   ...
}
 

Теперь в моем контроллере у меня есть что-то вроде этого:

 [HttpPut("foo/bar/{carId}")]
public async Task updateCar([FromRoute] string carId, [FromBody] Car car) {
    ...
}
 

И я хочу быть уверен, что carId в маршруте соответствует CarId свойству в теле.

Каков «лучший» способ добиться этого? Конечно, я мог бы просто проверить с if помощью контроллера в теле, но поскольку это задача проверки (или, по крайней мере, я так думаю), я хотел бы иметь эту логику в моем уровне проверки.


Пока что личные идеи

Хорошо, вопрос исчерпан, здесь я просто изложу некоторые идеи, которые я пробовал или хочу попробовать.


У меня есть пользовательский фильтр действий для проверки проверки, и я пытаюсь поиграть с ним, чтобы посмотреть, смогу ли я что-то там сделать, или добавить другой пользовательский фильтр действий только там, где я хочу проверить согласованность, но это не выглядит многообещающим.

На данный момент я видел, что в фильтре действий у меня есть доступ к параметрам метода контроллера через context.ActionArguments свойство, но я не знаю, как проверить, были ли эти аргументы «связанными аргументами» (а именно имели некоторый [FromXXX] атрибут). Если бы я мог это сделать, возможно, я мог бы проверить, есть ли аргументы с тем же именем (или со свойством с тем же именем), а затем сравнить их значения. Но это кажется очень громоздким и непоследовательным.


Я читал о пользовательских связующих, но я все еще царапаю поверхность (я надеюсь узнать что-то еще в ближайшие несколько часов): могут ли они быть возможным решением?

Комментарии:

1. Лучший способ — не ставить carId в маршрут. Просто из любопытства, зачем вам нужно ставить carId дважды?

2. @Serge Потому что один из них является частью модели автомобиля, а другой — частью маршрута для однозначной идентификации автомобиля. Так что, ИМХО, оба должны присутствовать. Я тоже кое -что читал об этом.

3. Спасибо за ответ и за ссылку. Но эта статья выглядит очень странно для меня. Это было написано человеком, который никогда не участвовал ни в одном серьезном проекте, кроме «Hello world». ИМХО, вам следует забыть об этой статье, это несерьезно.

4. @Serge Спасибо за разъяснение! Итак, как мне это сделать? Должен ли я удалить дублирующуюся информацию из тела или из маршрута? Или я должен просто «перезаписать» информацию в теле с информацией в маршруте? Также можете ли вы указать мне какой-нибудь ресурс для изучения этих дизайнерских материалов? Я также пытался прочитать сообщение MS о дизайне REST API, но, похоже, об этом не говорится.

5. Вы не можете удалить frombody, поскольку вам нужен не только carId, поэтому просто удалите «[FromRoute] string carId» и создайте свой маршрут [Route(«~/ foo / bar»)]. Я не думаю, что вам также нужно Put или Delete, например, если это не ваш проект scholl. Я всегда использую только Get или Post и не вижу никакого смысла добавлять эти слова к route. Не слушайте, если кто-то рассказывает вам о принципах Rest, это годится только для учебника. У реального контроллера может быть около сотни действий, и вы, конечно, не сможете просто использовать 4 метода для реализации этих действий. Ваши действия должны иметь осмысленные имена.

Ответ №1:

Вы должны сделать это так:

 [AttributeUsage(AttributeTargets.Property)]
public class VerifyWithRouteParams : Attribute
{
   public string ParamName
   {
      get
      {
        return paramName;
      }
   }

   private readonly string paramName;

   public VerifyWithRouteParams(string paramName)
   {
       this.paramName = paramName;
   }
}
 

Предположим, у вас есть модель автомобиля, которая передается через тело:

 public class Car
{
    [VerifyWithRouteParams("carId")]
    public string CarId { set; get; }

    public string AnotherParam {set; get;}
}
 

У вас должен быть фильтр по умолчанию:

 public class RouteBodyVerificationActionFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        // do something before the action executes
        if (context.ActionArguments != null amp;amp; context.ActionArguments.Count > 0)
        {
            foreach (var arg in context.ActionArguments)
            {
                if (arg.Value == null) continue;

                bool isThereAnyObjectInArgumentsWithVerificationAttribute = arg.Value
                .GetType()
                .GetProperties()
                .Any(
                        x => x.GetCustomAttributes(typeof(VerifyWithRouteParams), false).Any()
                    );

                if (isThereAnyObjectInArgumentsWithVerificationAttribute)
                {
                    foreach (var prop in arg.Value.GetType().GetProperties())
                    {
                        var verificationAttr = prop.GetCustomAttributes(typeof(VerifyWithRouteParams), false).FirstOrDefault() as VerifyWithRouteParams;
                        if (null == verificationAttr) continue;

                        string routeArgumentName = verificationAttr.ParamName;
                        context.ActionArguments.TryGetValue(routeArgumentName, out var routeArgumentValue);

                        if (null == routeArgumentValue)
                        {
                            context.ModelState.AddModelError("invalid argument value", routeArgumentName);
                        }

                        if (routeArgumentValue?.Equals(prop.GetValue(arg.Value)) != true)
                        {

                            context.ModelState.AddModelError("invalid argument value", routeArgumentName);
                        }
                    }
                }
            }
        }
    }
}
 

Затем вы должны добавить фильтр по умолчанию в автозагрузке:

 services.AddControllers(cfg =>
{
    cfg.Filters.Add<RouteBodyVerificationActionFilter>();
});
 

Затем вы можете проверить это с помощью ошибок проверки модели в контроллере:

 if (!ModelState.IsValid)
{
   return Content("invalid model");
}
 

Комментарии:

1. Большое вам спасибо за этот ответ! Я попробую сделать это сегодня днем. Мой единственный вопрос: я могу использовать этот фильтр только в определенных конечных точках, а не во всех, верно? Мне просто нужно добавить фильтр действий декларативным способом перед конечной точкой, на которой я хочу, чтобы он был активен?

2. Да конечно дорогая