A part of creating a production-level Web API is to ensure that no “garbage” comes in. But that is definitely not all. Ensuring that the API Client is aware of the correct way to use the API is also vital.
创建生产级Web API 的一部分是确保没有“垃圾”的用武之地。但是,这绝对不是全部。 确保API客户端知道使用API的正确方法也至关重要。
In this article, I am going to describe 3 ways the ASP.Net Request Model Validation helps us make our ASP.Net Web API safe and at the same time give helpful feedback to the user on how to use our API properly. Starting with a simple API I shall expand it step by step and show the code and the output of calling the API.
在本文中,我将描述ASP.Net请求模型验证帮助我们使ASP.Net Web API安全的3种方式,同时向用户提供有关如何正确使用API的有用反馈。 从一个简单的API开始,我将逐步扩展它,并显示调用API的代码和输出。
简单的API,无需任何验证 (Simple API without any Validation)
The ASP.Net Core Web API Controller starts with the m_1param
GET request:
ASP.Net Core Web API控制器以m_1param
GET请求开头:
Instead of using the tool Postman, we are going to use a VSCode extension called Rest Client that allows us to send REST API queries directly from VSCode which is awesome! It is among the other useful extensions that can be found in the following article by Simon Holdorf:
我们将使用一个称为Rest Client的VSCode扩展,而不是使用Postman工具,该扩展允许我们直接从VSCode发送REST API查询,这真是太棒了! 这是其他有用的扩展,可以在下面的Simon Holdorf的文章中找到:
Well, this is definitely not good. We should not allow the user to send an HTTP GET request without an id. Let’s change that.
好吧,这绝对不好。 我们不应该允许用户发送不带ID的HTTP GET请求。 让我们改变它。
1.使用内置属性进行请求验证 (1. Request Validation using Built-in attributes)
Now if we send the GET Request m_1paramR
without the id parameter we get:
现在,如果我们发送不带id参数的GET Request m_1paramR
,我们将得到:
Not only does the [Required]
Built-in validation attribute protects our API from invalid input, it also tells the user exactly what is expected. There are many more attributes to be found under:
[Required]
内置验证属性不仅可以保护我们的API免受无效输入的影响,还可以告诉用户确切的期望值。 在下面可以找到更多属性:
Here is another GET request that requires more than a single parameter:
这是另一个需要多个参数的GET请求:
Well now we have three parameters and we really don’t want to put [Required]
in front of each. Let’s improve the situation by creating a Request Model:
好了,现在我们有了三个参数,我们真的不想在每个参数前面都添加[Required]
。 让我们通过创建一个请求模型来改善这种情况:
And make the API simple again:
并再次简化API:
Oops! We forgot to set the [Required]
attribute to the id
parameter, but wait - we just modelled in the id
parameter! Luckily we can set the [Required]
attribute inside our Request Model:
糟糕! 我们忘记将[Required]
属性设置为id
参数,但是等等-我们只是在id
参数中建模! 幸运的是,我们可以在请求模型中设置[Required]
属性:
The API using our updated Request Model:
使用我们更新后的请求模型的API:
2.使用类级别验证进行请求验证 (2. Request Validation using Class-Level Validation)
Suppose for our Request Model above we would like to ensure that a user cannot input a FirstName
without a LastName
or vice versa. Of course, the easiest way to do that would be to add the [Required]
attribute to both. Let’s see another way to do it, just in case we ever need it for situations that require a high level of cross-property validation where simple built-in attributes are insufficient:
假设我们的请求模型上面我们想确保用户无法输入一个FirstName
没有LastName
,反之亦然。 当然,最简单的方法是将[Required]
属性添加到两者中。 让我们看一下另一种方法,以防万一我们需要简单的内置属性不足的,需要高级跨属性验证的情况:
We can enhance our Request Model using a Class-level Validation i.e. deriving from the IValidatableObject
Interface and implementing the Validate(ValidationContext validationContext)
method.
我们可以使用类级别的验证来增强我们的请求模型,即从IValidatableObject
接口派生并实现Validate(ValidationContext validationContext)
方法。
Inside the Validate
method, we can use any of the Properties in our Request Model. We use the switch expression
introduced in C# 8.0 and return a tuple with a valid flag and a corresponding ValidationResult
. The important thing to note here is that we have to yield return
validation result only on failure.
在Validate
方法内部,我们可以使用请求模型中的任何属性。 我们使用C#8.0中引入的switch expression
,并返回带有有效标志和对应的ValidationResult
的元组。 这里要注意的重要一点是,我们必须仅在失败时才 yield return
验证结果。
We use the new model as follows:
我们使用新模型如下:
Upon sending a GET request without FirstName
and LastName
as follows:
发送不带FirstName
和LastName
的GET请求时,如下所示:
We get the response:
我们得到了回应:
And if we send FirstName
and forget LastName
then we get:
如果我们发送FirstName
而忘记LastName
那么我们将得到:
3.使用自定义属性进行请求验证 (3. Request Validation using Custom Attributes)
Where there are Built-in Attributes there must be Custom Attributes.
如果有内置属性,则必须有自定义属性。
In this scenario we use an extended RequestModel as follows:
在这种情况下,我们使用扩展的RequestModel,如下所示:
In the method where we use the extended Request Model, we not only want to make sure that the id
, FirstName
and the LastName
properties are [Required]
but also that the MaritalStatus
is valid. To check the validity of the MaritalStatus
we created theValidMaritalStatus
Custom Validation attribute:
在使用扩展请求模型的方法中,我们不仅要确保id
, FirstName
和LastName
属性为[Required]
而且要确保MaritalStatus
有效。 为了检查MaritalStatus
的有效性,我们创建了ValidMaritalStatus
Custom Validation属性:
We create the Custom Validation Attribute by deriving from the ValidationAttribute
and overriding the IsValid
method that takes two parameters of type:
我们通过从ValidationAttribute
派生并覆盖采用以下两个参数类型的IsValid
方法来创建自定义验证属性:
object
: This is the property on which the Custom Attribute is used. In our caseMaritalStatus
object
:这是使用“定制属性”的属性。 在我们的案例中,MaritalStatus
ValidationContext
: This contains the instance of our Request ModelRequestWithCustomValidationAttribute
where the Custom Attribute is usedValidationContext
:这包含我们的请求模型RequestWithCustomValidationAttribute
的实例,其中使用了自定义属性
Inside the IsValid
method, we check that if the value of the MaritalStatus
property is Married
then both the properties - SpouseFirstName
and SpouseLastName
must be set. Note that since the MaritalStatus
property in our Request Model is of the enumeration type MaritalStatus
there is an automatic check to ensure that any input value can be mapped to a valid enumeration value - we get that for free 😀.
在IsValid
方法内部,我们检查如果MaritalStatus
属性的值是否为Married
则必须同时设置属性SpouseFirstName
和SpouseLastName
。 请注意,由于我们的请求模型中的MaritalStatus
属性是枚举类型MaritalStatus
因此将进行自动检查以确保任何输入值都可以映射为有效的枚举值-我们免费获得😀。
Let’s see the method using our latest and last Request Model in this article:
让我们在本文中使用最新和最后一个请求模型来查看该方法:
Upon sending a GET request with an invalid MaritalStatus
as follows:
发送带有无效MaritalStatus
的GET请求时,如下所示:
We get the following response:
我们得到以下回应:
And if the MaritalStatus
is Married
and the REST query does not contain a SpouseLastName
as follows:
并且如果MaritalStatus
已Married
并且REST查询不包含SpouseLastName
,如下所示:
We get the following response:
我们得到以下回应:
最后一件事… (One Last Thing…)
So far we have been using REST Get requests that take parameters. What if we are in a situation where a Get request requires to input a complex structure? The only way to do that would be to use the query body to pass in the structure e.g. in a JSON format. Let’s see how that would look like for our last Request Model:
到目前为止,我们一直在使用带有参数的REST Get请求。 如果我们在Get请求需要输入复杂结构的情况下该怎么办? 唯一的方法是使用查询主体以JSON格式(例如JSON)传递结构。 让我们来看看上一个请求模型的样子:
As we can see the REST Get request input is now being taken from the request body [FromBody]
and not from the query.
如我们所见,现在从请求正文[FromBody]
而不是从查询中获取REST Get请求输入。
The request we now send must include the input information in the request body:
我们现在发送的请求必须在请求正文中包含输入信息:
Note that the value of maritalstatus
is not a string but an enumeration value. If we wanted to keep using a string then we would need to use some sort of a JSON serializer e.g. Newtonsoft.JSON.
请注意, maritalstatus
的值不是字符串,而是枚举值。 如果我们想继续使用字符串,则需要使用某种JSON序列化程序,例如Newtonsoft.JSON。
The response is as expected:
响应符合预期:
翻译自: https://medium.com/the-innovation/protect-your-asp-net-web-api-from-the-client-7c5c68fab9f4