Search code examples
asp.net-coreentity-framework-coreaudit-loggingaudit.net

ASP.NET Core Entity changing history


I have many controllers like this:

public class EntityController : Controller
{
    private readonly IEntityRepository _entity;

    public EntityController(IEntityRepository entity)
    {
        _entity = entity;
    }

    [Authorize]
    [HttpPut("{id}")]
    public async ValueTask<IActionResult> Put(int id, [FromBody] Entity entity)
    {
        if (entity == null || entity.Id != id) return BadRequest();
        var updated = await _entity.Update(entity);
        if (updated == null) return NotFound();
        return Ok(updated);
    }
}

I need to implement entities editing (audit) history.

And, since the method is marked as [Authorize], I need to log by which user it was edited. I'm looking at Audit.NET, but I didn't find a way to do it.


Solution

  • The Audit.NET EF Provider allows to customize the audit entity before saving it. This has to be done at the startup with a so-called AuditEntity Action: an action that is triggered for each entity being modified.

    So, you can make this action retrieve the user name from the current HttpContext and store it in a UserName property on your audit entities.

    On your asp net startup code, setup a way to obtain the current HttpContext and configure the action to retrieve the username from the context:

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // Add the HttpContextAccessor if needed.
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
            // Get the service provider to access the http context
            var svcProvider = services.BuildServiceProvider();
    
            // Configure Audit.NET
            Audit.Core.Configuration.Setup()
                .UseEntityFramework(x => x
                    .AuditTypeNameMapper(typeName => "Audit_" + typeName)
                    .AuditEntityAction((evt, ent, auditEntity) =>
                    {
                        // Get the current HttpContext 
                        var httpContext = svcProvider.GetService<IHttpContextAccessor>().HttpContext;
                        // Store the identity name on the "UserName" property of the audit entity
                        ((dynamic)auditEntity).UserName = httpContext.User?.Identity.Name;
                    }));
        }
    }
    

    This is assuming your audit entities have a common UserName property.

    If your Audit Entities already inherits from an interface or base class including the UserName, you can use the generic AuditEntityAction<T> instead.

    Audit.Core.Configuration.Setup()
        .UseEntityFramework(x => x
            .AuditTypeNameMapper(typeName => "Audit_" + typeName)
            .AuditEntityAction<IUserName>((evt, ent, auditEntity) =>
            {
                var httpContext = svcProvider.GetService<IHttpContextAccessor>().HttpContext;
                auditEntity.UserName = httpContext.User?.Identity.Name;
            }));