Package Description
License
—
Deps
2
Install Size
—
Vulns
✓ 0
Published
May 24, 2022
$ dotnet add package Quarterware.AuditMapsIsaac S - 2022
Audit Maps is a audit or change log software solution. It can map generic objects and then generate logs files for you to use as you will.
AuditRecord {
List<string> Path;
string AuditName;
object NewValue;
object OldValue;
}
Path: a collection of object path to mapped property.AuditName: the final title and often final path segment.NewValue: the new property value.OldValue: the previous property value.Many of these values can be overridden. Values are dependent on use-case.
The package provides an ASP configuration extension to streamline implementation. Note, this service is a singleton and only builds once on the first request and then is reused.
IServiceCollection.AddAuditMaps(AuditMapperBuilder config);
Please provide the AuditMapperBuilder.
Please note, you don't need to use the service extension to use AuditMaps. All you need to do is configure the builder instance and call the AuditMapperBuilder.Build(), which returns a built AuditMapper instance.
Configure It!
To configure your AuditMapperBuilder, simply use the .Map<>(string profile) method. This is a generic method, so the target object must be provided (e.g. .Map<MyObject>).
profile parameter exists because multiple maps can exist of the same object (so you can have different maps etc), so the profile allows the mapper to differentiate between them..Map<>(string profile) returns a AuditMapBuildingInstance object. This will provide the .AtProperty(...) method which us allow to target specific properties in objects.
.AtProperty( target , property methods )
Usage Example
myAuditMapperBuilder.Map<MyObject>("showCaseProfile")
.AtProperty(x => x.Id, x => x.Ignore())
.AtProperty(x => x.Name, x => x.Name("User Name")
.AtProperty(x => x.Child, x => x.Name("Only Child").Recursive()));
var auditMapper = myAuditMapperBuilder.Build();
Note property methods can easily be linked.
.Pathless().Name("Test").Recursive() ...
.Ignore() .AtProperty(..., x => x.Ignore())
.Ignore() tells the builder to ignore this property no matter what.
.Pathless() .AtProperty(..., x => x.Pathless())
.Pathless() tells the mapper to not include the property in the final record path, but still include it.
.Name(string name) .AtProperty(..., x => x.Name("Awesome Name"))
.Name(string name) overrides records final name. If a path override isn't declared, the path segment will also reflect this overridden name.
.Recursive() .AtProperty(..., x => x.Recursive())
.Recursive() if the property values (old and new) are not null, the mapper will attempt to retrieve records for the sub value.
NOTE: AuditMapper can only map objects that have been configured with AuditMapperBuilder.
.TransformPath(string path) .AtProperty(..., x => x.TransformPath("Awesome Path"))
.TransformPath(string path) overrides a property's path segment.
.NullValue(object Value) .AtProperty(..., x => x.NullValue("Default Value"))
.NullValue(object Value) allows you to declare a value override when the property value is null.
.NullComparisonValue(object Value) .Property(..., x => x.NullComparisonValue("Not Null"))
.NullComparisonValue(object Value) declares a value override when the property value is matched with a null value.
Value override examples
if NullValue("N/A") is declared, and NewValue.ThisProperty == null, NewValue.ThisProperty's returning value will be "N/A" rather than null in the record. This is the same for OldValue.ThisProperty.
if NullComparisonValue("OBJECT") is declared, and OldValue.ThisProperty == null, as long as NewValue.ThisProperty != null, NewValue.ThisProperty will return as "OBJECT" rather than it's actual value. This is the same the other-way-round for OldValue.ThisProperty.
.SmartName() .AtProperty(..., x => x.SmartName())
.SmartName() exists to save memory after build. While .Name(string name) takes a name parameter, you'll see this flag doesn't. This is because .SmartName() generates the name from the property name on mapping runtime (or everytime you retrieve the records). This way the AutoMapper instance doesn't need to retain a string. It splits the name from camel case into multiple segments. NOTE: it doesn't currently support splitting numbers.
Examples
myNameVariable = "my Name Variable"
GivenName = "Given Name"
MyPassword123 = "My Password123"
.AsCollection<>(IAuditMapBuildingCollection<> configuration) .AtProperty(..., x => x.AsCollection(...))
This method allows you to define flags for children in a collection property (property of type IEnumerable). More bellow. This method is a generic method and you must specify the item type of the collection like so .AsCollection<MyObject>
NOTE: This collection property methods are only call if the collection property is flagged as .Recursive(). Otherwise it'll just compare the two collection properties together (old and new) and ignore the items.
.Pathless()
.AtProperty(..., x => x.AsCollection<MyObject>(c => c.Pathless()))
.Name(string name)
.AtProperty(..., x => x.AsCollection<MyObject>(c => c.Name("Item Name")))
.Recursive()
.AtProperty(..., x => x.AsCollection<MyObject>(c => c.Recursive()))
.TransformPath(string path)
.AtProperty(..., x => x.AsCollection<MyObject>(c => c.TransformPath("Item Path")))
.NullValue(object Value)
.AtProperty(..., x => x.AsCollection<MyObject>(c => c.NullValue("N/A")))
.NullComparisonValue(object Value)
.AtProperty(..., x => x.AsCollection<MyObject>(c => c.NullComparisonValue("Default Value")))
.MapName(Func<TModel, string> map)
.AtProperty(..., x => x.AsCollection<MyObject>(c => c.MapName(m => { ... })))
The .MapName(Func<TModel, string> map) method allows users to override the record name dynamically. By this I mean you can use item's data to define the item's record name.
Lets say you wanted every item in a list of users listed by the user name, you'd do something like this:
.AtProperty(x => x.Users, x => x.Recursive().AsCollection<User>(c => c.MapName(m => m != null ? m.Username : "Item")));
.Determine(Func<TModel, TModel, bool> determine) .AtProperty(..., x => x.AsCollection<MyObject>(c => c.Determine((oldVal,newVal) => { ... })))
If you want a functioning collection comparison you kind of need this. This method allows you to define what items are the same. This way our AuditMapper knows which items to compare for changes.
Determination function example.
.AsCollection(c => c.Determine((oldUser, newUser) => oldUser.Id == newUser.Id))
.NewItem(string message) .AtProperty(..., x => x.AsCollection<MyObject>(c => c.NewItem("Item Added")))
.NewItem(Func<TModel, string> message) .AtProperty(..., x => x.AsCollection<MyObject>(c => c.NewItem(model => {...})))
The .NewItem(...) methods are used to define a display value when a item exists in the new collection but not the old one (meaning it's been added and is new). You can either just define a plane string, or you can create a delegate Func which, like .Determine(...) and .MapName(...) allow you to generate the method based on the item context.
.MissingItem(string messafe) .AtProperty(..., x => x.AsCollection<MyObject>(c => c.MissingItem("Item Added"))
.MissingItem(Func<TModel, string> message) .AtProperty(..., x => x.AsCollection<MyObject>(c => c.MissingItem(model => {...})))
The .MissingItem(...) methods act similarly to the .NewItem(...) methods, but only apply when an item is missing or has been removed.
To generate your change log records, you simply need to call the
AutoMapper.Retrieve<>(TModel oldModel, TModel newModel, string profile, string initialPath)
method, or alternatively await the AutoMapper.RetrieveAsync method with the same parameters to get a asynchronous result.