Custom Property Names in Audit.NET: Handling JSON Serialization in Entity Changes

Recently, I was tasked with building an audit trail log for a legacy WinForms VB.NET project that uses Entity Framework v6.4 on .NET 4.5. Rather than reinventing the wheel, I opted for an open-source library like Audit.NET v20.2.4.

The default settings are quite nice, as they list all changes by column name and map the original value to the updated value. However, what if you want to use human friendly model property name as column names in the changes list in the audit log? For example, my models already use <Display> attributes like so:

<Display(Name:="Witness")>
Public Property Personnel2 As String

<Display(Name:="Another Personnel")>
Public Property Personnel1Lv As String

<Display(Name:="Another Witness")>
Public Property Personnel2Lv As String

After making updates to the model, your default audit event will look like this:

Screenshot of a JSON Entity Framework event log showing database update entries, including changes to personnel-related columns.
A JSON structure displaying an Entity Framework event log with changes to the “Personnel1Lv” and “Personnel2Lv” columns.

As you can see, the columnNames were not affected by this attribute. So, what needs to be done in the list of changes to support custom property names in this location?

Note, that Audit.NET uses two JSON serializers:

  • System.Text.Json for .NET5 and above
  • Json.NET for lower frameworks

So, in my case, I am using Json.NET. The good news is that Json.NET already provides the <JsonProperty> attribute specifically for this purpose. Therefore, I adjust my model accordingly:

<Display(Name:="Witness")>
<JsonProperty("Witness")>
Public Property Personnel2 As String

<Display(Name:="Another Personnel")>
<JsonProperty("Another Personnel")>
Public Property Personnel1Lv As String

<Display(Name:="Another Witness")>
<JsonProperty("Another Witnes")>
Public Property Personnel2Lv As String

As a result, if you enable IncludeEntityObjects() in Audit.EntityFramework.Configuration, the property names are affected in the Entity field but not in the Changes field. Could this be changed?

Screenshot of an Entity Framework event log displaying database updates, including changes to personnel and witness fields using display attributes.
A JSON log from an Entity Framework event showing column name changes and mapped display properties in an audit log.

Apparently, the library does not provide a setting to automatically use human-friendly column names. However, it does offer some post-processing hooks that you can use to achieve this behavior:

Public MustInherit Class AuditDbContext
  ...
  Public Overridable Sub OnScopeSaved(auditScope As IAuditScope)
  Public Overridable Sub OnScopeSaving(auditScope As IAuditScope)
  Public Overridable Sub OnScopeCreated(auditScope As IAuditScope)
  ...
End Class

You can use these hooks in whatever way best suits your needs. In my case, I used the following method, which does exactly what you’d expect: it reads the DisplayAttribute or JsonPropertyAttribute and uses their properties to dynamically change column names.

Public Overrides Sub OnScopeCreated(scope As IAuditScope)
    MyBase.OnScopeCreated(scope)

    Dim [event] = scope.GetEntityFrameworkEvent()
    If [event] Is Nothing OrElse [event].Entries Is Nothing Then Return

    For Each item In [event].Entries
        If item.Entity Is Nothing Then Continue For

        Dim entityType = item.Entity.GetType()
        If item.Changes Is Nothing Then Continue For

        For Each change In item.Changes

            Dim propInfo = entityType.GetProperty(change.ColumnName)
            If propInfo Is Nothing Then Continue For

            Dim displayAttrs = propInfo.GetCustomAttributes(GetType(DisplayAttribute), True)
            If displayAttrs IsNot Nothing AndAlso displayAttrs.Length > 0 Then
                Dim displayAttr = TryCast(displayAttrs(0), DisplayAttribute)
                If displayAttr IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(displayAttr.Name) Then
                    change.ColumnName = displayAttr.Name
                    Continue For
                End If
            End If

            Dim jsonAttrs = propInfo.GetCustomAttributes(GetType(JsonPropertyAttribute), True)
            If jsonAttrs IsNot Nothing AndAlso jsonAttrs.Length > 0 Then
                Dim jsonAttr = TryCast(jsonAttrs(0), JsonPropertyAttribute)
                If jsonAttr IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(jsonAttr.PropertyName) Then
                    change.ColumnName = jsonAttr.PropertyName
                End If
            End If
        Next
    Next
End Sub