Skip to main content
A newer version of this page is available. .
All docs
V21.1

Create an ASP.NET Core Web API CRUD Service

  • 5 minutes to read

This article describes how to implement a controller in an existing ASP.NET Core Web API project.

Access a UnitOfWork Object

XPO supports ASP.NET Core Dependency Injection, that means that you can add a UnitOfWork object as a constructor argument.

using Microsoft.AspNetCore.Mvc;
using DevExpress.Xpo;
// ...
public class CustomersController : ControllerBase {
   private UnitOfWork uow;
   public CustomersController(UnitOfWork uow) {
      this.uow = uow;
   }
}

To make this work, call the AddXpoDefaultUnitOfWork method in the Startup.ConfigureServices method. Refer to the Add Required Services section in this document for details.

Implement Get Methods

To implement Get methods, use LINQ to XPO and Projection Operations to create a collection of anonymous objects:

using Microsoft.AspNetCore.Mvc;
using DevExpress.Xpo;
// ...
readonly UnitOfWork UnitOfWork;
[HttpGet]
public ActionResult Get() {
    try {
        var data = UnitOfWork.Query<Orders>()
            .Select(o => new { o.ShipName, o.OrderDate, o.Freight })
            .ToList();
        return Ok(data);
    } catch (Exception ex) {
        return BadRequest(ex);
    }
}

Implement Post and Put Methods

ASP.NET Core runtime can populate model properties from the HTTP request body if a Controller method accepts a model as a parameter that has the FromBody attribute.

To help ASP.NET Core runtime create and load XPO models correctly, use XPO JSON Converters.

Add the following helper class to your project to register the XPO JSON Converters factory in an ASP.NET Core application:

using System;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using DevExpress.Xpo;
// ...
public class ConfigureJsonOptions : IConfigureOptions<JsonOptions>, IServiceProvider {
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IServiceProvider _serviceProvider;

    public ConfigureJsonOptions(
        IHttpContextAccessor httpContextAccessor,
        IServiceProvider serviceProvider) {
        _httpContextAccessor = httpContextAccessor;
        _serviceProvider = serviceProvider;
    }

    public void Configure(JsonOptions options) {
        options.JsonSerializerOptions.Converters.Add(new PersistentBaseConverterFactory(this));
    }

    public object GetService(Type serviceType) {
        return (_httpContextAccessor.HttpContext?.RequestServices ?? _serviceProvider).GetService(serviceType);
    }
}

Use this class in the Startup.ConfigureServices method as shown below:

services.ConfigureOptions<ConfigureJsonOptions>();

Tip

Data Model Wizard adds the ConfigureJsonOptions class to an ASP.NET Core project if it has the System.Text.Json package installed. Refer to the following section for more information: Add Required Services.

After you have enabled XPO JSON Converters, you can implement Post and Put methods like in a code sample below:

[HttpPost]
public IActionResult Post([FromBody] Customer customer) {
    try {
        uow.CommitChanges();
        return NoContent();
    } catch(Exception exception) {
        return BadRequest(exception);
    }
}
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody] Customer customer) {
    if(id != customer.Oid)
        return NotFound();
    try {
        uow.CommitChanges();
        return NoContent();
    } catch(Exception exception) {
        return BadRequest(exception);
    }
}

Custom Metadata Provider

XPO models implement built-in service properties. To make sure that the ASP.NET Core framework will ignore service properties, extend the DefaultModelMetadataProvider component and register it as shown below:

using System;
using System.Linq;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using DevExpress.Xpo;

public class XpoMetadataProvider : DefaultModelMetadataProvider {
    public XpoMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider) : base(detailsProvider) {

    }
    public XpoMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider, IOptions<MvcOptions> optionsAccessor) : base(detailsProvider, optionsAccessor) {

    }
    protected override DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key) {
        DefaultMetadataDetails[] result = base.CreatePropertyDetails(key);
        if(typeof(PersistentBase).IsAssignableFrom(key.ModelType))
            return result.Where(x => !IsServiceField(x.Key)).ToArray();
        else
            return result;
    }
    static bool IsServiceField(ModelMetadataIdentity identity) {
        Type declaringType = identity.PropertyInfo.DeclaringType;
        return declaringType == typeof(PersistentBase)
            || declaringType == typeof(XPBaseObject);
    }
}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
// ...
public void ConfigureServices(IServiceCollection services) {
    // ...
    services.AddSingleton(typeof(IModelMetadataProvider), typeof(XpoMetadataProvider));
}

Tip

Data Model Wizard adds the XpoMetadataProvider class to an ASP.NET Core project if it has the System.Text.Json package installed. Refer to the following section for additional information: Add Required Services.

Add Required Services

Using XPO Data Model Wizard

XPO Data Model Wizard adds the ServiceCollectionExtensions.cs and JsonSerializationHelper.cs files to your project. These files extend the IServiceCollection interface with helper methods required to add XPO components to the DI service container.

You can prepare an ASP.NET Core Web API project as follows:

  1. Install the DevExpress.Xpo NuGet package.
  2. Use steps from the following article to create an XPO data model: Data Model Wizard.
  3. Open the Startup.cs file and modify the ConfigureServices method as shown below. If you copy this code, replace “Northwind” with your XPO data model name or choose appropriate methods from the IntelliSense window.

    public void ConfigureServices(IServiceCollection services) {
        // ...
    
        services.AddNorthwindAsXpoDefaultUnitOfWork(true, options =>
            options.UseConnectionStringForNorthwind(Configuration));
        services.AddNorthwindJsonOptions();
    }
    

Manually

  1. Refer to the following article that describes XPO Dependency Injection APIs: XPO Extensions for ASP.NET Core Dependency Injection.
  2. Add the XPOJsonOptions.cs file to your project: XPOJsonOptions.cs.
  3. Register XPO DI components in the ConfigureServices method as shown below.

    public void ConfigureServices(IServiceCollection services) {  
        services.AddXpoDefaultUnitOfWork(true, options =>  
            options.UseConnectionString(Configuration.GetConnectionString("MSSqlServer"))  
                .UseAutoCreateOption(AutoCreateOption.DatabaseAndSchema) // Remove this line if the database already exists.
                .UseEntityTypes(new Type[] { typeof(User) })); // Pass all of your persistent object types to this method.  
        services.ConfigureOptions<ConfigureJsonOptions>();
        services.AddSingleton(typeof(IModelMetadataProvider), typeof(XpoMetadataProvider));
    }
    

How the XPO JSON Serialization Works

Serialization

  1. A JSON object contains all public properties except for collections.
  2. If a public property references another persistent object and this property does not have the Aggregated attribute, the corresponding JSON field contains only the object’s key.
  3. If a public property references another persistent object and this property has the Aggregated attribute, the corresponding JSON field contains an object literal.
  4. If a collection property does not have the Association attribute, the corresponding JSON field contains an array of objects.

Deserialization

  1. If a JSON object contains a key member, PersistentBaseConverter attempts to load an object from a data store. If the corresponding object does not exist in the data store, PersistentBaseConverter creates a new object.
  2. If a JSON object does not contain a key member, PersistentBaseConverter creates a new object.
  3. PersistentBaseConverter skips a JSON field if the corresponding property is read-only or does not exist in a persistent class.
  4. If a property references another XPO object, the corresponding JSON field should contain a key value. PersistentBaseConverter loads a referenced object by key and updates the reference property.
  5. If a property has the Aggregated attribute, the corresponding JSON field should contain an object literal. PersistentBaseConverter updates a referenced object’s properties directly.

Optimistic Locking

Every JSON object should include the OptimisticLockField property when sent to or received from the server if Optimistic Locking is enabled in a client-server application.