2

Context

I am trying wireup Audit.Net in an MVC5 app with .Net Framework 4.8.

Dataprovider: EntityFramework

Output: Cosmos

DI Provider: Autofac

The Problem

I have tried the following call backs to try and write the username to the AuditEvent.

Configuration.AddOnCreatedAction(scope =>
{
    var username = HttpContext.Current.User.Identity.GetUserId();
    scope.SetCustomField("User2", username);
});
Configuration.AddOnSavingAction(scope =>
{
    var username = HttpContext.Current.User.Identity.GetUserId();
    scope.SetCustomField("User2", username);
});
Configuration.AddOnSavingAction(scope =>
{
    var user = AutofacDependencyResolver.Current.Resolve<IPrincipal>();
    scope.SetCustomField("User2", user.Identity.GetUserId());
});

These for work for synchronous calls to dbContext.SaveChanges(), when SaveChangesAsync() is called then I cannot access the current HttpContext as it is always null.

Any suggestions?

vick
  • 81
  • 7

2 Answers2

1

The key (which might help in search terms) is async. A quick search for async httpcontext mvc gets us to this existing question with a lot of information about async/await and HttpContext usage.

The super short version: in MVC, the HttpContext gets carried around in the thread synchronization context (which is also where, say, the culture and other thread settings are carried around) but it's expensive to cart that context from thread to thread so the default is to not do that. You need to enable it explicitly to work.

Here's a blog article explaining the various knobs you can turn in web.config to get it to work but the net result is, basically, to make sure <httpRuntime targetFramework="4.5" /> is set (well, set that value to 4.5 or higher).

If that's already set, then... maybe there's something else at play, like a call has the ConfigureAwait(false) set on it so it's not returning to a thread context that has the HttpContext. But, more than likely, that httpRuntime flag should fix it.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • Thanks a ton @travis-illig . While it did not fix the issue, I am a step closer and I learnt a lot. The runtime was already at 4.8. Audit.NET's code did not have a configureawait which would cause the context to be lost. The Db Save I was losing context in is when saving the User. Using `Microsoft.AspNet.Identity.UpdateAsync()` This function is probably the culprit. So I will now pull on that thread. – vick Oct 11 '21 at 23:38
0

You may capture the HttpContext earlier in the Asp .Net pipeline:

public interface IHttpContextContainer
{
    HttpContextBase HttpContextBase { get; set; }
}

In your controller or service you may attach the lifetime scope to your custom Audit event:

public abstract class MyAuditEvent : AuditEvent
{
    [JsonIgnore]
    public ILifetimeScope LifetimeScope { get; set; }
}

Add JsonIgnoreAttribute to prevent serializing the LifetimeScope.

In the global event handler you may now access the custom audit event:

Configuration
            .AddCustomAction(
                ActionType.OnScopeCreated,
                auditScope =>
                {
                    if (auditScope.Event is MyAuditEvent auditEvent)
                    {
                        var scope = auditEvent.LifetimeScope;
                        var httpContextContainer = scope.Resolve<IHttpContextContainer>();
                        // ...
                        // access HttpContextBase from httpContextContainer
                    }
                });
Edward Olamisan
  • 800
  • 1
  • 18
  • 28