Miscellaneous Customizations of the Audit Trail System (XPO)
- 4 minutes to read
Implement Custom Persistent Object to be Used as the Audit Data Storage
The Audit Trail Module uses the AuditDataItemPersistent class as the audit data storage. To store extra audit information, inherit from this class or implement the IAuditDataItemPersistent interface. In the overridden OnSaving method of your AuditDataItemPersistent descendant, initialize extra properties based on the AuditedObject, PropertyName, and other information from the base class. Set the AuditDataItemPersistentType property to your class to use it instead of the default class.
Specify the String Representation of the Null Value
The Audit Trail Module saves the null value as the “N/A” string. To change its string representation, navigate to the Localization | AuditTrail node in the Model Editor and specify the NullValueString property.

If you want to use a different null value representation in audit records in the UI, set AuditDataStore‘s NullValueString property as shown below.
In .NET Applications
File: MySolution.Blazor.Server\Startup.cs, MySolution.Win\Startup.cs, MySolution.WebApi\Startup.cs
using DevExpress.Persistent.AuditTrail;
// ...
builder.Modules
.AddAuditTrailXpo(o => {
o.Events.OnCustomizeAuditDataStore = context => {
context.AuditDataStore.NullValueString = "";
};
})
In .NET Framework Applications
File: MySolution.Win\WinApplication.cs, MySolution.Web\WebApplication.cs.
using DevExpress.Persistent.AuditTrail;
// ...
public partial class MySolutionWinApplication : WinApplication {
//...
protected override void OnSetupComplete() {
base.OnSetupComplete();
AuditTrailService.Instance.AuditDataStore.NullValueString = "";
}
// ...
}
Customize the Blob Properties Storage Mechanism
The Audit Trail Module saves Blob property values as the “Blob data” string. Follow the steps below to customize this behavior.
Note
You can also use this technique to specify the string representation of reference type properties.
Implement the
AuditDataStoredescendant and override itsGetDefaultStringRepresentationmethod.File: MySolution.Module\CustomAuditDataStore.cs.
using DevExpress.Persistent.AuditTrail; using DevExpress.Persistent.BaseImpl; // ... public class CustomAuditDataStore : AuditDataStore<AuditDataItemPersistent, AuditedObjectWeakReference> { protected override string GetDefaultStringRepresentation(object value) { string result = base.GetDefaultStringRepresentation(value); if(result == BlobDataString) return GetCustomBlobDataString(value); return result; } private string GetCustomBlobDataString(object value) { // ... } }Configure the Audit Trail system to use your custom data store:
In .NET Applications
In the application’s Startup.cs file, add the following code to the
AddAuditTrailXpomethod call.File: MySolution.Blazor.Server\Startup.cs, MySolution.Win\Startup.cs, MySolution.WebApi\Startup.cs
using DevExpress.Persistent.AuditTrail; // ... builder.Modules .AddAuditTrailXpo(o => { o.Events.OnCustomCreateAuditDataStore = context => { context.AuditDataStore = new CustomAuditDataStore(); }; })In .NET Framework Applications
Set the AuditTrailService.Instance.AuditDataStore property to an instance of your class.
File: MySolution.Win\WinApplication.cs, MySolution.Web\WebApplication.cs.
using DevExpress.Persistent.AuditTrail; // ... public partial class MySolutionWinApplication : WinApplication { // ... protected override void OnSetupComplete() { base.OnSetupComplete(); AuditTrailService.Instance.AuditDataStore = new CustomAuditDataStore(); } // ... }
Implement a Custom AuditTrailService (.NET)
In XAF applications that target .NET, the Audit Trail system is implemented as a service (IAuditTrailService). You can create and register your own implementation of the IAuditTrailService as this section describes.
To create a custom IAuditTrailService, you can use the AuditTrailServiceBase type as a base type for your custom implementation or implement the IAuditTrailService interface from scratch:
using DevExpress.Persistent.BaseImpl.AuditTrail.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
public class CustomAuditTrailService : AuditTrailServiceBase {
private const string unknownUserName = "Unknown";
private readonly ISecurityStrategyBase securityStrategy;
private readonly IAuditTrailServiceRoot auditTrailServiceRoot;
private bool isSetupAuditCalled = false;
public CustomAuditTrailService(IServiceProvider serviceProvider, IAuditTrailServiceRoot auditTrailServiceRoot, IOptionsSnapshot<AuditTrailOptions> auditTrailServiceOptions) :
base(serviceProvider, auditTrailServiceRoot, auditTrailServiceOptions.Value) {
this.securityStrategy = serviceProvider.GetService<ISecurityStrategyBase>();
this.auditTrailServiceRoot = auditTrailServiceRoot;
}
protected override void EnsureSetupAudit() {
if(!isSetupAuditCalled) {
isSetupAuditCalled = true;
auditTrailServiceRoot.SetupAudit(serviceProvider.GetRequiredService<ITypesInfo>(), Options.AuditDataItemPersistentType);
}
}
protected override string GetCurrentUserNameCore() {
return securityStrategy != null ? securityStrategy.UserName : unknownUserName;
}
}
Register your custom IAuditTrailService type as a scoped service:
File: MySolution.Blazor.Server\Startup.cs, MySolution.Win\Startup.cs, MySolution.WebApi\Startup.cs
// ...
public class Startup {
// ...
public void ConfigureServices(IServiceCollection services) {
// ...
services.AddXaf(Configuration, builder => {
builder.UseApplication<MyBlazorApplication>();
builder.Modules
// ...
.AddAuditTrailXpo()
// ...
});
services.AddScoped<IAuditTrailService, CustomAuditTrailService>();
// ...
}
//...
}