How can I populate my audit log UserId field when I don't have access to HttpContext?
Asked Answered
P

3

6

I have a solution with two projects, my API layer and my Data layer.

The Data layer is using Entity Framework, and does not know about ASP.NET WebApi.

The API layer is using ASP.NET WebApi.

I am planning on using Audit.NET to audit record changes.

Currently, I have installed Audit.NET, Audit.EntityFramework and Audit.WebApi in my API project.

I wish to use the Audit Filter Attribute. (config.Filters.Add(new AuditApiAttribute());)

My issue is, when I come to populate the EntityLog object, I also would like to populate the UserId field with the Id of the user currently performing an authorized action, or null if the action is anonymous.

As part of my Startup.cs, I run this function to configure Audit.NET:

private void ConfigureAudit()
        {
            Audit.Core.Configuration.Setup()
                .UseEntityFramework(x => x
                    .AuditTypeMapper(t => typeof(AuditLog))
                    .AuditEntityAction<AuditLog>((ev, entry, entity) =>
                    {
                        entity.UserId = ???;
                        // other fields...
                    })
                    .IgnoreMatchedProperties()
                );
        }

Obviously at this point I cannot use HttpContext.Current.User.Identity.GetUserId() as I'm not inside a controller, and I don't see a way of passing the UserId into the Audit event.

How can I retrieve the UserId at this point?

Pearlpearla answered 19/9, 2019 at 10:48 Comment(0)
S
3

In addition to Steve answer, I guess you should be able to add a Custom Field from a OnScopeCreated custom action, like this on your asp.net startup logic:

Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
    scope.SetCustomField("User", HttpContext.Current.User.Identity.GetUserId());
});

So you will have that field on the AuditEvent on your EF data provider, and should be able to retrieve like this:

Audit.Core.Configuration.Setup()
    .UseEntityFramework(_ => _
        .AuditTypeMapper(t => typeof(AuditLog))
        .AuditEntityAction<AuditLog>((ev, entry, entity) =>
        {
            var x = ev.CustomFields["User"];
            ...
        })
        .IgnoreMatchedProperties());

Note: if you are only interested in auditing entity framework DB contexts, you don't need the Audit.WebApi library. That one is for auditing the controller actions.

Sixtyfour answered 20/9, 2019 at 2:12 Comment(0)
P
3

A little something for ASP.NET Core...

Given that ASP.NET Core has removed HttpContext.Current in favor of DI-based access, I'd like to add some update to @thepirat000's answer to reflect one of the possible ways of accessing the HttpContext outside of the controller.

  1. In the ConfigureServices method in your Startup.cs, register the dependency:

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  2. Add parameter injection for the registered dependency in the method: Configure in Startup.cs, use that to retrieve the context and use that to set the custom field.

     public void Configure(/* your existing parameters... */, IHttpContextAccessor httpContextAccessor)
     {
          ...
    
         Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
         {
            scope.SetCustomField("User", httpContextAccessor.HttpContext.User.Identity.Name);
         });
     }
    
  3. Access the custom field as usual:

     Audit.Core.Configuration.Setup()
      .UseEntityFramework(_ => _
      .AuditTypeMapper(t => typeof(AuditLog))
      .AuditEntityAction<AuditLog>((ev, entry, entity) =>
       {
          var x = ev.CustomFields["User"];
          ...
       })
       .IgnoreMatchedProperties());
    
Pandit answered 5/9, 2020 at 22:40 Comment(0)
P
0

The approach I use is a IUserContextLocator interface which becomes a dependency to the DbContext or whatever class implements the auditing. An implementation of the interface is created in the web application and wired up through the dependency injection to use a constructor initialization to provide details from the current session. so that the DbContext or other audit service can resolve a current user structure from a method in the IUserContextLocator which in turn fetches from the session.

My current implementation is at work so I don't have an example handy, but I can add the guts of it tomorrow. A similar principle to it can be seen here: http://josephwoodward.co.uk/2014/06/sessions-asp-net-mvc-using-dependency-injection/

Essentially, the domain-level code won't have access to things like HttpContext.Current, but an implementation that is initialized in your web application can. By registering that implementation in your DI via a common interface your domain can reference, you can inject the dependency sourced by the web application, allowing you to access session data. This example calls it a Registry which might be a more accurate pattern for it.

Parasiticide answered 19/9, 2019 at 11:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.