Audit
Audit is a proprietary product designed to track changes to any and all entities in a user’s system.
Creating an audit message
To log an audit on an entity, send a CreateAuditEventCommand through the Greyhound mediator. The properties for a CreateAuditEventCommand are:
public CreateAuditEventCommand( AuditTypeId AuditTypeId, EntityId EntityId, string EntityLabel, IPrincipal RequestedBy, string? Description, IReadOnlyList<FieldChange>? FieldChanges)AuditTypeId
The AuditTypeId is a StronglyTypedId backed by an int value. This type is defined in the LeverageSoftware.Audit.Abstractions package. Your project will probably have a class of full of static AuditTypeId to define the integer identifiers for the entities in your project.
EntityId
The EntityId is a StronglyTypedId backed by a string value. This type is defined in the LeverageSoftware.Audit.Abstractions package. This is meant to be identifier for the project-specific entity you are auditing. For example, a User with Id = 7. The EntityId would be "7".
EntityLabel
The EntityLabel is meant to be a more human-friendly representation of the entity. For example, a User with Id = 7 and Name = "Santa Claus". It is better to show “User: Santa Claus” than “User: 7”.
RequestedBy
RequestedBy is the IPrincipal of the user making the request that triggered this audit. For example, the user who edited an Employee and clicked “Save”. IPrincipal is a type defined by LeverageSoftware.Greyhound and used throughout our systems.
Description
The Description is the text string that will be displayed representing the change. See the section Descriptions for a more thorough discussion on how these should be constructed.
FieldChanges
FieldChanges is a listing of all the fields that where changed on the entity, plus the old and new values. It allows us to show a grid of all the changes
Descriptions
The Description is the text string that will be displayed representing the change.
Convention of Descriptions
When creating the description of an audit, the message should read as if you are looking at that particular entity. For the following examples, assume we have an Employee entity.
Some conventional audit messages would be:
“This employee was created.”
“This employee was deleted.”
“This employee was updated.”
Linked Entities
In some cases, two entities may be associated. Using our Employee example, that Employee may be associated to a different entity, like a Department. In that case, we want the Description to mention the other entity, e.g.:
“This employee was associated to the ‘Accounting’ department.”
Or, if a user has a role associated to them:
“This user had the ‘Administrator’ role granted.”
Markdown Support
The Description supports basic markdown support. So in the cases above, we may want to link the Description to the other entity.
“This employee was associated to the [Accounting](/department/1) department.”
This would be rendered with a link to the department the user can click:
“This employee was associated to the Accounting department.”
UniversalLinks
Your project will probably have a UniversalLinks helper class defined to build the markdown for the URLs. For example:
Employee employee = ...;Department associatedDepartment = ...;
CreateAuditEventCommand cmd = new( Description = $"This employee was associated to the {UniversalLinks.Department(associatedDepartment)} department.");When a user is looking at the audit on the employee, this link gives them an easy way to jump to the department page if they need it.
AuditableNotification
The audit product provides a utility abstract record AuditableNotification. This helper defines properties needed to generate a CreateAuditEventCommand. The idea is that certain notifications within the system should automatically be audited. For example, an EmployeeCreatedNotification should be audited. Greyhound can handle that auditing for you if your notification extends AuditableNotification.
public record EmployeeCreatedNotification(Employee Employee, IPricipal RaisedBy) : AuditableNotification(RaisedBy){ public override AuditTypeId AuditTypeId => AuditTypes.Employee; // AuditTypes is defined in your project public override EntityId EntityId => EntityId.From(Employee.Id); public override string EntityLabel => Employee.Name; public override string Description => "This employee was created."}Just by calling mediator.PublishAuditable(new EmployeeCreatedNotification(...)), an audit message will also be created.
AdditionalAuditEvents
Frequently, we will have an audit that impacts two entities. Using our Employee example from above, when the Employee is associated to a Department, we also want to create an audit on the Department that an Employee was added.
The AuditableNotification provides an easy way to audit both entities:
public record EmployeeDepartmentUpdatedNotification(Employee Employee, Department Department, IPricipal RaisedBy) : AuditableNotification(RaisedBy){ public override string Description => $"This employee was added to the {UniversalLinks.Department(Department)} department.";
public override IEnumerable<CreateAuditEventCommand> AdditionalAuditEvents { get { yield return new( AuditTypes.Department, EntityId.From(Department.Id), Department.Name, RaisedBy, $"This department had {UniversalLinks.Employee(Employee)} added."); } }}