1

I have a WebAPI project which is using Entity Framework, and the following is the SaveChanges that I overrides to monitor timestamp & etc. I wonder why when I'm moved AddAuditCustomField into the child Method, HttpContext became null. I'm using Audit.NET AuditDbContext.

        public override async Task<int> SaveChangesAsync()
        {

            AddAuditCustomField("url_endpoint",HttpContextHelper.GetUriEndpoint());
            return await SaveChangesAsync(true);
        }


        public async Task<int> SaveChangesAsync(bool invokeEvent)
        {
            try
            {

            //Placing here will be NULL 
           //AddAuditCustomField("url_endpoint",HttpContextHelper.GetUriEndpoint());
            return await SaveChangesAsync(true);
                if (invokeEvent)
                    OnItemSaveChanges?.Invoke();
                AddTimestamps();

                return await base.SaveChangesAsync();
            }
            catch (DbEntityValidationException e)
            {
                throw;
            }
        }

Below is the HttpContextHelper

namespace Test.Core.Helpers
{
    public class HttpContextHelper
    {
        public static  string GetUriEndpoint()
        {

            if (HttpContext.Current != null) return HttpContext.Current.Request.Url.AbsoluteUri;

            if (WebOperationContext.Current != null)
                return WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.OriginalString;

            if( HttpContextProvider.Current!=null) return HttpContextProvider.Current.Request.Url.AbsoluteUri;

            return null;
        }
    }
}

The Controller I'm calling it

  var entity = new Entity
                {
                    Name = "test"
                };
                  Db.Entities.Add(entity);
                try
                {
                    await Db.SaveChangesAsync();
                }
                catch (Exception e)
                {
                    if (e.IsDuplicateIndexError())
                    {
                        LogUtil.Error(message);

                    }
                    throw;
                }

Have been reading some other thread and they stated it's working Using HttpContext.Current in WebApi is dangerous because of async

And I'm targetting 4.7.1

thepirat000
  • 10,774
  • 4
  • 38
  • 63
AppeShopper
  • 265
  • 1
  • 2
  • 10
  • 1
    You need to show us the rest of the stack above `SaveChangesAsync()`, all the way up to the controller, and does anything in it specify `.ConfigureAwait(false)` or use `.Wait()` or `.Result`? – sellotape Dec 07 '18 at 19:33
  • 2
    Somewhere is code you are not showing you have `.ConfigureAwait(false)` https://stackoverflow.com/questions/13489065/best-practice-to-call-configureawait-for-all-server-side-code – Alexei Levenkov Dec 07 '18 at 19:34
  • @sellotape `.Wait()` or `.Result` are unlikely case is they would not lead to `null` as code will deadlock before that. – Alexei Levenkov Dec 07 '18 at 19:35
  • There is no .ConfigureAwait() or Wait() used. from above u can see, I'm able to get HttpContext if I don't nest the async. – AppeShopper Dec 07 '18 at 19:35
  • then it is `new Task`... Really you need [MCVE]. – Alexei Levenkov Dec 07 '18 at 19:37
  • @AlexeiLevenkov - yes I agree; I was trying to tease out whether there might be `Task.Run()`s in there as well in one succinct question. – sellotape Dec 07 '18 at 19:39
  • Your database logic shouldn't include any access to HttpContext. That's a violation of Separation of Concerns. You should refactor the code to remove that dependency. – mason Dec 07 '18 at 19:42
  • sorry I have forgotten to add, I'm using Audit.NET AuditDbContext – AppeShopper Dec 07 '18 at 19:43
  • @mason because I'm using Audit, username or IP has to be capture whenever there are changes toward the entity. What's the ideal solution to refactor? – AppeShopper Dec 07 '18 at 19:46
  • 1
    Grab the specific username/IP in the web layer, and pass them along to the data layer. Don't have the data layer depend on the HttpContext to obtain them. Don't let web layer implementation details leak into the data layer. – mason Dec 07 '18 at 20:27
  • Perhaps when you create the DbContext, you could pass in the IP address or username via the constructor, store it in a field, then utilize it later when you save changes. – mason Dec 07 '18 at 20:39

1 Answers1

0

If you are using the Audit.NET library you should already have the IP and user name on the AuditEvent.Environment object. Also there is an extension Audit.WebApi to log WebAPI actions, and it can be combined with the Audit.EntityFramework.

Also there is no need to pass or to get the HttpContext on the Db Context layer. Audit.NET already provides a mechanism to enrich the audit event data via custom actions, so you can put the code that depends on the HTTP context only on the web layer on your initialization code, for example on your WebAPI startup code (assuming you use asp.net core 2):

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    var svcProvider = services.BuildServiceProvider();
    // Add the Audit.NET custom action
    Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
    {
        // Additional information as a custom field
        var someData = svcProvider
            .GetService<IHttpContextAccessor>()
            .HttpContext.WhateverYouNeed;
        scope.Event.CustomFields["SomeData"] = someData;
    }
}
thepirat000
  • 10,774
  • 4
  • 38
  • 63