Skip to main content
All docs
V24.1
.NET 6.0+

Execute CRUD Operations for Non-Persistent Objects

  • 5 minutes to read

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.

You may need Non-Persistent Objects if you develop a project with the help of XAF. See our XAF documentation to familiarize yourself with this concept, if necessary. To implement CRUD operations for non-persistent objects in the Web API Service, follow the steps that are described in the following topic: How to: Perform CRUD Operations with Non-Persistent Objects.

Use the steps below as an 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:

  • The Name property stores string values.
  • The DomainComponentAttribute marks the object as non-persistent.
  • The base class - NonPersistentBaseObject - implements the key field property.
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.

In the Startup.cs file, add or find the AddXafWebApi() method call and use the BusinessObject() method to create an endpoint.

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.

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).

File: MySolution.WebApi\Startup.cs (standalone), MySolution.Blazor.Server\Startup.cs (integrated)

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.

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.

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:

public void ConfigureServices(IServiceCollection services) {
    services.AddSingleton<NonPersistentObjectStorageService>();
    //...
}

Tip

In the Web API Service, you can choose to register the global object storage 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.

  • 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.
  • 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.

Integrated or Standalone Web API Service Project

These controls appear if you selected both Web and Service on the Choose Target Platforms page.

Unit Tests

Follow the steps below to add unit tests for the code that queries Non-Persistent Objects via the Web API Service.

  1. If your solution does not have a testing project, add a new xUnit test project.
  2. Add a reference to the {SolutionName}.Blazor.Server project.
  3. Add the Microsoft.AspNetCore.Mvc.Testing package reference.
  4. Add the following test to the test project:
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 response = await httpClient.SendAsync(httpRequest);
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);

        var jsonResult = await response.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 getResponse = await httpClient.SendAsync(getHttpRequest);
            Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);

            jsonResult = await getResponse.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 deleteResponse = await httpClient.SendAsync(deleteHttpRequest);
            Assert.Equal(HttpStatusCode.OK, deleteResponse.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;
    }
}