The Audit Trail module provides essential tools for tracking and recovering data changes within your application. It allows users to view detailed change history for any object and restore deleted objects along with their related data. This ensures transparency, supports data recovery, and enhances overall data integrity.
This module requires the XPO or EF specific module to be added to your solution to function.
The Audit Trail module provides essential tools for tracking and recovering data changes within your application. It allows users to view detailed change history for any object and restore deleted objects along with their related data. This ensures transparency, supports data recovery, and enhances overall data integrity.
This module requires the XPO or EF specific module to be added to your solution to function.
Install the relevant ORM-specific module:
Install-Package 'Llamachant.ExpressApp.AuditTrail.Xpo'
Install-Package 'Llamachant.ExpressApp.AuditTrail.EF'
Register the relevant ORM-specific module (either Xpo or EF Core):
public MyModule() {
// Xpo:
RequiredModuleTypes.Add(typeof(LlamachantFrameworkAuditTrailModuleXpo));
// EF Core:
RequiredModuleTypes.Add(typeof(LlamachantFrameworkAuditTrailModuleEF));
}
OR
services.AddXaf(Configuration, builder => {
builder.UseApplication<ExpressAppBlazorApplication>();
builder.Modules
// Xpo:
.AddLlamachantFrameworkAuditTrailModuleXpo();
// EF Core:
.AddLlamachantFrameworkAuditTrailModuleEF();
});
Install the relevant ORM-specific module:
Install-Package 'Llamachant.ExpressApp.AuditTrail.Xpo'
Install-Package 'Llamachant.ExpressApp.AuditTrail.EF'
Register the relevant ORM-specific module (either Xpo or EF Core):
public MyModule() {
// Xpo:
RequiredModuleTypes.Add(typeof(LlamachantFrameworkAuditTrailModuleXpo));
// EF Core:
RequiredModuleTypes.Add(typeof(LlamachantFrameworkAuditTrailModuleEF));
}
OR
services.AddXaf(Configuration, builder => {
builder.UseApplication<ExpressAppBlazorApplication>();
builder.Modules
// Xpo:
.AddLlamachantFrameworkAuditTrailModuleXpo();
// EF Core:
.AddLlamachantFrameworkAuditTrailModuleEF();
});
The View Audit Trail feature allows users to access an object’s full change history directly from its Detail View. When enabled, a View Audit Trail action appears in the Tools container, opening a popup window that displays all recorded changes.
Visibility of this action is controlled by the CanViewAuditTrail setting in the Model’s Options node.
| Application | Llamachant Framework | Values |
|---|---|---|
| CanViewAuditTrail | None / All / UserSpecific* |
To use the UserSpecific option, implement the IAuditTrailUser interface on your custom user class.
The View Audit Trail feature allows users to access an object’s full change history directly from its Detail View. When enabled, a View Audit Trail action appears in the Tools container, opening a popup window that displays all recorded changes.
Visibility of this action is controlled by the CanViewAuditTrail setting in the Model’s Options node.
| Application | Llamachant Framework | Values |
|---|---|---|
| CanViewAuditTrail | None / All / UserSpecific* |
To use the UserSpecific option, implement the IAuditTrailUser interface on your custom user class.
The Audit Trail Restore feature allows users to recover deleted objects, including their related data, by reconstructing them from audit records. This is particularly useful for restoring important business data that may have been removed unintentionally.
Visibility of this action is controlled by the CanRestoreFromAuditTrail setting in the Model’s Options node.
| Application | Llamachant Framework | Values |
|---|---|---|
| CanRestoreFromAuditTrail | None / All / UserSpecific* |
To use the UserSpecific option, implement the IAuditTrailUser interface on your custom user class.
To enable this feature, ensure the following requirements are met:
The Audit Trail Restore feature allows users to recover deleted objects, including their related data, by reconstructing them from audit records. This is particularly useful for restoring important business data that may have been removed unintentionally.
Visibility of this action is controlled by the CanRestoreFromAuditTrail setting in the Model’s Options node.
| Application | Llamachant Framework | Values |
|---|---|---|
| CanRestoreFromAuditTrail | None / All / UserSpecific* |
To use the UserSpecific option, implement the IAuditTrailUser interface on your custom user class.
To enable this feature, ensure the following requirements are met:
If your application uses a custom audit trail system, you can integrate it with the Llamachant Framework Audit Trail Module by implementing a few key extension points. This allows you to leverage the module’s View Audit Trail and Audit Trail Restore features while maintaining full control over your audit data source.
The implementation process varies slightly depending on your ORM.
For Xpo or EF Core instructions and examples, please see the respective section below.
If your application uses a custom audit trail system, you can integrate it with the Llamachant Framework Audit Trail Module by implementing a few key extension points. This allows you to leverage the module’s View Audit Trail and Audit Trail Restore features while maintaining full control over your audit data source.
The implementation process varies slightly depending on your ORM.
For Xpo or EF Core instructions and examples, please see the respective section below.
Begin by creating a class that inherits from LlamachantFramework.AuditTrail.NonPersistent.AuditDataRecord. This class acts as a wrapper around your custom audit data and must override the following members:
If your custom audit trail stores object references in a non-standard format (e.g., IDs or serialized strings), you may also need to implement a ResolveObject method to convert these into usable object instances.
Example:
[DomainComponent]
public class CosmosAuditDataRecord : AuditDataRecord
{
public override object GetAuditedObject() => AuditedObject;
public override object GetNewObject() => NewObject;
public override object GetOldObject() => OldObject;
}
Create an extension method on the object type returned by your audit trail queries. This method should construct and return an instance of your custom AuditDataRecord, transforming the raw audit data into the required format.
Example:
public static class AuditDataItemPersistentExtensions
{
public static AuditDataRecord CreateAuditDataRecord(this AuditDataItem record, IObjectSpace space)
{
CustomAuditDataRecord customAuditDataRecord = space.CreateObject<CustomAuditDataRecord>();
customAuditDataRecord.ModifiedOn = record.ModifiedOn;
customAuditDataRecord.OperationType = record.OperationType;
customAuditDataRecord.PropertyName = record.PropertyName;
customAuditDataRecord.OldValue = record.OldValue;
customAuditDataRecord.NewValue = record.NewValue;
customAuditDataRecord.OldObject = record.OldObject;
customAuditDataRecord.NewObject = record.NewObject;
customAuditDataRecord.AuditedObject = record.AuditedObjectId;
customAuditDataRecord.UserName = record.UserName;
return customAuditDataRecord;
}
}
Create a new class that implements IAuditTrailStorage. This class defines how the audit trail module communicates with your custom data source. Implement the following methods:
Example:
public class CustomDataItemPersistentStorage : IAuditTrailStorage
{
protected IObjectSpace ObjectSpace;
protected IAuditTrailService AuditTrailService;
public virtual void Initialize(IObjectSpace objectspace)
{
ObjectSpace = objectspace;
AuditTrailService = ObjectSpace.ServiceProvider.GetService<IAuditTrailService>();
}
public virtual IEnumerable<AuditDataRecord> GetAuditTrail(object obj, params string[] operationtypes)
{
// Query your audit trail and create Audit Data Records. For example:
var auditdata = AuditTrailService.GetRecords();
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace));
}
public virtual void MarkObjectsAsRestored(List<object> restoredobjects, string oldvalue, string newvalue)
{
// Mark objects as "restored". For example:
foreach (object obj in restoredobjects)
{
AuditDataItem a = new AuditDataItem(obj, null, oldvalue, newvalue, AuditOperationType.CustomData);
if (ObjectSpace is XPObjectSpace space)
AuditTrailService.AddCustomAuditData(((XPObjectSpace)ObjectSpace).Session, a);
else
AuditTrailService.AddCustomAuditData(((XPObjectSpace)ObjectSpace.ForType(obj.GetType())).Session, a);
}
}
public virtual void RemoveDeferredDeletionValue(object obj)
{
// Restore soft-deleted objects by removing their deferred deletion marker. For example:
ITypeInfo currenttypeinfo = XafTypesInfo.Instance.FindTypeInfo(obj.GetType().FullName);
if (currenttypeinfo.IsDomainComponent)
{
System.Reflection.MethodInfo method = obj.GetType().GetMethod("SetMemberValue");
method.Invoke(obj, new object[] { "GCRecord", null });
}
else if (currenttypeinfo.FindAttribute<DeferredDeletionAttribute>() != null && currenttypeinfo.FindMember("GCRecord").GetValue(obj) != null)
currenttypeinfo.FindMember("GCRecord").SetValue(obj, null);
}
public virtual IEnumerable<AuditDataRecord> SearchAuditTrail(DateTime start, DateTime end, string query, params string[] operationtypes)
{
// Perform a filtered query against your custom audit trail and convert the results into Audit Data Records. For example:
string auditquery = "{your query here}";
ICustomAuditTrail audittrail = CustomAuditTrailFactory.Create();
var auditdata = audittrail.QueryAuditTrailData(auditquery);
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace)).DistinctBy(x => x.AuditedObject);
}
public virtual void Dispose()
{
// Clean up any necessary resources. For example:
ObjectSpace = null;
AuditTrailService = null;
}
}
Finally, configure the Audit Trail module to use your custom storage class. In your platform-specific Module.cs file, subscribe to the Application.SetupComplete event, retrieve the Llamachant Audit Trail module from the application’s modules collection, and set its AuditTrailStorageType property:
private void Application_SetupComplete(object sender, EventArgs e)
{
var auditmodule = Application.Modules.FindModule<LlamachantFrameworkAuditTrailModule>();
if(auditmodule != null)
auditmodule.AuditTrailStorageType = typeof(CustomAuditDataItemPersistentStorage);
}
Begin by creating a class that inherits from LlamachantFramework.AuditTrail.NonPersistent.AuditDataRecord. This class acts as a wrapper around your custom audit data and must override the following members:
If your custom audit trail stores object references in a non-standard format (e.g., IDs or serialized strings), you may also need to implement a ResolveObject method to convert these into usable object instances.
Example:
[DomainComponent]
public class CosmosAuditDataRecord : AuditDataRecord
{
public override object GetAuditedObject() => AuditedObject;
public override object GetNewObject() => NewObject;
public override object GetOldObject() => OldObject;
}
Create an extension method on the object type returned by your audit trail queries. This method should construct and return an instance of your custom AuditDataRecord, transforming the raw audit data into the required format.
Example:
public static class AuditDataItemPersistentExtensions
{
public static AuditDataRecord CreateAuditDataRecord(this AuditDataItem record, IObjectSpace space)
{
CustomAuditDataRecord customAuditDataRecord = space.CreateObject<CustomAuditDataRecord>();
customAuditDataRecord.ModifiedOn = record.ModifiedOn;
customAuditDataRecord.OperationType = record.OperationType;
customAuditDataRecord.PropertyName = record.PropertyName;
customAuditDataRecord.OldValue = record.OldValue;
customAuditDataRecord.NewValue = record.NewValue;
customAuditDataRecord.OldObject = record.OldObject;
customAuditDataRecord.NewObject = record.NewObject;
customAuditDataRecord.AuditedObject = record.AuditedObjectId;
customAuditDataRecord.UserName = record.UserName;
return customAuditDataRecord;
}
}
Create a new class that implements IAuditTrailStorage. This class defines how the audit trail module communicates with your custom data source. Implement the following methods:
Example:
public class CustomDataItemPersistentStorage : IAuditTrailStorage
{
protected IObjectSpace ObjectSpace;
protected IAuditTrailService AuditTrailService;
public virtual void Initialize(IObjectSpace objectspace)
{
ObjectSpace = objectspace;
AuditTrailService = ObjectSpace.ServiceProvider.GetService<IAuditTrailService>();
}
public virtual IEnumerable<AuditDataRecord> GetAuditTrail(object obj, params string[] operationtypes)
{
// Query your audit trail and create Audit Data Records. For example:
var auditdata = AuditTrailService.GetRecords();
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace));
}
public virtual void MarkObjectsAsRestored(List<object> restoredobjects, string oldvalue, string newvalue)
{
// Mark objects as "restored". For example:
foreach (object obj in restoredobjects)
{
AuditDataItem a = new AuditDataItem(obj, null, oldvalue, newvalue, AuditOperationType.CustomData);
if (ObjectSpace is XPObjectSpace space)
AuditTrailService.AddCustomAuditData(((XPObjectSpace)ObjectSpace).Session, a);
else
AuditTrailService.AddCustomAuditData(((XPObjectSpace)ObjectSpace.ForType(obj.GetType())).Session, a);
}
}
public virtual void RemoveDeferredDeletionValue(object obj)
{
// Restore soft-deleted objects by removing their deferred deletion marker. For example:
ITypeInfo currenttypeinfo = XafTypesInfo.Instance.FindTypeInfo(obj.GetType().FullName);
if (currenttypeinfo.IsDomainComponent)
{
System.Reflection.MethodInfo method = obj.GetType().GetMethod("SetMemberValue");
method.Invoke(obj, new object[] { "GCRecord", null });
}
else if (currenttypeinfo.FindAttribute<DeferredDeletionAttribute>() != null && currenttypeinfo.FindMember("GCRecord").GetValue(obj) != null)
currenttypeinfo.FindMember("GCRecord").SetValue(obj, null);
}
public virtual IEnumerable<AuditDataRecord> SearchAuditTrail(DateTime start, DateTime end, string query, params string[] operationtypes)
{
// Perform a filtered query against your custom audit trail and convert the results into Audit Data Records. For example:
string auditquery = "{your query here}";
ICustomAuditTrail audittrail = CustomAuditTrailFactory.Create();
var auditdata = audittrail.QueryAuditTrailData(auditquery);
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace)).DistinctBy(x => x.AuditedObject);
}
public virtual void Dispose()
{
// Clean up any necessary resources. For example:
ObjectSpace = null;
AuditTrailService = null;
}
}
Finally, configure the Audit Trail module to use your custom storage class. In your platform-specific Module.cs file, subscribe to the Application.SetupComplete event, retrieve the Llamachant Audit Trail module from the application’s modules collection, and set its AuditTrailStorageType property:
private void Application_SetupComplete(object sender, EventArgs e)
{
var auditmodule = Application.Modules.FindModule<LlamachantFrameworkAuditTrailModule>();
if(auditmodule != null)
auditmodule.AuditTrailStorageType = typeof(CustomAuditDataItemPersistentStorage);
}
Begin by creating a class that inherits from LlamachantFramework.AuditTrail.NonPersistent.AuditDataRecord. This class acts as a wrapper around your custom audit data and must override the following members:
If your custom audit trail stores object references in a non-standard format (e.g., IDs or serialized strings), you may also need to implement a ResolveObject method to convert these into usable object instances.
Example:
[DomainComponent]
public class CosmosAuditDataRecord : AuditDataRecord
{
public override object GetAuditedObject() => AuditedObject;
public override object GetNewObject() => NewObject;
public override object GetOldObject() => OldObject;
}
Create an extension method on the object type returned by your audit trail queries. This method should construct and return an instance of your custom AuditDataRecord, transforming the raw audit data into the required format.
Example:
public static class AuditDataItemPersistentExtensions
{
public static AuditDataRecord CreateAuditDataRecord(this AuditDataItem record, IObjectSpace space)
{
CustomAuditDataRecord customAuditDataRecord = space.CreateObject<CustomAuditDataRecord>();
customAuditDataRecord.ModifiedOn = record.ModifiedOn;
customAuditDataRecord.OperationType = record.OperationType;
customAuditDataRecord.PropertyName = record.PropertyName;
customAuditDataRecord.OldValue = record.OldValue;
customAuditDataRecord.NewValue = record.NewValue;
customAuditDataRecord.OldObject = record.OldObject;
customAuditDataRecord.NewObject = record.NewObject;
customAuditDataRecord.AuditedObject = record.AuditedObjectId;
customAuditDataRecord.UserName = record.UserName;
return customAuditDataRecord;
}
}
Create a new class that implements IAuditTrailStorage. This class defines how the audit trail module communicates with your custom data source. Implement the following methods:
Example:
public class CustomDataItemPersistentStorage : IAuditTrailStorage
{
protected IObjectSpace ObjectSpace;
protected IAuditTrailService AuditTrailService;
public virtual void Initialize(IObjectSpace objectspace)
{
ObjectSpace = objectspace;
AuditTrailService = ObjectSpace.ServiceProvider.GetService<IAuditTrailService>();
}
public virtual IEnumerable<AuditDataRecord> GetAuditTrail(object obj, params string[] operationtypes)
{
// Query your audit trail and create Audit Data Records. For example:
var auditdata = AuditTrailService.GetRecords();
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace));
}
public virtual void MarkObjectsAsRestored(List<object> restoredobjects, string oldvalue, string newvalue)
{
// Mark objects as "restored". For example:
foreach (object obj in restoredobjects)
{
((EFCoreObjectSpace)ObjectSpace.ForType<AuditDataItemPersistent>()).GetAuditTrailService().SaveCustomData((data, wr) =>
{
data.OldValue = oldvalue;
data.NewValue = newvalue;
data.AuditedObject = wr(obj);
data.ModifiedOn = DateTime.Now;
});
}
}
public virtual void RemoveDeferredDeletionValue(object obj)
{
// Restore soft-deleted objects by removing their deferred deletion marker. For example:
Type t = ObjectSpace.ForType(obj.GetType()).GetObjectType(obj);
ITypeInfo currenttypeinfo = XafTypesInfo.Instance.FindTypeInfo(t.FullName);
var member = currenttypeinfo.FindMember("GCRecord");
if (member != null && !member.IsReadOnly)
member.SetValue(obj, null);
else if (currenttypeinfo.IsDomainComponent)
{
System.Reflection.MethodInfo method = obj.GetType().GetMethod("SetMemberValue");
method.Invoke(obj, new object[] { "GCRecord", null });
}
}
public virtual IEnumerable<AuditDataRecord> SearchAuditTrail(DateTime start, DateTime end, string query, params string[] operationtypes)
{
// Perform a filtered query against your custom audit trail and convert the results into Audit Data Records. For example:
string auditquery = "{your query here}";
ICustomAuditTrail audittrail = CustomAuditTrailFactory.Create();
var auditdata = audittrail.QueryAuditTrailData(auditquery);
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace)).DistinctBy(x => x.AuditedObject);
}
public virtual void Dispose()
{
// Clean up any necessary resources. For example:
ObjectSpace = null;
AuditTrailService = null;
}
}
Finally, configure the Audit Trail module to use your custom storage class. In your platform-specific Module.cs file, subscribe to the Application.SetupComplete event, retrieve the Llamachant Audit Trail module from the application’s modules collection, and set its AuditTrailStorageType property:
private void Application_SetupComplete(object sender, EventArgs e)
{
var auditmodule = Application.Modules.FindModule<LlamachantFrameworkAuditTrailModule>();
if(auditmodule != null)
auditmodule.AuditTrailStorageType = typeof(CustomAuditDataItemPersistentStorage);
}
Begin by creating a class that inherits from LlamachantFramework.AuditTrail.NonPersistent.AuditDataRecord. This class acts as a wrapper around your custom audit data and must override the following members:
If your custom audit trail stores object references in a non-standard format (e.g., IDs or serialized strings), you may also need to implement a ResolveObject method to convert these into usable object instances.
Example:
[DomainComponent]
public class CosmosAuditDataRecord : AuditDataRecord
{
public override object GetAuditedObject() => AuditedObject;
public override object GetNewObject() => NewObject;
public override object GetOldObject() => OldObject;
}
Create an extension method on the object type returned by your audit trail queries. This method should construct and return an instance of your custom AuditDataRecord, transforming the raw audit data into the required format.
Example:
public static class AuditDataItemPersistentExtensions
{
public static AuditDataRecord CreateAuditDataRecord(this AuditDataItem record, IObjectSpace space)
{
CustomAuditDataRecord customAuditDataRecord = space.CreateObject<CustomAuditDataRecord>();
customAuditDataRecord.ModifiedOn = record.ModifiedOn;
customAuditDataRecord.OperationType = record.OperationType;
customAuditDataRecord.PropertyName = record.PropertyName;
customAuditDataRecord.OldValue = record.OldValue;
customAuditDataRecord.NewValue = record.NewValue;
customAuditDataRecord.OldObject = record.OldObject;
customAuditDataRecord.NewObject = record.NewObject;
customAuditDataRecord.AuditedObject = record.AuditedObjectId;
customAuditDataRecord.UserName = record.UserName;
return customAuditDataRecord;
}
}
Create a new class that implements IAuditTrailStorage. This class defines how the audit trail module communicates with your custom data source. Implement the following methods:
Example:
public class CustomDataItemPersistentStorage : IAuditTrailStorage
{
protected IObjectSpace ObjectSpace;
protected IAuditTrailService AuditTrailService;
public virtual void Initialize(IObjectSpace objectspace)
{
ObjectSpace = objectspace;
AuditTrailService = ObjectSpace.ServiceProvider.GetService<IAuditTrailService>();
}
public virtual IEnumerable<AuditDataRecord> GetAuditTrail(object obj, params string[] operationtypes)
{
// Query your audit trail and create Audit Data Records. For example:
var auditdata = AuditTrailService.GetRecords();
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace));
}
public virtual void MarkObjectsAsRestored(List<object> restoredobjects, string oldvalue, string newvalue)
{
// Mark objects as "restored". For example:
foreach (object obj in restoredobjects)
{
((EFCoreObjectSpace)ObjectSpace.ForType<AuditDataItemPersistent>()).GetAuditTrailService().SaveCustomData((data, wr) =>
{
data.OldValue = oldvalue;
data.NewValue = newvalue;
data.AuditedObject = wr(obj);
data.ModifiedOn = DateTime.Now;
});
}
}
public virtual void RemoveDeferredDeletionValue(object obj)
{
// Restore soft-deleted objects by removing their deferred deletion marker. For example:
Type t = ObjectSpace.ForType(obj.GetType()).GetObjectType(obj);
ITypeInfo currenttypeinfo = XafTypesInfo.Instance.FindTypeInfo(t.FullName);
var member = currenttypeinfo.FindMember("GCRecord");
if (member != null && !member.IsReadOnly)
member.SetValue(obj, null);
else if (currenttypeinfo.IsDomainComponent)
{
System.Reflection.MethodInfo method = obj.GetType().GetMethod("SetMemberValue");
method.Invoke(obj, new object[] { "GCRecord", null });
}
}
public virtual IEnumerable<AuditDataRecord> SearchAuditTrail(DateTime start, DateTime end, string query, params string[] operationtypes)
{
// Perform a filtered query against your custom audit trail and convert the results into Audit Data Records. For example:
string auditquery = "{your query here}";
ICustomAuditTrail audittrail = CustomAuditTrailFactory.Create();
var auditdata = audittrail.QueryAuditTrailData(auditquery);
return auditdata.Select(x => x.CreateAuditDataRecord(ObjectSpace)).DistinctBy(x => x.AuditedObject);
}
public virtual void Dispose()
{
// Clean up any necessary resources. For example:
ObjectSpace = null;
AuditTrailService = null;
}
}
Finally, configure the Audit Trail module to use your custom storage class. In your platform-specific Module.cs file, subscribe to the Application.SetupComplete event, retrieve the Llamachant Audit Trail module from the application’s modules collection, and set its AuditTrailStorageType property:
private void Application_SetupComplete(object sender, EventArgs e)
{
var auditmodule = Application.Modules.FindModule<LlamachantFrameworkAuditTrailModule>();
if(auditmodule != null)
auditmodule.AuditTrailStorageType = typeof(CustomAuditDataItemPersistentStorage);
}