Audit Trail Module (EF Core)
- 5 minutes to read
This topic describes how to add the Audit Trail Module to your ASP.NET Core Blazor or WinForms application and explains the Module’s features specific to the Entity Framework Core ORM.
Audited Objects
The Audit Trail Module logs changes in the following objects and properties:
- Persistent classes registered as DbSet<TEntity> properties in DbContext.
- Public writable simple and reference properties defined in persistent classes.
- Public collection properties defined in persistent classes.
Note
- To exclude a persistent property from audit, set UseInAuditTrailAttribute to
false
.
Add the Audit Trail Module to Your Application
Follow the steps below to add the Audit Trail Module to your application. If you added this Module when you created an XAF application, the Solution Wizard generates the described code automatically:
Add the DevExpress.ExpressApp.AuditTrail.EFCore NuGet package to the application’s main module (MySolution.Module).
Add a new DbContext used to store audit trail records to the application’s main module and register the
AuditDataItemPersistent
andAuditEFCoreWeakReference
types both in this DbContext and in the application’s main DbContext.Note
You only need to declare the properties used by the Audit Trail Module in both DbContexts if they share the same database connection, which is the default configuration that XAF Solution Wizard generates. If you store audit trail records in a separate database, you should only declare these properties in the additional DbContext. Refer to the following help topic for more information on this technique: Store Audit Data in a Separate Database.
File: MySolution.Module/BusinessObjects/MySolutionDbContext.cs
public class MySolutionEFCoreDbContext : DbContext { // ... // Do not add these properties to the main DbConext if you store // audit trail records in a separate database public DbSet<AuditDataItemPersistent> AuditData { get; set; } public DbSet<AuditEFCoreWeakReference> AuditEFCoreWeakReference { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // ... modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.AuditItems) .WithOne(p => p.AuditedObject); modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.OldItems) .WithOne(p => p.OldObject); modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.NewItems) .WithOne(p => p.NewObject); modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.UserItems) .WithOne(p => p.UserObject); modelBuilder.Entity<ModelDifference>() .HasMany(t => t.Aspects) .WithOne(t => t.Owner) .OnDelete(DeleteBehavior.Cascade); } } public class MySolutionAuditingDbContext : DbContext { public DbSet<AuditDataItemPersistent> AuditData { get; set; } public DbSet<AuditEFCoreWeakReference> AuditEFCoreWeakReference { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.AuditItems) .WithOne(p => p.AuditedObject); modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.OldItems) .WithOne(p => p.OldObject); modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.NewItems) .WithOne(p => p.NewObject); modelBuilder.Entity<AuditEFCoreWeakReference>() .HasMany(p => p.UserItems) .WithOne(p => p.UserObject); } }
For more information on
AuditDataItemPersistent
andAuditEFCoreWeakReference
classes, refer to the following help topic: Access the Audit Log In the Database.Register the Audit Trail Module in the ASP.NET Core Blazor and WinForms application projects:
File: MySolution.Blazor.Server/Startup.cs., MySolution.Win/Startup.cs
public void ConfigureServices(IServiceCollection services) {-+ // ... services.AddXaf(Configuration, builder => { // ... builder.Modules .AddAuditTrailEFCore() } // ... }
Tip
See the following topic for information on alternative ways to register a module: Ways to Register a Module.
In both files, locate the code that configures the object space provider and replace the call to
WithDbContext
withWithAuditedDbContext
as follows:File: MySolution.Blazor.Server/Startup.cs., MySolution.Win/Startup.cs
public void ConfigureServices(IServiceCollection services) {-+ // ... services.AddXaf(Configuration, builder => { // ... builder.ObjectSpaceProviders .AddSecuredEFCore().WithAuditedDbContext(contexts => { contexts.Configure<MySolutionEFCoreDbContext, MySolutionAuditingDbContext>( (serviceProvider, businessObjectDbContextOptions) => { string connectionString = null; if (Configuration.GetConnectionString("ConnectionString") != null) { connectionString = Configuration.GetConnectionString("ConnectionString"); } ArgumentNullException.ThrowIfNull(connectionString); businessObjectDbContextOptions.UseSqlServer(connectionString); businessObjectDbContextOptions.UseChangeTrackingProxies(); businessObjectDbContextOptions.UseObjectSpaceLinkProxies(); businessObjectDbContextOptions.UseLazyLoadingProxies(); }, (serviceProvider, auditHistoryDbContextOptions) => { string connectionString = null; if (Configuration.GetConnectionString("ConnectionString") != null) { connectionString = Configuration.GetConnectionString("ConnectionString"); } ArgumentNullException.ThrowIfNull(connectionString); auditHistoryDbContextOptions.UseSqlServer(connectionString); auditHistoryDbContextOptions.UseChangeTrackingProxies(); auditHistoryDbContextOptions.UseObjectSpaceLinkProxies(); auditHistoryDbContextOptions.UseLazyLoadingProxies(); }); // ... }) // ... } // ... }
Run the application and click the Reports | Audit Event navigation item. The invoked list view contains change history for audited objects.
ASP.NET Core Blazor
WinForms
Note
The AuditInformationReadonlyViewController
does not allow users to create, edit, or delete IAuditDataItemPersistent
objects from the UI.
Display Change History in the Object Detail View
Follow the steps below to show the history of an object in its detail view:
Add the
AuditDataItemPersistent
collection property to your business class.using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using DevExpress.ExpressApp; using DevExpress.Persistent.Base; using DevExpress.Persistent.BaseImpl.EF; using DevExpress.Persistent.BaseImpl.EFCore.AuditTrail; // ... [DefaultClassOptions] public class MyBusinessObject : BaseObject { public virtual string StringProperty { get; set; } [CollectionOperationSet(AllowAdd = false, AllowRemove = false)] [NotMapped] public virtual IList<AuditDataItemPersistent> ChangeHistory { get { return AuditDataItemPersistent.GetAuditTrail(ObjectSpace, this); } } } // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
In applications with the Security System, configure permissions for non-administrative user roles so users can read information on their changes only.
File: MySolution.Module/DatabaseUpdater/Updater.cs
using DevExpress.Persistent.BaseImpl.EFCore.AuditTrail; // ... public class Updater : ModuleUpdater { // ... public override void UpdateDatabaseAfterUpdateSchema() { base.UpdateDatabaseAfterUpdateSchema(); // ... PermissionPolicyRole userRole = ObjectSpace.FirstOrDefault<PermissionPolicyRole>(role => role.Name == "Users"); if(userRole == null) { defaultRole = ObjectSpace.CreateObject<PermissionPolicyRole>(); // ... defaultRole.AddTypePermission<AuditDataItemPersistent>(SecurityOperations.Read, SecurityPermissionState.Deny); defaultRole.AddObjectPermissionFromLambda<AuditDataItemPersistent>(SecurityOperations.Read, a => a.UserObject.Key == CurrentUserIdOperator.CurrentUserId().ToString(), SecurityPermissionState.Allow); defaultRole.AddTypePermission<AuditEFCoreWeakReference>(SecurityOperations.Read, SecurityPermissionState.Allow); } } }
The images below demonstrate the result:
ASP.NET Core Blazor
WinForms
Tip
You can also access the audit log in the database directly if you do not want to display history in the UI.