Reputation: 91608
I'm running into a strange issue using dependency injection, adding in a Singleton for a type that comes from an outside assembly. This is using the Azure Function framework, but I'm not sure if that has anything to do with it or if this would repro with ASP.NET Core as well. My actual "real world" implementation is far, far too complicated to outline here, but I've managed to come up with a minimal repro.
This has two files.
Test.cs:
public class Test
{
private readonly AppConfig config;
public Test(AppConfig config)
{
this.config = config;
}
[FunctionName("Test")]
public async Task<IActionResult> RunAsync([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
{
return new OkObjectResult(config.SampleSetting);
}
}
Startup.cs:
namespace BugRepro
{
public class AppConfig
{
public string SampleSetting { get; set; } = "Test";
}
public static class Startup
{
public static void Configure(IServiceCollection services)
{
services.AddSingleton(new AppConfig());
}
}
}
This assembly has a reference to TestDll.dll
.
Startup.cs:
[assembly: FunctionsStartup(typeof(TestDll.Startup))]
namespace TestDll
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var asm = Assembly.LoadFile(Path.Combine(builder.GetContext().ApplicationRootPath, @"bin\BugRepro.dll"));
var type = asm.GetType("BugRepro.Startup");
var method = type.GetMethod("Configure");
method.Invoke(null, new object[] {builder.Services});
}
}
}
When the Azure Function runs, the TestDll.Startup.Configure
method is automatically called by the framework. This method loads the other assembly, BugRepro.dll
using reflection and calls the BugRepro.Startup.Configure
static method, passing the IServiceCollection
.
The BugRepro.Startup.Configure
static method adds a single instance of AppConfig
to the service collection. I can verify the instance has successfully been added to the service collection, and stepping all the way into the code, the right ServiceDescriptor and everything has been created. Everything appears perfect.
However, when I call the /Test
endpoint, I get the error:
[2021-02-04T06:39:10.502Z] Executed 'Test' (Failed, Id=22cfb587-3ba9-401f-b0a5-8688aed7bc9d, Duration=386ms)
[2021-02-04T06:39:10.506Z] Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'BugRepro.AppConfig' while attempting to activate 'BugRepro.Test'.
Basically, it acts like the Singleton has never been registered and it cannot resolve that type.
So, if I move the AppConfig
class from the BugRepro.dll to TestDll.dll (basically the AppConfig type is in the same DLL as my FunctionsStartup
class), the code works as expected.
Another way to fix it is to use an interface, which is defined in TestDll:
public interface IConfig
{
}
Then make AppConfig
implement that interface, then register the Singleton using its interface:
services.AddSingleton<IConfig>(new AppConfig());
However, then I have to inject IConfig
into the Test
constructor rather than AppConfig
which I do not want to do.
Is there a way to register a Singleton for a type that lives in an external assembly? To me, this seems like a bug in the .NET Core DI framework. Thanks!
Upvotes: 0
Views: 1171
Reputation: 172646
This problem is caused by your use of Assembly.LoadFile
. The LoadFile
method can cause the same assembly to be loaded twice in such way that the framework sees this as a totally different assembly, with different types.
The solution is to use Asssembly.Load
instead:
var assembly = Assembly.Load(AssemblyName.GetAssemblyName("c:\..."));
See for instance this related topic (for Simple Injector, but the problem is identical) for more information.
Upvotes: 3