4

I am setting up a MVC 5, WebApi 2, Entity Framework solution. I want to insert audit fields in my database without writing boilerplate code to do this every time. My database entities are in their own separate project that references EntityFramework.

So far I have:

    public class MyDbContext : IdentityDbContext<ApplicationUser>
    {
        public MyDbContext(/*Autofac can inject stuff here*/)
        {
        }

        public override int SaveChanges()
        {
            // Updates ModifiedBy, CreatedBy, ModifiedOn, CreatedOn fields
            DbContextHelper.TrackSaveChanges(ChangeTracker, userName);

            return base.SaveChanges();
        }
    }

The logic inside TrackSaveChanges() is not important, this simply loops over all changed entities and sets the values on the fields. Nothing too clever.

The issue is getting that userName inside of my DbContext derived class. I am thinking the best way is to inject this on the MyDbContext constructor?

There are suggestions on using HttpContext.Current.User, but I prefer not to add a Web dependency to my data project. Also a direct reference to HttpContext will compromise unit-testability.

  • I have tried in Autofac builder.Register<IPrincipal>(ip => HttpContext.Current.User); to inject an IPrincipal into the dbcontext, but this throws an exception as the HttpContext.Current.User is null at the time of creation.
  • If I have to, I'd prefer to use HttpContextBase in place of HttpContext.

Is there a clean way to do this? I am using Entity Framework 6, WebAPI 2, ASP NET Identity and Autofac.

Ali
  • 1,462
  • 2
  • 17
  • 32

1 Answers1

4

You can inject an interface instead of the string value itself:

interface IUserNameProvider
{
    string GetUserName();
}

The concrete implementation will use HttpContext.Current.User, but this approach will not compromise testability because an interface can be easily mocked.

interface HttpContextUserNameProvider : IUserNameProvider
{
    public string GetUserName()
    {
        return HttpContext.Current.User;
    }
}

Client code example:

public class MyDbContext : IdentityDbContext<ApplicationUser>
{
    internal IUserNameProvider _userNameProvider;

    public MyDbContext(IUserNameProvider userNameProvider)
    {
        _userNameProvider = userNameProvider;
    }

    public override int SaveChanges()
    {
        string userName = _userNameProvider.GetUserName();
        // ...
    }
}
Sergey Kolodiy
  • 5,829
  • 1
  • 36
  • 58
  • yes, I am thinking the same thing now though I was hoping I could get away using the existing IPrincipal. Also do you know of a way to get the username from HttpContextBase? – Ali Jan 29 '16 at 12:26