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;

Model Binding an Array from a Form Post | Asp.Net MVC & Asp.Net Core

if you form post html form fields:

<li>
  <label for="name">Your name<span>*</span></label>
  <input type="text" id="name" name="Name" />
</li>
<li>
  <label for="phoneNumber">Telephone<span>*</span></label>
  <input type="tel" id="phoneNumber" name="PhoneNumber" />
</li>

to a Controller using the [FromForm] attribute, AspNet will modelbind the form fields to your parameters by matching the name= attribute of the input elements to Property names on the parameter class:

class MyController : Controller
{
    public IActionResult Index([FromForm]Contact contact){ ... }
}

// ----
public class Contact
{
    public string Name {get;set;}
    public string PhoneNumber {get;set;}
}

But what about model binding an array — for instance, if you have a list of question inputs, and want to store the answers in a list?

If you name each field as if it were an array element:

 <fieldset id="q1">
     <label class="question">Question 1</label>
     <ul class="form-style">
        <li>
            <input type="radio" id="c1" name="answers[0]" value="@c1"/>
            <label for="c1">@c1</label>
        </li>
        <li>
            <input type="radio" id="c2" name="answers[0]" value="@c2"/>
            <label for="c2">@c2</label>
        </li>
    </ul>
  </fieldset>
    <fieldset id="q2">
        <label class="question">Question 1?</label>
        <ul class="form-style">
            <li>
                <input type="radio" id="b1" name="answers[1]" value="@b1"/>
                <label for="b1">@b1</label>
            </li>
            <li>
                <input type="radio" id="b2" name="answers[1]" value="@b2"/>
                <label for="b2">@b2</label>
            </li>
            <li>
                <input type="radio" id="b3" name="answers[1]" value="@b3"/>
                <label for="b3">@b3</label>
            </li>
            <li>
                <input type="radio" id="b4" name="answers[1]" value="@b4"/>
                <label for="b4">@b4</label>
            </li>
        </ul>
    </fieldset>

then AspNet will bind the submitted fields named answers[0], answers[1], ... to an array Property in your class with the matching name:

public class Questions
{
    public string[] Answers {get;set;}
}

.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.