Validate Data Sent to Web API Endpoints(验证发送到 Web API 端点的数据 )
This topic shows how you can validate user input if you use DevExpress Web API Service for data access.
本主题展示了如果您使用DevExpress Web API Service进行数据访问,如何验证用户输入。
The following technologies enable data validation:
以下技术支持数据验证:
- XPO | EF Core
These ORM tools allow you to define your Business Model together with validation attributes.
这些ORM工具允许您定义业务模型以及验证属性。
- XAF Validation Module(XAF验证模块)
Allows you to apply 10+ predefined rules or any number of custom validation rules to your data objects. Enforces those rules in XAF applications.
允许您对数据对象应用10多个预设规则或任意数量的自定义验证规则。在XAF应用程序中强制执行这些规则。
Even if you don’t use predefined validation attributes in your data model, you can still use DevExpress Web API Service to validate user input. You can add custom data validation logic if you extend the basic implementation described in this article.
即使您没有在数据模型中使用预定义的验证属性,您仍然可以使用DevExpress Web API Service来验证用户输入。如果您扩展了本文中描述的基本实现,您可以添加自定义数据验证逻辑。
Note
This option of our Web API Service ships as part of the DevExpress Universal Subscription.
我们的Web API服务的此选项作为DevExpress通用订阅的一部分提供。
Validation API Availability(验证API可用性)
If you use DevExpress Web API Service endpoints to manage data and need to enable validation, use a specially designed IValidator service available in the following namespace: DevExpress.Persistent.Validation.
如果您使用DevExpress Web API服务端点来管理数据并且需要启用验证,请使用以下命名空间中提供的专门设计的IValidator服务:DevExpress. Persis.Validation。
You can use the following methods to enable the service.
您可以使用以下方法来启用该服务。
Enable the Validation Module in the Solution Wizard(在解决方案向导中启用验证模块)
If you create your Backend Web API project with the help of the Solution Wizard, a dedicated wizard screen allows you to enable the Validation module. For additional information, see Create a Standalone Web API Application.
如果您在解决方案向导的帮助下创建后端Web API项目,则专用向导屏幕允许您启用验证模块。有关其他信息,请参阅创建独立Web API应用程序。
Add the Validation Module to a Standalone Web API or XAF Blazor Application(将验证模块添加到独立的Web API或XAF Blazor应用程序)
Install the DevExpress.ExpressApp.Validation.Blazor NuGet package.
安装DevExpress. ExpressApp.Validation.Blazor NuGet包。
Register the Validation module and required services in Startup.cs. Use the Web API or XAF Application builder:
在Startup. cs中注册验证模块和所需的服务。使用Web API或XAF应用程序构建器:
File: MySolution.WebApi\Startup.cs (MySolution.Blazor.Server\Startup.cs)
C#
(Standalone WEB API Service) C# (XAF Application Builder) services.AddXafWebApi(builder => {
//..
builder.Modules
.AddValidation(options => {
//...
})
//...
}, Configuration);
services.AddXaf(Configuration, builder => {
//..
builder.Modules
.AddValidation(options => {
//...
})
//...
}
Validation Does Not Run Automatically(验证不会自动运行)
CRUD endpoints don’t initiate data validation: you need to add that functionality. One reason for this behavior is performance optimization. Another reason has to do with current endpoint limitations. For example, we do not yet support requests that create a master object with their children at the same time. On the other hand, a validation rule may not let you create a master object without certain children.
CRUD端点不会启动数据验证:您需要添加该功能。这种行为的一个原因是性能优化。另一个原因与当前端点限制有关。例如,我们还不支持同时创建带有其子对象的主对象的请求。另一方面,验证规则可能不允许您在没有某些子对象的情况下创建主对象。
Basic Data Validation Implementation(基本数据验证实现)
In a most basic scenario, you access your data using CRUD endpoints. When a user changes data, you enforce specified validation attributes in your data model.
在最基本的场景中,您使用CRUD端点访问数据。当用户更改数据时,您将在数据模型中强制执行指定的验证属性。
The following example shows how you can enable such functionality. The code implements a custom IDataService. This service runs validation before it commits an object space (IObjectSpace).
以下示例显示了如何启用此类功能。该代码实现了自定义IDataService。此服务在提交对象空间(IObjectSpace)之前运行验证。
For details about custom data services, please see the following article: Execute Custom Operations on Endpoint Requests.
有关自定义数据服务的详细信息,请参阅以下文章:在端点请求上执行自定义操作。
C#
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Core;
using DevExpress.ExpressApp.DC;
using DevExpress.ExpressApp.WebApi.Services;
using DevExpress.Persistent.Validation;
// ...
public class CustomDataService : DataService {
readonly IValidator validator;
public CustomDataService(IObjectSpaceFactory objectSpaceFactory,
ITypesInfo typesInfo, IValidator validator)
: base(objectSpaceFactory, typesInfo) {
this.validator = validator;
}
protected override IObjectSpace CreateObjectSpace(Type objectType) {
IObjectSpace objectSpace = base.CreateObjectSpace(objectType);
objectSpace.Committing += ObjectSpace_Committing;
return objectSpace;
}
private void ObjectSpace_Committing(object? sender,
System.ComponentModel.CancelEventArgs e) {
IObjectSpace os = (IObjectSpace)sender!;
var validationResult = validator.RuleSet.ValidateAllTargets(
os, os.ModifiedObjects, DefaultContexts.Save
);
if(validationResult.ValidationOutcome == ValidationOutcome.Error) {
throw new ValidationException(validationResult);
}
}
}
Configure the Request Locale(配置请求区域设置)
You can use the HttpRequestMessage API to change the request locale. The invalid validation results you obtain from the service will use the locale you specified.
您可以使用HttpRequestMessage API更改请求区域设置。您从服务获得的无效验证结果将使用您指定的区域设置。
C#
// ...
httpClient.DefaultRequestHeaders.Add("Accept-Language", "de-DE");
// ...
Add Unit Tests(添加单元测试)
Test Scenario(测试场景)
This example checks validation rules for XAF’s standard ApplicationUser class. XAF declares this class in all new projects that use the Security feature.
此示例检查XAF的标准Application ationUser类的验证规则。XAF在所有使用安全功能的新项目中声明此类。
The ApplicationUser class applies a RuleRequiredField attribute to the UserName property. In other words, you cannot create a new user with an empty name.
Application ationUser类将Rule必需字段属性应用于UserName属性。换句话说,您不能使用空名称创建新用户。
The test tries to create an ApplicationUser object with an empty UserName property and receives a validation error.
该测试尝试创建一个具有空UserName属性的Application ationUser对象并接收验证错误。
This example uses the IDataService implementation demonstrated above. You can find the same test included in our MainDemo.EFCore demo application.
此示例使用上面演示的IDataService实现。您可以在我们的MainDemo. EFCore演示应用程序中找到相同的测试。
Step-by-Step Instructions(分步说明)
Follow the steps below to add a test to your solution:
按照以下步骤向您的解决方案添加测试:
1.If your solution does not include a testing project, add a new xUnit test project.
如果您的解决方案不包括测试项目,请添加一个新的xUnit测试项目。
2.Add a reference to the {SolutionName}.Blazor.Server project.
添加对{SolutionName}的引用。Blazor. Server项目。
3.Add the Microsoft.AspNetCore.Mvc.Testing package reference.
添加Microsoft. AspNetCore.Mvc.测试包参考。
4.Add the following test to the xUnit project.
将以下测试添加到xUnit项目中。
C#
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using MySolution.Module.BusinessObjects;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
public class CustomValidationTests_out : IClassFixture<WebApplicationFactory<MySolution.Blazor.Server.Startup>> {
HttpClient httpClient;
public CustomValidationTests_out(WebApplicationFactory<MySolution.Blazor.Server.Startup> webApplicationFactory) {
httpClient = webApplicationFactory.CreateClient();
}
[Fact]
public async System.Threading.Tasks.Task CreateApplicationUser_ValidateUserNameIsNotEmpty() {
string tokenString = await GetUserTokenAsync("Admin", "", "/api/Authentication/Authenticate");
var authorizationToken = new AuthenticationHeaderValue("Bearer", tokenString);
string url = $"/api/odata/{typeof(ApplicationUser).Name}";
string content = $"{{\"{nameof(ApplicationUser.ChangePasswordOnFirstLogon)}\":true}}";
var httpRequest = new HttpRequestMessage(HttpMethod.Post, url);
httpRequest.Content = new StringContent(content, Encoding.UTF8, "application/json");
httpRequest.Headers.Authorization = authorizationToken;
var badResponce = await httpClient.SendAsync(httpRequest);
Assert.False(badResponce.IsSuccessStatusCode);
string expectedErrorMessage =
$"Bad Request : Data Validation Error: Please review and correct the data validation error(s) listed below to proceed.{Environment.NewLine}" +
$" - The user name must not be empty";
string actualErrorMessage;
using(var stream = await badResponce.Content.ReadAsStreamAsync()) {
using(StreamReader reader = new StreamReader(stream)) {
actualErrorMessage = badResponce.ReasonPhrase + " : " + reader.ReadToEnd();
}
}
Assert.Equal(expectedErrorMessage, actualErrorMessage);
Assert.Equal(HttpStatusCode.BadRequest, badResponce.StatusCode);
//Correct request content
content = $"{{\"{nameof(ApplicationUser.UserName)}\":\"TestUserName\",\"{nameof(ApplicationUser.ChangePasswordOnFirstLogon)}\":true}}";
httpRequest = new HttpRequestMessage(HttpMethod.Post, url);
httpRequest.Content = new StringContent(content, Encoding.UTF8, "application/json");
httpRequest.Headers.Authorization = authorizationToken;
var responce = await httpClient.SendAsync(httpRequest);
Assert.Equal(HttpStatusCode.Created, responce.StatusCode);
var jsonResult = await responce.Content.ReadFromJsonAsync<JsonElement>();
var newUser = jsonResult.Deserialize(typeof(ApplicationUser)) as ApplicationUser;
ArgumentNullException.ThrowIfNull(newUser);
try {
Assert.Equal("TestUserName", newUser.UserName);
Assert.True(newUser.ChangePasswordOnFirstLogon);
}
finally {
//Delete a new user
httpRequest = new HttpRequestMessage(HttpMethod.Delete, $"/api/odata/{typeof(ApplicationUser).Name}/{newUser.ID}");
httpRequest.Headers.Authorization = authorizationToken;
await httpClient.SendAsync(httpRequest);
}
}
async Task<string> GetUserTokenAsync(string userName, string password, string requestPath) {
var request = new HttpRequestMessage(HttpMethod.Post, requestPath);
request.Content = new StringContent(
$"{{ \"userName\": \"{userName}\", \"password\": \"{password}\" }}", Encoding.UTF8, "application/json");
var httpResponse = await httpClient.SendAsync(request);
if(!httpResponse.IsSuccessStatusCode) {
throw new UnauthorizedAccessException($"Authorization request failed! Code {(int)httpResponse.StatusCode}, '{httpResponse.ReasonPhrase}'");
}
var tokenString = await httpResponse.Content.ReadAsStringAsync();
return tokenString;
}
}