HangfireIO / Hangfire

An easy way to perform background job processing in .NET and .NET Core applications. No Windows Service or separate process required
https://www.hangfire.io
Other
9.31k stars 1.69k forks source link

Bi-Directional Dependency with Structuremap and ASP.NET Core targeting .NET Framework #1405

Open stefan-toubia opened 5 years ago

stefan-toubia commented 5 years ago

My project is .net core 2.2 targeting .net framework 4.7.2. I was trying to get Hangfire (1.7.0 and 1.7.1) configured on a project today and kept running into the exception below exception (thrown by StructureMap).

I had no problem configuring this on an identical project targeting .net core.

I was able to get it working by rolling back Hangfire to 1.6.25.

StructureMap.Building.StructureMapBuildException: Bi-directional dependency relationship detected! Check the StructureMap stacktrace below: 1.) Instance of Hangfire.Client.IBackgroundJobFactory ('Hangfire.Client.BackgroundJobFactory, Hangfire.Core, Version=1.7.0.0, Culture=neutral, PublicKeyToken=null') 2.) new BackgroundJobFactory(Default of IJobFilterProvider, Default of IBackgroundJobFactory) 3.) Hangfire.Client.BackgroundJobFactory ('Hangfire.Client.BackgroundJobFactory, Hangfire.Core, Version=1.7.0.0, Culture=neutral, PublicKeyToken=null') 4.) Instance of Hangfire.Client.IBackgroundJobFactory ('Hangfire.Client.BackgroundJobFactory, Hangfire.Core, Version=1.7.0.0, Culture=neutral, PublicKeyToken=null') 5.) Container.GetInstance(Hangfire.Client.IBackgroundJobFactory) 6.) Container.TryGetInstance(Hangfire.Client.IBackgroundJobFactory)

at lambda_method(Closure , IBuildSession , IContext ) at StructureMap.Building.BuildPlan.Build(IBuildSession session, IContext context) at StructureMap.SessionCache.GetObject(Type pluginType, Instance instance, ILifecycle lifecycle) at StructureMap.SessionCache.GetDefault(Type pluginType, IPipelineGraph pipelineGraph) at StructureMap.Container.GetInstance(Type pluginType) at StructureMap.Container.TryGetInstance(Type pluginType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider) at Hangfire.HangfireServiceCollectionExtensions.GetInternalServices(IServiceProvider provider, IBackgroundJobFactory& factory, IBackgroundJobStateChanger& stateChanger, IBackgroundJobPerformer& performer) at Hangfire.HangfireApplicationBuilderExtensions.UseHangfireServer(IApplicationBuilder app, BackgroundJobServerOptions options, IEnumerable1 additionalProcesses, JobStorage storage) at Documents.API.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, IContainer container) in C:\Users\stefantoubia\source\repos\Traceability.API\Documents.API\Startup.cs:line 120 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app) at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() at Microsoft.AspNetCore.Hosting.Internal.WebHost.d26.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Hosting.WebHostExtensions.d5.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Hosting.WebHostExtensions.d__4.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host) at Documents.API.Program.Main(String[] args) in C:\Users\stefantoubia\source\repos\Traceability.API\Documents.API\Program.cs:line 41

odinserj commented 5 years ago

Wasn't able to reproduce this problem with the following configuration logic. Background job interface is registered in StructureMap only, and ASP.NET Core's dependency injection is successfully resolving it so Hangfire Server is able to create an instance correctly. I've checked in both .NET Core and .NET Framework.

Maybe there are other important details? How can I reproduce this?

Also that's odd that StructureMap knows about the following constructor, since it's an internal one (as it was in 1.6.X):

new BackgroundJobFactory(Default of IJobFilterProvider, Default of IBackgroundJobFactory)
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddHangfire(config => config.UseSqlServerStorage("Database=TestTest;Integrated Security=SSPI;"));

    var container = new Container();

    container.Configure(config =>
    {
        config.Populate(services);
        config.For<IMyInterface>().Use<MyClass>();
    });

    return container.GetInstance<IServiceProvider>();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();

    app.UseHangfireDashboard();
    app.UseHangfireServer();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}
<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
  <PackageReference Include="Microsoft.AspNetCore.CookiePolicy" Version="2.2.0" />
  <PackageReference Include="Microsoft.AspNetCore.HttpsPolicy" Version="2.2.0" />
  <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
  <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
  <PackageReference Include="Hangfire.Core" Version="1.7.1" />
  <PackageReference Include="Hangfire.AspNetCore" Version="1.7.1" />
  <PackageReference Include="Hangfire.SqlServer" Version="1.7.1" />
  <PackageReference Include="StructureMap.Microsoft.DependencyInjection" Version="2.0.0" />
</ItemGroup>
stefan-toubia commented 5 years ago

I went back and made a sample project to try and duplicate the issue and I isolated it to one of the scanning conventions I'm using in the structuremap configuration AssembliesAndExecutablesFromApplicationBaseDirectory();

public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            var connectionString = Configuration["connectionString"];

            services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services
                .AddHangfire(x => x.UseSqlServerStorage(connectionString));

            var container = new Container();
            container.Configure(config =>
            {
                config.Scan(scan =>
                {
                    scan.TheCallingAssembly();
                    // This is causing the issue
                    scan.AssembliesAndExecutablesFromApplicationBaseDirectory();
                    scan.WithDefaultConventions();
                });
                config.Populate(services);
            });
            return container.GetInstance<IServiceProvider>();
        }

I'm going to have to go back in and see why I've been using that in my configurations in the first place since I know it's not very common to use and can cause problems (just like this).

MasiumDev commented 5 years ago

I had this problem when I Release my app. there's no problem in debug mode !!!

StructureMapBuildException: Bi-directional dependency relationship detected!
Check the StructureMap stacktrace below:
1.) Instance of Hangfire.Client.IBackgroundJobFactory ('Hangfire.Client.BackgroundJobFactory, Hangfire.Core, Version=1.7.6.0, Culture=neutral, PublicKeyToken=null')
2.) new BackgroundJobFactory(*Default of IJobFilterProvider*, *Default of IBackgroundJobFactory*)
3.) Hangfire.Client.BackgroundJobFactory ('Hangfire.Client.BackgroundJobFactory, Hangfire.Core, Version=1.7.6.0, Culture=neutral, PublicKeyToken=null')
4.) Instance of Hangfire.Client.IBackgroundJobFactory ('Hangfire.Client.BackgroundJobFactory, Hangfire.Core, Version=1.7.6.0, Culture=neutral, PublicKeyToken=null')
5.) Container.GetInstance(Hangfire.Client.IBackgroundJobFactory)
6.) Container.TryGetInstance(Hangfire.Client.IBackgroundJobFactory)

I changed the hangfire version to 1.6.27, and problem solved. why 1.7+ version has this problem? Is it the conflict with StructureMap??