I am working on the back end for a web-based SAAS application, written in C#, and using .NET Core 2.1. The database is mostly handled by Entity Framework with custom code that supports the architecture as a whole.
I had the simple requirement of populating a column in our database with the “owner” (or creator) of the record. The owner is an application user, and it is completely dependent on the context of the current request. I wanted to make this requirement transparent to the rest of the application.
Limitations on an entity
The goal is to get a reference to an ICurrentUserProvider
into the entities so they can update their fields appropriately. The obvious (and, as it turns out, somewhat naïve) solution is to inject an instance of the interface into the entities. I will note again, that this is probably not the best approach to solve this problem, but I will carry on.
When creating an entity instance, Entity Framework supports some limited forms of dependency injection. It will happily create an entity that has a constructor that requires the DbContext
, for example. It will not, however, use the IServiceCollection
of the application to inject an arbitrary service. I have seen notes that this feature is coming, but as of this writing, workarounds are necessary.
An attempt at a solution
The first thing I did was make my DbContext
override implement the interface in question. To prevent pollution of the namespace, I made it an explicit implementation. Something like this:
public class CustomDbContext : DbContext, ICurrentUserProvider
{
private readonly ICurrentUserProvider _currentUserProvider;
public CustomDbContext(DbContextOptions options, ICurrentUserProvider currentUserProvider) : base(options)
{
_currentUserProvider = currentUserProvider;
}
ICurrentUserProvider.CurrentUser => _currentUserProvider.CurrentUser;
}
In other words, my CustomDbContext
is pretty much just a wrapper around the real CurrentUserProvider
. It does not make any sense to put the responsibility in this class. A glaring issue here is that the class is going to fill up with otherwise useless interfaces as the complexity of the system grows. But let’s move on.
Since the functionality we are implementing here is common throughout all of the entities in the system (or most of them, at least), I put it into a base class, BaseEntity
. The simplified for this discussion version follows:
public abstract class BaseEntity
{
protected ICurrentUserProvider CurrentUserProvider { get; }
public BaseEntity(ICurrentUserProvider currentUserProvider)
{
CurrentUserProvider = currentUserProvider;
ModifiedBy = CreatedBy = currentUserProvider.CurrentUser.UserName;
}
public string ModifiedBy { get; set; }
public string CreatedBy { get; private set; }
}
Now things start to get kind of cumbersome, in my opinion. Classes derived from this BaseEntity
class can’t get an ICurrentUserProvider
from Entity Framework when an entity is constructed from the database. An abstract DbContext
(think IDbContext
) won’t work, either. For testing and other purposes, we don’t want an entire concrete instance of CustomDbContext
, that would force everything to be integration tests!
So, derived classes now have two constructors. As an example:
public class User : BaseEntity
{
[UsedImplicitly]
private User(CustomDbContext context) : base(context)
{
}
public User(ICurrentUserProvider currentUserProvider) : base(currentUserProvider)
{
}
public Guid Uuid { get; set; }
[MaxLength(100)]
public string Login { get; set; }
[MaxLength(512)]
public byte[] HashedPassword { get; set; }
[MaxLength(128)]
public byte[] PasswordSalt { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
I know, right? An implicitly called private constructor just so EF can do its thing, and a public one that’s what we really want so that we aren’t too constrained when we test. We can’t even ask for the abstract DbContext
here, unless we want to cast it to the interface. If we’re doing that anyway, we may as well be explicit. Now imagine putting that pattern in every model class.
At this point, I played around with the idea of just injecting the IServiceCollection
into my context and letting the models get whatever services it needed from there. It may have made the concept easier to grasp, but it doesn’t remove the need for the double constructor pattern, and it complicates the unit tests
The only saving grace for this pattern was that I didn’t have to think much about the unit test setups. I could just inject a mock of my interface into the entity, and go. But there is a better solution, in a future post.