Skip to main content
All docs
V23.2

Change the Entity Data Model Structure Exposed in OData

  • 6 minutes to read

This article describes techniques that you can use to customize the Web API Service’s OData interface including the availability of particular endpoints and the structure of data written to the service’s responses.

To specify the ODataValidationSettings.MaxExpansionDepth option, follow the steps below:

  • Implement a custom ODataQueryValidator service to set MaxExpansionDepth:

    using Microsoft.AspNetCore.OData.Query.Validator;
    using Microsoft.AspNetCore.OData.Query;
    // ...
    public class MyODataQueryValidator : ODataQueryValidator {
        public override void Validate(ODataQueryOptions options, ODataValidationSettings validationSettings){
            validationSettings.MaxExpansionDepth = 3;
            base.Validate(options, validationSettings);
        }
    }
    
  • Replace the default ODataQueryValidator service with the custom service in the ConfigureServices method:

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

    public void ConfigureServices(IServiceCollection services) {
    // ...
    services.AddControllers().AddOData((options, serviceProvider) => {
        options
            .EnableQueryFeatures(100)
            .AddRouteComponents("api/odata", new EdmModelBuilder(serviceProvider).GetEdmModel(), odataServices => {
                odataServices.AddSingleton<ODataQueryValidator, MyODataQueryValidator>();
            });
        });
        // ...
    }
    

Enable/Disable Web Actions for Business Objects

Use the techniques described below to enable or disable specific combinations of OData endpoints and HTTP verbs for business objects in your Web API Service application.

Important

We recommend that you configure Type, Object, and Member security permissions for your business objects. Ensure that your API controls data access or CRUD operations depending on the user role. In addition, you can use the techniques described in this section to prevent execution of specific web actions on a business object at the Swagger UI and OData query levels.

  • Use the WebApiOptions.ConfigureDataControllers extension method to enable/disable web actions globally for all business objects:

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

    services.AddXafWebApi(builder => {
        builder.ConfigureOptions(options => {
            options.ConfigureDataControllers(b => {
                // Place your logic to customize the availability of web actions here.
                // You can use one of the following methods:
                // - b.WithActions(a => !a.ActionName.Contains("Ref"));
                // - b.WithActions(WebApiActions.PostEntity | WebApiActions.GetEntity);
                // - b.ReadOnly();
            });
        });
    }, Configuration);
    
  • Use the BusinessObjectConfigurator.ConfigureController method to enable/disable web actions for a specific business object. If this method is called for a business object, it overrides all effects of WebApiOptions.ConfigureDataControllers for this object.

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

    services.AddXafWebApi(builder => {
        builder.ConfigureOptions(options => {
            options.BusinessObject<MyEntity>().ConfigureController(b => {
                // Place your logic to customize the availability of web actions here.
                // You can use one of the following methods:
                // - b.WithActions(a => !a.ActionName.Contains("Ref"));
                // - b.WithActions(WebApiActions.PostEntity | WebApiActions.GetEntity);
                // - b.ReadOnly();
            });
        });
    }, Configuration);
    

Both methods take a delegate as a parameter. The delegate’s argument exposes the WithActions method, which has two overloads that you can use in the following ways:

  • Allow web actions based on an arbitrary predicate:

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

    options.BusinessObject<MyEntity>().ConfigureController(b => { 
        b.WithActions(a => !a.ActionName.Contains("Ref"));
    });
    
  • Use predefined bit flags:

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

    options.BusinessObject<MyEntity>().ConfigureController(b => { 
        b.WithActions(WebApiActions.PostEntity | WebApiActions.GetEntity);
    });
    

The WebApiActions enumeration defines the following bit flags:

Flag Description
GetEntity Gets the specified object.
GetEntities Gets multiple objects.
PostEntity Creates a new object.
PutEntity Overwrites the specified object.
PatchEntity Updates the specified object.
DeleteEntity Deletes the specified object.
GetReference Gets an object or a list of objects from the specified navigation or collection property.
CreateReference Associates an object with a navigation property or adds an object to a list property.
DeleteNavigation Unlinks an object from a navigation property.
DeleteFromCollection Removes an object from a list property.
ReadOnly Equivalent to GetEntity | GetEntities | GetReference
EntityActions Equivalent to GetEntity | GetEntities | PostEntity | PutEntity | PatchEntity | DeleteEntity
ReferenceActions Equivalent to GetReference | CreateReference | DeleteNavigation | DeleteFromCollection

You can also use the ReadOnly method to only allow read actions. This method has the same effect as the ReadOnly bit flag.

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

options.BusinessObject<MyEntity>().ConfigureController(b => { 
    b.ReadOnly();
    // The above line is equivalent to:
    // b.WithActions(WebApiActions.ReadOnly);
});

The AllActions method allows you to enable all web actions for a specific business class when some of the actions are prohibited globally.

options.BusinessObject<MyEntity>().ConfigureController(b => { 
    b.AllActions();
});

For example, the image below demonstrates how the Swagger UI reflects a business object configured in read-only mode.

Important

Keep in mind that the hidden endpoints are not only invisible in Swagger, but are entirely unavailable through the OData interface.

Customize Web Actions

Configure an OData Entity Type

Use one of the following techniques to customize the OData entity data model structure.

Use the OnCustomizeEdmModel property

The WebApiEvents.OnCustomizeEdmModel property allows you to customize the OData entity data model structure at runtime (change types, properties, actions, and so on).

For example, you can implement custom logic that adds a property that was removed from the model by the IgnoreDataMemberAttribute.

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

services.AddXaf(Configuration, builder => {
    //...
    builder.AddXafWebApi(webApiBuilder => {
        webApiBuilder.ConfigureOptions(options => {
            options.Events.OnCustomizeEdmModel = context => {
                context.ODataModelBuilder.AddEntityType(typeof(MyEntity)).AddProperty(typeof(MyEntity).GetProperty(nameof(MyEntity.MyProperty)));
            };
            //...
        });
    });
});

public class MyEntity : BaseObject {
    [IgnoreDataMember]
    public virtual int MyProperty { get; set; }
    //...
}

Use the ODataModelBuilder

Implement the DevExpress.ExpressApp.WebApi.Services.IEdmModelCustomizer interface in a custom class. In your implementation of the class CustomizeEdmModel method, use the ODataModelBuilder to customize the OData entity data model based on your requirements.

You can register your IEdmModelCustomizer implementation after the services.AddXafWebApi call in the ConfigureServices method:

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

services.AddXafWebApi(options => { /* ... */  }, Configuration);
services.TryAddEnumerable(ServiceDescriptor.Transient<IEdmModelCustomizer, CustomEdmModelCustomizer>());

For more information, refer to the following resources:

Use the EntityTypeConfiguration

The EntityTypeConfigurator.ConfigureODataEntityType method allows you to directly access an EntityTypeConfiguration<TEntityType> instance for your business class. Use the API exposed through the instance to configure the entity type.

For example, you can add a property that was hidden from the Entity Data Model by the IgnoreDataMemberAttribute as shown below.

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

services.AddXafWebApi(builder => {
    builder.ConfigureOptions(options => {
        options.BusinessObject<MyEntity>().ConfigureEntityType(b => {
            // Add the current business class ignored property.
            b.ConfigureODataEntityType(d => d.Property(o => o.IgnoredProperty));
            // Add the base class ignored property.
            b.ConfigureODataEntityType(
                // You can access the `BaseType` properties sequentially to get to
                // an arbitrary ancestor's configurator (`d.BaseType.BaseType ...`)
                d => d.BaseType.AddProperty(
                    typeof(MyEntity)
                        .GetProperty(nameof(BusinessEntityBase.BaseClassIgnoredProperty))
                )
            );
        });
    });
}, Configuration);