Search code examples
c#asp.net-coreentity-framework-coreautofacin-memory-database

Autofac Dependency Injection for Azure Table and EntityFrameworkCore InMemoryDb


I have built a service class that communicates with both an Azure Table Storage Table, and an EntityFrameworkCore InMemory Database.

public sealed class NotificationService : INotificationService
{
    private readonly NotificationDatabaseContext _inMemoryCache;
    private readonly INotificationRepository _notificationRepository;

    public NotificationService(
        NotificationDatabaseContext inMemoryCache,
        INotificationRepository notificationRepository)
    {
        _inMemoryCache = inMemoryCache;
        _notificationRepository = notificationRepository;
    }

    public string Test()
    {
        return DateTime.UtcNow.ToShortTimeString();
    }
}

public sealed class NotificationRepository : INotificationRepository
{
    private readonly CloudTable _cloudTable;

    public NotificationRepository(CloudTable cloudTable)
    {
        _cloudTable = cloudTable;
    }
}

public class NotificationDatabaseContext : DbContext
{
    public NotificationDatabaseContext(DbContextOptions<NotificationDatabaseContext> dbContextOptions) : base(dbContextOptions) { }
    public DbSet<Notification> Notifications { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Notification>()
            .HasKey(x => x.RowKey);
    }
}

public sealed class Notification : TableEntity
{
    public bool HasBeenRead { get; set; }
}

I am using Autofac for dependency injection.

public sealed class AutofacAzureTableStorageModule : AutofacAzureModuleBase
{
    private static string _azureStorageConnectionString;
    private const string NOTIFICATIONS_AZURE_TABLE_STORAGE_CLOUD_TABLE_NAME = "Notifications";

    public AutofacAzureTableStorageModule(string azureStorageConnectionString)
    {
        _azureStorageConnectionString = azureStorageConnectionString;
    }

    protected override void Load(ContainerBuilder containerBuilder)
    {
        ConfigureAzureStorageAccount(containerBuilder, _azureStorageConnectionString);
        ConfigureServicesWithRepositories(containerBuilder);
        ConfigureAzureCloudTables(containerBuilder);
        ConfigureCloudTablesForRepositories(containerBuilder);
    }

    private static void ConfigureServicesWithRepositories(ContainerBuilder containerBuilder)
    {
        containerBuilder.RegisterType<NotificationService>()
                .AsImplementedInterfaces()
                .InstancePerLifetimeScope();
    }

    private static void ConfigureAzureCloudTables(ContainerBuilder containerBuilder)
    {
        containerBuilder.Register(x => x.Resolve<CloudStorageAccount>().CreateCloudTableClient());

        containerBuilder.Register(x =>
                GetCloudTable(x, NOTIFICATIONS_AZURE_TABLE_STORAGE_CLOUD_TABLE_NAME))
                .Named<CloudTable>(NOTIFICATIONS_AZURE_TABLE_STORAGE_CLOUD_TABLE_NAME);
    }

    private static void ConfigureCloudTablesForRepositories(ContainerBuilder containerBuilder)
    {
        containerBuilder.RegisterType<NotificationRepository>()
                .WithParameter(
                (x, y) => x.ParameterType == (typeof(CloudTable)),
                (x, y) => y.ResolveNamed<CloudTable>(
                NOTIFICATIONS_AZURE_TABLE_STORAGE_CLOUD_TABLE_NAME))
                .AsImplementedInterfaces();
    }
}

public abstract class AutofacAzureModuleBase : Module
{
    protected static void ConfigureAzureStorageAccount(ContainerBuilder containerBuilder, string azureStorageConnectionString)
    {
        containerBuilder.Register(c => CreateAzureCloudStorageAccount(azureStorageConnectionString));
    }

    private static CloudStorageAccount CreateAzureCloudStorageAccount(string azureStorageConnectionString)
    {
        if (String.IsNullOrEmpty(azureStorageConnectionString))
        {
            throw new Exception("Azure Storage connection string is null!");
        }

        return CloudStorageAccount.Parse(azureStorageConnectionString);
    }

    protected static CloudTable GetCloudTable(IComponentContext componentContext, string tableName)
    {
        var cloudTable = componentContext.Resolve<CloudTableClient>().GetTableReference(tableName);

        cloudTable.CreateIfNotExistsAsync().GetAwaiter();

        return cloudTable;
    }
}

Everything is configured in Program.cs file as follows:

// Add services to the container.
builder.Services.AddControllersWithViews();

// Configure EntityFrameworkCore InMemoryDatabase Cache
var notificationsInMemoryDatabaseName = builder.Configuration["EntityFrameworkCoreInMemoryDatabase:NotificationsDatabaseName"];
builder.Services.AddDbContext<NotificationDatabaseContext>(options =>
    options.UseInMemoryDatabase(notificationsInMemoryDatabaseName));
// Configure EntityFrameworkCore InMemoryDatabase Cache

// Configure Autofac Modules
var azureStorageConnectionString = builder.Configuration["AzureStorage"];
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here.
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
    builder.RegisterModule(new AutofacAzureTableStorageModule(azureStorageConnectionString));
});
// Configure Autofac Modules

var app = builder.Build();

The Autofac dependency injection isn't correct (it worked previously when I was simply using DI for an Azure Storage Table (without the EntityFrameworkCore InMemory Database)).

Please could someone advise/help me refactor the Autofac dependency injection, to work with the InMemory Database?

UPDATE:

The following exception is thrown:

enter image description here


Solution

  • I've managed to fix it by adding the following method to the Autofac module.

    private static void ConfigureEntityFrameworkCore(ContainerBuilder containerBuilder)
    {
        containerBuilder.Register(c => new DbContextOptionsBuilder<NotificationDatabaseContext>()
            .UseInMemoryDatabase(_entityFrameworkCoreInMemoryDatabaseNameForNotifications)
            .Options)
            .SingleInstance();
    
        containerBuilder.RegisterType<NotificationDatabaseContext>()
            .WithParameter(
                (x, y) => x.ParameterType == typeof(DbContextOptions<NotificationDatabaseContext>),
                (x, y) => y.Resolve<DbContextOptions<NotificationDatabaseContext>>())
            .InstancePerLifetimeScope();
    }
    

    Hopefully this helps someone in future.