Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Services added with AddHostedService are unable to resolve registrations in ASP.NET Core 3 #754

Closed
sychare opened this issue Sep 30, 2019 · 8 comments
Labels
Milestone

Comments

@sychare
Copy link

@sychare sychare commented Sep 30, 2019

Description of the bug

Adding a service with AddHostedService<T>() is unable to resolve types registered with Simple Injector in ASP.NET Core 3. The same code worked in 2.2.

Trying to step through the code it appears that services added with AddHostedService() are being started (and subsequently resolved) earlier in Core 3 than in 2.2, causing registrations to not yet have been added. The Startup.Configure() method is not hit at the time the exception is raised.

Moving the registrations to occur at the end of ConfigureServices() doesn't help, as despite it fixing the missing registrations it is not possible to then call app.UseSimpleInjector() in Configure() as the hosted service being resolved before Configure() is called locks the container:

The container can't be changed after the first call to GetInstance, GetAllInstances, Verify, and some calls of GetRegistration. Please see https://simpleinjector.org/locked to understand why the container is locked. The following stack trace describes the location where the container was locked:

at SimpleInjector.Container.GetInstance[TService]()
at SimpleInjector.SimpleInjectorGenericHostExtensions.<>c__DisplayClass0_0`1.<AddHostedService>b__0(IServiceProvider _)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
at Test.Web.Program.Main(String[] args) in E:\projects\test\src\backend\Test.Web\Program.cs:line 14

Expected behavior

Type of HostedService is resolved.

Actual behavior

Exception:

The container can't be changed after the first call to GetInstance, GetAllInstances, Verify, and some calls of GetRegistration. Please see https://simpleinjector.org/locked to understand why the container is locked. The following stack trace describes the location where the container was locked:

To Reproduce

Replace the default Startup with the following:

public class HostedService : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

public class Startup
{
    private readonly Container container = new Container();

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();

        services.AddSimpleInjector(this.container, options =>
        {
            options.AddHostedService<HostedService>();
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.ApplicationServices.UseSimpleInjector(this.container);

        app.UseStaticFiles();

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

Additional context

  • Simple Injector 4.7.1
  • .NET Core 3
@sychare sychare added the question label Sep 30, 2019
@dotnetjunkie

This comment has been minimized.

Copy link
Collaborator

@dotnetjunkie dotnetjunkie commented Sep 30, 2019

Crap. Yet another unfortunate breaking change in ASP.NET Core v3. I will investigate this and report back to you when I found a workaround or have a fix available. Might take a few days for me to get to this point though.

@dotnetjunkie dotnetjunkie added bug task and removed question labels Sep 30, 2019
@dotnetjunkie

This comment has been minimized.

Copy link
Collaborator

@dotnetjunkie dotnetjunkie commented Sep 30, 2019

btw thank you for this detailed and excellent bug report. This is how I rather see everyone pointing their bugs and questions.

@dotnetjunkie

This comment has been minimized.

Copy link
Collaborator

@dotnetjunkie dotnetjunkie commented Sep 30, 2019

This problem is caused by the behavioral change of the new Host class compared to the previous WebHost class. I filed a bug.

As workaround, switch back to using WebHost in your Program.cs instead of the new Host, which is used in the default Visual Studio template. When creating a new ASP.NET Core 3.0 application, the Program.cs looks like this:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

You should change it to the following:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

The reason this works is because Host starts creating hosted services too early in the pipeline, which causes problems for containers like Simple Injector, Castle Windsor, and Ninject, that can't conform to the presented Microsoft container abstraction.

As it currently stands, there is no way for Simple Injector to work around this issue, as this behavior is embedded deeply into the Host class.

@dotnetjunkie dotnetjunkie added feature and removed bug labels Oct 30, 2019
dotnetjunkie added a commit that referenced this issue Nov 17, 2019
…plify migration for developers.

* Ensured that Microsoft's Logger<T> and StringLocalizer<T> are injected instead of Simple Injector-specific types.
 Fixed #754.
@dotnetjunkie dotnetjunkie added this to the v4.8 milestone Nov 20, 2019
@dotnetjunkie

This comment has been minimized.

Copy link
Collaborator

@dotnetjunkie dotnetjunkie commented Nov 20, 2019

I published beta2 release of v4.8 to NuGet that aims to fix this problem. The package marked some methods obsolete and guides you towards the right methods. Do note that the obsolete messages reference the documentation, but the documentation has not yet been updated.

I appreciate any feedback on this.

@dotnetjunkie

This comment has been minimized.

Copy link
Collaborator

@dotnetjunkie dotnetjunkie commented Nov 20, 2019

feature-754-v2 branch.

dotnetjunkie added a commit that referenced this issue Nov 20, 2019
…plify migration for developers.

* Ensured that Microsoft's Logger<T> and StringLocalizer<T> are injected instead of Simple Injector-specific types.
 Fixed #754.
@sychare

This comment has been minimized.

Copy link
Author

@sychare sychare commented Nov 22, 2019

Just given the v4.8 beta2 a spin and it seems to work for me after following the obsoletion messages.

@dotnetjunkie

This comment has been minimized.

Copy link
Collaborator

@dotnetjunkie dotnetjunkie commented Nov 22, 2019

Awesome. Great to hear. Thank you for your input @sychare.

@dotnetjunkie

This comment has been minimized.

Copy link
Collaborator

@dotnetjunkie dotnetjunkie commented Nov 24, 2019

We just release Simple Injector v4.8, which fixed this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.