【问题标题】:Check coherence between route and body data during .NET Core validation在 .NET Core 验证期间检查路由和正文数据之间的一致性
【发布时间】:2022-01-14 14:46:00
【问题描述】:

我正在尝试寻找“最佳”方式来检查请求 URL 中的数据与请求正文中的数据之间的一致性。

例如,假设我们有这个 PUT 端点:

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

我可以将 Car 对象作为 body 传递:

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 属性匹配。

实现这一目标的“最佳”方法是什么?当然,我可以简单地在 body 控制器中使用 if 进行检查,但由于这是一个验证任务(或者至少我认为是这样),我希望在我的验证层中有这个逻辑。


到目前为止的个人想法

好了,问题结束了,这里我就放一些我尝试过或者想尝试的想法。


我有一个自定义操作过滤器来检查验证,我正在尝试使用它来看看我是否可以在那里做某事,或者只在我需要的地方添加另一个自定义操作过滤器想检查连贯性,但这看起来不太乐观。

目前我看到在动作过滤器中我可以通过context.ActionArguments 属性访问控制器方法参数,但我不知道如何检查这些参数是否是“绑定参数”(即有一些[FromXXX] 属性)。如果我能做到这一点,也许我可以检查是否有同名的参数(或同名的属性),然后比较它们的值。但这似乎非常麻烦且不一致。


我已经阅读了有关自定义活页夹的内容,但我仍处于摸索阶段(我希望在接下来的几个小时内能学到更多东西):它们是一种可能的解决方案吗?

【问题讨论】:

  • 最好的办法是不要把carId放在一个路由里。只是因为好奇,为什么需要两次 put carId?
  • @Serge 因为一个是汽车模型的一部分,一个是唯一识别汽车的路线的一部分。所以恕我直言,两者都应该在场。我也读过something 关于这个。
  • 感谢您的回答和链接。但是这篇文章对我来说看起来很奇怪。它是一个人写的,除了“Hello world”之外,他从未参与过任何严肃的项目。恕我直言,您应该忘记这篇文章,这并不严重。
  • @Serge 感谢您的澄清!那么我该怎么做呢?我应该从正文还是从路线中删除重复的信息?还是我应该简单地用路线中的信息“覆盖”正文中的信息?你也可以给我一些资源来学习这些设计的东西吗?我也尝试阅读MS post关于 REST API 设计,但似乎没有谈论这个。
  • 您不能从body中删除,因为您不仅需要carId,因此只需删除“[FromRoute] string carId”并制作您的路线[Route("~/foo/bar")]。例如,如果不是您的 scholl 项目,我认为您也不需要 Put 或 Delete 。我总是只使用 Get 或 Post 并且认为将这些词放到路由中也没有任何意义。如果有人告诉你休息原则,不要听,这只是一本教科书。真正的控制器可以有大约一百个动作,你肯定不能只使用 4 种方法来实现这些动作。你的动作应该有有意义的名字。

标签: c# asp.net-core validation .net-core asp.net-core-webapi


【解决方案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 && 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");
}

【讨论】:

  • 非常感谢您的回答!我今天下午试试这个。我唯一的问题是:我只能在特定端点而不是全部使用这个过滤器,对吗?我只需在要激活它的端点之前以声明方式添加操作过滤器?
  • 是的,亲爱的
猜你喜欢
  • 2020-04-29
  • 1970-01-01
  • 2013-05-09
  • 2023-01-08
  • 2020-05-18
  • 2019-08-17
  • 2014-02-21
  • 1970-01-01
  • 2019-01-28
相关资源
最近更新 更多