保护来自客户端的ASP.Net Web API

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请求开头:

Method_1Param takes an id parameter from the query
Method_1Param takes an id parameter from the query
Method_1Param从查询中获取id参数

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的文章中找到:

The REST query without an id parameter!
The REST query without an id parameter!
没有id参数的REST查询!
The result of the query without the id parameter (just showing the body)
The result of the query without the id parameter (just showing the body)
没有id参数的查询结果(仅显示正文)
The query m_1param with id=42
The query m_1param with id=42
查询ID为42的m_1param
The result of the query with id = 42
The result of the query with id = 42
ID = 42的查询结果

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 with the Built-in validation attribute [Required]
Now with the Built-in validation attribute [Required]
现在具有内置验证属性[必需]

Now if we send the GET Request m_1paramR without the id parameter we get:

现在,如果我们发送不带id参数的GET Request m_1paramR ,我们将得到:

Image for post

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请求:

Method_3Param takes 3 parameters
Method_3Param takes 3 parameters
Method_3Param采用3个参数

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] 。 让我们通过创建一个请求模型来改善这种情况:

Request Model taking 3 parameters
Request Model taking 3 parameters
请求模型采用3个参数

And make the API simple again:

并再次简化API:

Method_RequestModel just takes a single parameter now
Method_RequestModel just takes a single parameter now
Method_RequestModel现在只需要一个参数

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]属性:

Request Model with Build-in validation (Note: This a different class than the previous one)
Request Model with Build-in validation (Note: This a different class than the previous one)
具有内置验证的请求模型(注意:与上一个类不同)

The API using our updated Request Model:

使用我们更新后的请求模型的API:

Method_RequestModelWithBuildInValidationAttribute using our new Request Model
Method_RequestModelWithBuildInValidationAttribute using our new Request Model
使用我们的新请求模型的Method_RequestModelWithBuildInValidationAttribute

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]属性添加到两者中。 让我们看一下另一种方法,以防万一我们需要简单的内置属性不足的,需要高级跨属性验证的情况:

Request Model with Class-level Validation
Request Model with Class-level Validation
具有类级别验证的请求模型

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:

我们使用新模型如下:

Method_RequestModelWithClassLevelValidation using our new Request Model
Method_RequestModelWithClassLevelValidation using our new Request Model
使用我们的新请求模型的Method_RequestModelWithClassLevelValidation

Upon sending a GET request without FirstName and LastName as follows:

发送不带FirstNameLastName的GET请求时,如下所示:

The REST query with an id but without FirstName and LastName
The REST query with an id but without FirstName and LastName
具有ID但不包含名字和姓氏的REST查询

We get the response:

我们得到了回应:

The result of the query with an id and without FirstName and LastName
The result of the query with an id and without FirstName and LastName
具有id且没有FirstName和LastName的查询结果

And if we send FirstName and forget LastName then we get:

如果我们发送FirstName而忘记LastName那么我们将得到:

The result of the query with an id and FirstName and without LastName
The result of the query with an id and FirstName and without LastName
具有id和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,如下所示:

Extended Request Model with Marital Status and Spouse name
Extended Request Model with Marital Status and Spouse name
具有婚姻状况和配偶姓名的扩展请求模型

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:

在使用扩展请求模型的方法中,我们不仅要确保idFirstNameLastName属性为[Required]而且要确保MaritalStatus有效。 为了检查MaritalStatus的有效性,我们创建了ValidMaritalStatus Custom Validation属性:

ValidMaritalStatus Custom Validation Attribute
ValidMaritalStatus Custom Validation Attribute
ValidMaritalStatus自定义验证属性

We create the Custom Validation Attribute by deriving from the ValidationAttribute and overriding the IsValid method that takes two parameters of type:

我们通过从ValidationAttribute派生并覆盖采用以下两个参数类型的IsValid方法来创建自定义验证属性:

  1. object : This is the property on which the Custom Attribute is used. In our case MaritalStatus

    object :这是使用“定制属性”的属性。 在我们的案例中, MaritalStatus

  2. ValidationContext : This contains the instance of our Request Model RequestWithCustomValidationAttribute where the Custom Attribute is used

    ValidationContext :这包含我们的请求模型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则必须同时设置属性SpouseFirstNameSpouseLastName 。 请注意,由于我们的请求模型中的MaritalStatus属性是枚举类型MaritalStatus因此将进行自动检查以确保任何输入值都可以映射为有效的枚举值-我们免费获得😀。

Let’s see the method using our latest and last Request Model in this article:

让我们在本文中使用最新和最后一个请求模型来查看该方法:

Method_RequestModelWithCustomValidationAttribute using our last Request Model
Method_RequestModelWithCustomValidationAttribute using our last Request Model
使用我们的上一个请求模型的Method_RequestModelWithCustomValidationAttribute

Upon sending a GET request with an invalid MaritalStatus as follows:

发送带有无效MaritalStatus的GET请求时,如下所示:

REST query with maritalstatus = unknown (Invalid value)
REST query with maritalstatus = unknown (Invalid value)
具有maritalstatus =未知的REST查询(无效值)

We get the following response:

我们得到以下回应:

Image for post

And if the MaritalStatus is Married and the REST query does not contain a SpouseLastName as follows:

并且如果MaritalStatusMarried并且REST查询不包含SpouseLastName ,如下所示:

Image for post

We get the following response:

我们得到以下回应:

Image for post

最后一件事… (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)传递结构。 让我们来看看上一个请求模型的样子:

Method_RequestModelWithCustomValidationAttributeB — B stands for Body
Method_RequestModelWithCustomValidationAttributeB — B stands for Body
Method_RequestModelWithCustomValidationAttributeB — B代表正文

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:

我们现在发送的请求必须在请求正文中包含输入信息:

REST Query using input in Request Body
REST Query using input in Request Body
使用请求正文中的输入进行REST查询

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:

响应符合预期:

Response of the REST Request using a Body to input information
Response of the REST Request using a Body to input information
REST请求使用正文输入信息的响应

All code used in this article can be found in my GitHub repository.

可以在我的GitHub存储库中找到本文中使用的所有代码。

I hope that my article helps others.

我希望我的文章对其他人有帮助。

翻译自: https://medium.com/the-innovation/protect-your-asp-net-web-api-from-the-client-7c5c68fab9f4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值