EntityFramework Core, Dependency Injection, and how to inject more dependencies when you can’t use the constructor

Perhaps having dependency injection in .Net for fifteen years has spoilt us a little. We expect any class to simply declare some dependency or other as a constructor parameter and by DI magic it all just works.

It doesn't work for everything though. Notably, it doesn't ‘just work’ in the dependency injection factories provided by Microsoft for EntityFramework. The DI factory .AddDbContext<T>(...) method expects your constructor to take a single DbContextOptions<T> options parameter. No other dependencies can be declared.

The injection technique offered instead by Entity Framework is to add IDbContextOptionsExtension instances to the DbContextOptions. This requires some boilerplate.

The Problem

You use microsoft.extensions.dependencyinjection. You use EntityFrameworkCore. You want to inject dependencies into a DbContext created by the DI factory. You can’t add constructor parameters. How to do it?

How To Create and Use an IDbContextOptionsExtension

As best I can see, the boilerplate for this is about five separate pieces of code. The final piece is a single line in your DI setup, in the same place you add out-of-the-box extensions like EnableDetailErrors:

services.AddDbContext<MyApplicationsDbContext>(
  (s,o) =>  {
               o.UseSqlServer(dbString)
                .EnableDetailedErrors()
                .EnableSensitiveDataLogging(isDevOrTest)
                // -----------------------------------------
                // 👇 add one line to inject your dependency
                .InjectMyThings( s.GetRequiredService<ThingToInject>() );
                // 👆 --------------------------------------
});

The Boilerplate

To make that one line work, you write four more pieces of code.

1. The DbContextOptionsBuilder extension method

/// <summary>
/// Boilerplate to make <see cref="DbOptionsMyInjectedThingExtension"/> available.
/// </summary>
public static class MyInjectedThingExtensionDbContextOptionsBuilderExtensions
{
    public static DbContextOptionsBuilder InjectMyThings(this DbContextOptionsBuilder builder, ThingToInject myInjectedThing)
    {
        (builder as IDbContextOptionsBuilderInfrastructure)
            .AddOrUpdateExtension(new DbOptionsMyInjectedThingExtension(myInjectedThing));
        return builder;
    }
}

2. Your custom IDbContextOptionsExtension class which holds the dependencies to inject

/// <summary>
/// Add MyInjectedThing to the <see cref="DbContextOptions"/>.
/// </summary>
public class DbOptionsMyInjectedThingExtension : IDbContextOptionsExtension
{
    ///<summary>This class carries the thing you want to inject</summary>
    public bool MyInjectedBool { get; }
    public ThingToInject EvenMoreInjectedThings { get; }

    public DbOptionsMyInjectedThingExtension(ThingToInject myInjectedThing)
    {
        MyInjectedBool = myInjectedThing.MyInjectedBool;
        EvenMoreInjectedThings = myInjectedThing;
        Info = new DbOptionsMyInjectedThingExtensionInfo(this);
    }
    public void ApplyServices(IServiceCollection services)
    {
        services.AddSingleton(this); 
    }
    public void Validate(IDbContextOptions options) { }
    public DbContextOptionsExtensionInfo Info { get; }
}

3. The EFCore MetaData

/// <summary>
/// Boilerplate to make <see cref="DbOptionsMyInjectedThingExtension"/> available.
/// </summary>
public class DbOptionsMyInjectedThingExtensionInfo : DbContextOptionsExtensionInfo
{
    /// <inheritdoc/>
    public DbOptionsMyInjectedThingExtensionInfo(DbOptionsMyInjectedThingExtension extension) : base(extension) { }

    /// <inheritdoc/>
    public override bool IsDatabaseProvider => false;

    /// <inheritdoc/>
    public override string LogFragment => "MyInjectedThing: {MyInjectedThing}";

    /// <inheritdoc/>
    public override int GetServiceProviderHashCode() => 0;

    /// <inheritdoc/>
    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => (other.Extension as DbOptionsMyInjectedThingExtension)?.MyInjectedBool == (Extension as DbOptionsMyInjectedThingExtension)?.MyInjectedBool;

    /// <inheritdoc/>
    public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
    {
        debugInfo["MyInjectedThing:"] = $"MyInjectedThing={(Extension as DbOptionsMyInjectedThingExtension)}";
    }
}

4. Finally, your DbContext constructor

    public MyApplicationsDbContext(DbContextOptions<MyApplicationsDbContext> options) 
          : base(options)
    {
        MyInjectedThing = options.FindExtension<DbOptionsMyInjectedThingExtension>()?.EvenMoreInjectedThings;
        MyInjectedBool = EvenMoreInjectedThings?.MyInjectedBool ?? false ;
    }
    protected bool MyInjectedBool;
    protected ThingToInject MyInjectedThing;

Blazor Cascading Parameters don’t “just work” with lambdas or method callback

TL;DR: Blazor Cascading Parameters are matched by Type not necessarily by Name. The simplest solution is to declare the Parameter type as Delegate. If you need to distinguish more than one delegate cascading parameter, then declare a named delegate type (that's a one-liner in C#) and use that as the parameter type.

The problem

You want to pass a callback as a cascading parameter. You try passing a lambda, or possibly a method, and create a CascadingParameter property, with matching name, of type Action or Func<...> or similar, to receive it. But the cascading parameter is always set to null.

Cascading parameters are matched up by Type, not by name, and who knows what the compiled type of a lambda will be? The C# reference tells you that that lambdas can be converted to delegates or expression trees but 'can be converted to' isn't good enough for matching up by Type.

The quickest fix

Declare your parameter to be of type Delegate. Most callable things in C# are of type Delegate.

// Declaration in the child component
[CascadingParameter]public Delegate OnChange { get; set; }

// Use in the child component. The null is what DynamicInvoke requires for 'no parameters'
OnChange.DynamicInvoke(null);

# In the parent container
<CascadingValue Value="StateHasChanged" >

If that's not good enough

If you must distinguish between several Delegate parameters, or just want to better express intent in your code, you can define a named delegate:

public delegate void StateHasChangedHook();

and use that as the Type of your CascadingParameter:

// Declaration in the child component
[CascadingParameter]public StateHasChangedHook OnChange { get; set; }

// Use in the child component is slightly simpler
OnChange();

# In the parent container
<CascadingValue Value="(StateHasChangedHook)this.StateHasChanged" >

When you use named delegates, you can invoke them with onChange() syntax instead of .Invoke() or DynamicInvoke(null).

You can inspect what was set in the child parameter

log.LogDebug("OnChange was set {OnChange}.MethodInfo={MethodInfo}",OnChange,OnChange?.GetMethodInfo())

Recall that Action<> and Func<> are themselves named delegates, not types. They are declared in the System assembly as for instance: public delegate void Action<in T>(T obj)

C# nullable refs and virtual vs abstract properties

Consider:

public class InitialStep2Sqlite : ScriptMigration
{
    protected override string UpPath => "Sqlite2.Up.sql";
    protected override string DownPath => "Sqlite2.Down.sql";
}

public abstract class ScriptMigration : Migration
{
  protected virtual string UpPath {get;}
  protected virtual string DownPath {get;}

  protected override void Up(MigrationBuilder mb) 
    => mb.RunSqlFile(UpPath);
  protected override void Down(MigrationBuilder mb) 
    => mb.RunSqlFile(DownPath);
}

which, compiled under nullable enable, says “[CS8618] Non-nullable property 'UpPath' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.”

Changing the property's virtual to abstact removes the warning.

The lesson: If your non-null proof of a property depends crucially on the subclass making it non-null, then making the parent property abstract not virtual is the correct statement of intent.

Generally, if a parent Member depends on the subclass for correctness – if the parent can't provide a correct implementation itself – then, logically, its correctness requires it to be abstract not virtual.

.Net Core Strong Typed Configuration Binding for Arrays | Microsoft.Extensions.Configuration

The .Net Core Microsoft.Extensions.Configuration package includes the ConfigurationBinder.Get<>() method which can read a config file and bind name-value entries to a class. This gives you strongly typed configuration:

public class FromTo
{
    public string From { get; init; }
    public string To { get; init; }
}

The method is an extension method for Configuration so you can call it straight off a Configuration instance. In an Asp.Net Startup.cs class, for instance:

services.AddSingleton(
    s=>Configuration
            .GetSection("fromto").Get<FromTo>());

will result in your services collection knowing to provide an FromTo class with the Properties populated from the configuration entries, matched by section:propertyname :

{
  "fromto:from": "[email protected]",
  "fromto:to": "[email protected]"
}

or if you use secrets:

cd <projectdirectory>
dotnet user-secrets init
dotnet user-secrets set fromto:from [email protected]
dotnet user-secrets set fromto:to [email protected]

That works great for the usual primitives - string, numbers, boolean — but what about binding an Array?

public class FromTo
{
    public string From { get; init; }
    public string To[] { get; init; }
}

the From field is still bound but the To field silently fails and results in a null value.

The magic trick is to add a colon-separated number suffix to each setting name:

{
  "fromto:from": "[email protected]",
  "fromto:to:0": "[email protected]",
  "fromto:to:1": "[email protected]",
  "fromto:to:2": "[email protected]",
}

Now Configuration.GetSection("fromto").Get<FromTo>() will successfully bind all 3 rows for "fromto:to into the public string To[] array.