Execute CRUD Operations for Non-Persistent Objects(对非持久对象执行增删改查操作 )
This topic describes how to implement Create, Read, Update, and Delete operations for Non-Persistent Objects.
本主题介绍如何为非持久性对象实现创建、读取、更新和删除操作。
Note
This option of our Web API Service ships as part of the DevExpress Universal Subscription.
我们的Web API服务的此选项作为DevExpress通用订阅的一部分提供。
You may need Non-Persistent Objects if you develop a project with the help of the XAF. Use XAF documentation to familiarize yourself with the concept, if necessary. Note that the Web API Service requires you to implement certain steps in a different manner. The sections below highlight important differences.
如果您在XAF的帮助下开发项目,您可能需要非持久性对象。如有必要,请使用XAF留档来熟悉该概念。请注意,Web API服务要求您以不同的方式实现某些步骤。下面的部分强调了重要的区别。
Use the steps below as the implementation guide:
使用以下步骤作为实施指南:
- Declare a Non-Persistent Object(为新类型创建端点)
- Create an Endpoint for the New Type(为新类型创建端点)
- Enable the Object Space Provider for Non-Persistent Types(为非持久类型启用对象空间提供程序)
- Create a Singleton Service for Data Access(为数据访问创建单例服务)
- Implement an Object Space Customizer Service(实现对象空间自定义器服务)
Declare a Non-Persistent Object(声明一个非持久性对象)
The following code implements a new CustomNonPersistentObject type:
下面的代码实现了一个新的CustomNonPersistentObject类型:
- The Name property stores string values.
Name属性存储字符串值。 - The DomainComponentAttribute marks the object as non-persistent.
DomainComponentAttribute将对象标记为非持久对象。 - The base class - NonPersistentBaseObject - implements the key field property.
基类-NonPersistentBaseObject-实现key field属性。
C#
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;
[DomainComponent]
public class CustomNonPersistentObject : NonPersistentBaseObject {
public string Name { get; set; }
}
Create an Endpoint for the New Type(为新类型创建端点)
The code in this section creates an endpoint for CustomNonPersistentObject.
本节中的代码为CustomNonPersistentObject创建端点。
In Startup.cs, add or find the AddXafWebApi() method call and use the BusinessObject() method to create an endpoint.
在Startup. cs中,添加或查找AddXafWebApi()方法调用并使用BusinessObject()方法创建端点。
C#
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// ...
services.AddXafWebApi(builder => {
builder.ConfigureOptions(options => {
options.BusinessObject<CustomNonPersistentObject>();
});
}, Configuration);
}
// ...
}
You can review the following topic to learn about endpoint creation: Add CRUD Endpoints and Consume the Web API.
您可以查看以下主题以了解端点创建:添加CRUD端点并使用Web API。
Enable the Non-Persistent Object Space Provider(启用非持久性对象空间提供程序)
The Solution Wizard automatically adds registration code for the Non-Persistent Object Space Provider. Make sure that the following code is present in your Web API Service project’s Startup.cs file. Add it if needed (in case you didn’t use the Solution Wizard).
解决方案向导自动为非持久性对象空间提供程序添加注册代码。确保以下代码存在于您的Web API服务项目的Startup. cs文件中。如果需要,请添加它(以防您没有使用解决方案向导)。
File: MySolution.WebApi\Startup.cs (standalone), MySolution.Blazor.Server\Startup.cs (integrated)
C#
builder.ObjectSpaceProviders
//...
.AddNonPersistent();
Create a Singleton Service for Data Access(为数据访问创建单例服务)
The following class will enable data storage and access. The ObjectCache property - a Dictionary - holds the object list. The constructor initializes this list with a few objects.
以下类将启用数据存储和访问。ObjectCache属性-字典-保存对象列表。构造函数使用几个对象初始化此列表。
Note
If you are familiar with Non-Persistent Object management in XAF applications, this step is new for you. You need to implement a storage in this manner because our Web API Service doesn’t work with the ValueManager.
如果您熟悉XAF应用程序中的非持久性对象管理,则此步骤对您来说是新的。您需要以这种方式实现存储,因为我们的Web API服务不适用于ValueManager。
C#
using DevExpress.ExpressApp;
public class NonPersistentObjectStorageService {
public Dictionary<Guid, NonPersistentBaseObject>
ObjectCache { get; } = new();
public NonPersistentObjectStorageService() {
CreateObject<CustomNonPersistentObject>("A");
CreateObject<CustomNonPersistentObject>("B");
CreateObject<CustomNonPersistentObject>("C");
}
private NonPersistentBaseObject CreateObject<T>(string value) where T : NonPersistentBaseObject, new() {
T result = new T();
if(result is CustomNonPersistentObject custom) {
custom.Name = value;
}
ObjectCache.Add(result.Oid, result);
return result;
}
}
Register the class as a singleton service. Add the following line to Startup.cs:
将类注册为单例服务。将以下行添加到Startup. cs:
C#
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<NonPersistentObjectStorageService>();
//...
}
Tip
You can register this service as a scoped service. The lifetime of such a service depends on whether you host the Web API Service as part of a Blazor Server project or as a standalone ASP.NET Core project.
您可以将此服务注册为作用域服务。此类服务的生命周期取决于您是将Web API服务作为Blazor Server项目的一部分托管还是作为独立的ASP.NETCore项目托管。
- Integrated (Single Project - Blazor Server Application)
The service’s visibility scope matches the lifetime of the Blazor app’s user circuit. The application typically releases a circuit if a user closes the browser tab or window.
该服务的可见性范围与Blazor应用程序的用户电路的生命周期相匹配。如果用户关闭浏览器选项卡或窗口,应用程序通常会释放电路。
- Standalone (Separate Projects)
The service’s visibility scope matches the lifetime of a single request.
服务的可见性范围与单个请求的生命周期相匹配。The XAF Solution Wizard allows you to select the integration style on the Choose Options page.
XAF解决方案向导允许您在选择选项页面上选择集成样式。
These controls appear if you selected both Web and Service on the Choose Target Platforms page.
如果您在选择目标平台页面上同时选择了Web和服务,则会出现这些控件。
Implement an Object Space Customizer Service(实现对象空间自定义器服务)
In this step, you implement an Object Space Customizer. This service holds a reference to the data storage you defined in the previous step. It can also access the NonPersistentObjectSpace to handle the following events:
在此步骤中,您将实现一个对象空间自定义器。此服务保存对您在上一步中定义的数据存储的引用。它还可以访问NonPersistentObjectSpace以处理以下事件:
- ObjectGetting - obtains all objects from the storage.(从存储中获取所有对象。)
- ObjectByKeyGetting - obtains an object by its key value.(通过其键值获取对象。)
- Committing - updates the storage in response to CREATE or DELETE operations.(更新存储以响应CREATE或DELETE操作。)
Note
If you are familiar with Non-Persistent Object management in XAF applications, this step is new for you. XAF allows you to handle the Application.ObjectSpaceCreated event. You cannot access this event in Web API Service applications, so you need to implement an IObjectSpaceCustomizer instead.
如果您熟悉XAF应用程序中的非持久性对象管理,则此步骤对您来说是新的。XAF允许您处理Application. ObjectSpaceCreated事件。您无法在Web API Service应用程序中访问此事件,因此需要实现一个IObjectSpaceCustomizer。
C#
using System.ComponentModel;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Core;
//Scoped service
public class NonPersistentObjectSpaceCustomizer : IObjectSpaceCustomizer {
private readonly IObjectSpaceProviderService objectSpaceProvider;
private readonly IObjectSpaceCustomizerService objectSpaceCustomizerService;
private Dictionary<Guid, NonPersistentBaseObject> ObjectCache { get; }
public NonPersistentObjectSpaceCustomizer(
NonPersistentObjectStorageService nonPersistentStorage,
IObjectSpaceProviderService objectSpaceProvider,
IObjectSpaceCustomizerService objectSpaceCustomizerService) {
ObjectCache = nonPersistentStorage.ObjectCache;
this.objectSpaceProvider = objectSpaceProvider;
this.objectSpaceCustomizerService = objectSpaceCustomizerService;
}
public void OnObjectSpaceCreated(IObjectSpace objectSpace) {
if(objectSpace is NonPersistentObjectSpace nonPersistentObjectSpace) {
nonPersistentObjectSpace.ObjectsGetting += NonPersistentObjectSpace_ObjectsGetting;
nonPersistentObjectSpace.ObjectByKeyGetting += NonPersistentObjectSpace_ObjectByKeyGetting;
nonPersistentObjectSpace.Committing += NonPersistentObjectSpace_Committing;
nonPersistentObjectSpace.PopulateAdditionalObjectSpaces(objectSpaceProvider, objectSpaceCustomizerService);
}
}
private void NonPersistentObjectSpace_ObjectsGetting(object? sender, ObjectsGettingEventArgs e) {
if(typeof(NonPersistentBaseObject).IsAssignableFrom(e.ObjectType)) {
ArgumentNullException.ThrowIfNull(sender);
IObjectSpace objectSpace = (IObjectSpace)sender;
var objects = new BindingList<NonPersistentBaseObject>() {
AllowNew = true,
AllowEdit = true,
AllowRemove = true
};
foreach(var obj in ObjectCache.Values) {
if(e.ObjectType.IsAssignableFrom(obj.GetType()))
objects.Add(objectSpace.GetObject(obj));
}
e.Objects = objects;
}
}
private void NonPersistentObjectSpace_ObjectByKeyGetting(object? sender, ObjectByKeyGettingEventArgs e) {
ArgumentNullException.ThrowIfNull(sender);
IObjectSpace objectSpace = (IObjectSpace)sender;
if(typeof(NonPersistentBaseObject).IsAssignableFrom(e.ObjectType)) {
NonPersistentBaseObject? obj;
if(ObjectCache.TryGetValue((Guid)e.Key, out obj)) {
e.Object = objectSpace.GetObject(obj);
}
}
}
private void NonPersistentObjectSpace_Committing(object? sender, CancelEventArgs e) {
ArgumentNullException.ThrowIfNull(sender);
IObjectSpace objectSpace = (IObjectSpace)sender;
foreach(var obj in objectSpace.ModifiedObjects) {
if(obj is NonPersistentBaseObject nonPersistentObject) {
if(objectSpace.IsNewObject(obj)) {
ObjectCache.Add(nonPersistentObject.Oid, nonPersistentObject);
}
else if(objectSpace.IsDeletedObject(obj)) {
ObjectCache.Remove(nonPersistentObject.Oid);
}
}
}
}
}
Register the service. Add the following TryAddEnumerable extension method call to Startup.cs.
注册服务。将以下TryAddEnumable扩展方法调用添加到Startup. cs。
C#
public void ConfigureServices(IServiceCollection services) {
//...
services.TryAddEnumerable(ServiceDescriptor.Scoped<IObjectSpaceCustomizer,
NonPersistentObjectSpaceCustomizer>());
//...
}
Unit Tests(单元测试)
Follow the steps below to add unit tests for the code that queries Non-Persistent Objects via the Web API Service.
按照以下步骤为通过Web API服务查询非持久性对象的代码添加单元测试。
1.If your solution does not have 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 test project:
将以下测试添加到测试项目中:
C#
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
public class CRUDNonPersistentTests : IClassFixture<WebApplicationFactory<MySolution.Blazor.Server.Startup>> {
readonly string url = $"/api/odata/{typeof(CustomNonPersistentObject).Name}";
HttpClient httpClient;
public CRUDNonPersistentTests(WebApplicationFactory<Blazor.Server.Startup> webApplicationFactory) {
httpClient = webApplicationFactory.CreateClient();
}
[Fact]
public async System.Threading.Tasks.Task Create_Read_Delete() {
string tokenString = await GetUserTokenAsync("Sam", "", "/api/Authentication/Authenticate");
var authorizationToken = new AuthenticationHeaderValue("Bearer", tokenString);
string content = $"{{\"{nameof(CustomNonPersistentObject.Name)}\":'Test Data'}}";
var 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 newNonPersistentObject = jsonResult.Deserialize(typeof(CustomNonPersistentObject)) as CustomNonPersistentObject;
ArgumentNullException.ThrowIfNull(newNonPersistentObject);
var objKey = newNonPersistentObject.Oid;
try {
Assert.Equal("Test Data", newNonPersistentObject.Name);
//Get by key
var getHttpRequest = new HttpRequestMessage(HttpMethod.Get, $"{url}/{objKey}");
getHttpRequest.Headers.Authorization = authorizationToken;
var getResponce = await httpClient.SendAsync(getHttpRequest);
Assert.Equal(HttpStatusCode.OK, getResponce.StatusCode);
jsonResult = await getResponce.Content.ReadFromJsonAsync<JsonElement>();
var loadedObj = jsonResult.Deserialize(typeof(CustomNonPersistentObject)) as CustomNonPersistentObject;
ArgumentNullException.ThrowIfNull(loadedObj);
Assert.Equal("Test Data", loadedObj.Name);
}
finally {
//Delete the test object
var deleteHttpRequest = new HttpRequestMessage(HttpMethod.Delete, $"{url}/{objKey}");
deleteHttpRequest.Headers.Authorization = authorizationToken;
var deleteResponce = await httpClient.SendAsync(deleteHttpRequest);
Assert.Equal(HttpStatusCode.OK, deleteResponce.StatusCode);
}
}
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;
}
}