NOTE :看到最后的编辑 .

试着了解如何在网络核心Web Api测试项目中实现Hangfire Simple Injector(已经在asp.net项目中使用过SI [但是在核心项目中设置核心是相当不同的野兽]和HF [使用默认容器])然后我可以继续使用它来修改使用默认Microsoft DI容器的现有项目 .

该项目正在使用这些包

Hangfire 1.6.17 HangFire.SimpleInjector.Core 0.0.1 SimpleInjector 4.0.12 SimpleInjector.Integration.AspNetCore 4.0.12 SimpleInjector.Integration.AspNetCore.Mvc 4.0.12

我的代码目前

启动

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Routing;
using Hangfire;
using TestSimpleInjector.Services;
using TestSimpleInjector.Controllers;
using TestSimpleInjector.Model;
using Newtonsoft.Json;
using SimpleInjector;
using Hangfire.SimpleInjector;
using SimpleInjector.Integration.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using SimpleInjector.Lifestyles;
using SimpleInjector.Diagnostics;

namespace TestSimpleInjector
{
public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    private readonly Container ApplicationContainer = new Container();

    public IConfigurationRoot Configuration { get; private set; }

    // ConfigureServices is where you register dependencies. This gets
    // called by the runtime before the Configure method, below.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add services to the collection.
        services.AddHangfire(config =>
            config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection")));

        services.AddMvc().AddJsonOptions(opt =>
        {
            opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        });

        services.AddSingleton(Configuration);
        //services.AddTransient<ITest, Test>();
        //services.AddDbContext<TestDbContext>(ServiceLifetime.Transient);
        var SimpleInjector = new SimpleInjectorJobActivator(ApplicationContainer, Lifestyle.Scoped);

        GlobalConfiguration.Configuration.UseActivator(SimpleInjector);

        IntegrateSimpleInjector(services);
    }

    private void IntegrateSimpleInjector(IServiceCollection services)
    {
        ApplicationContainer.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        services.AddSingleton<IControllerActivator>(
            new SimpleInjectorControllerActivator(ApplicationContainer));
        services.AddSingleton<IViewComponentActivator>(
            new SimpleInjectorViewComponentActivator(ApplicationContainer));

        services.EnableSimpleInjectorCrossWiring(ApplicationContainer);
        services.UseSimpleInjectorAspNetRequestScoping(ApplicationContainer);
    }

    private void InitializeContainer(IApplicationBuilder app)
    {
        // Add application presentation components:
        ApplicationContainer.RegisterMvcControllers(app);
        ApplicationContainer.RegisterMvcViewComponents(app);

        // Add application services. For instance:
        ApplicationContainer.Register<ITest, Test>(Lifestyle.Scoped);
        var registration = Lifestyle.Scoped.CreateRegistration(typeof(TestDbContext), ApplicationContainer);
        registration.SuppressDiagnosticWarning(DiagnosticType.DisposableTransientComponent,
            "Mvc controller");

        //ApplicationContainer.AddRegistration(typeof(TestDbContext), registration);
        ApplicationContainer.AddRegistration<TestDbContext>(registration);

        // Cross-wire ASP.NET services (if any). For instance:
        ApplicationContainer.CrossWire<ILoggerFactory>(app);

        // NOTE: Do prevent cross-wired instances as much as possible.
        // See: https://simpleinjector.org/blog/2016/07/
    }

    // Configure is where you add middleware. This is called after
    // ConfigureServices. You can use IApplicationBuilder.ApplicationServices
    // here if you need to resolve things from the container.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        InitializeContainer(app);

        ApplicationContainer.Verify();

        app.UseMvc(ConfigureRoutes);

        // If you want to dispose of resources that have been resolved in the
        // application container, register for the "ApplicationStopped" event.
        // You can only do this if you have a direct reference to the container,
        // so it won't work with the above ConfigureContainer mechanism.
        var options = new BackgroundJobServerOptions
        {
            Queues = new[] { "coda-Sms", "default" }
        };
        app.UseHangfireDashboard();
        app.UseHangfireServer(options);
        app.UseMvc(ConfigureRoutes);
    }

    private void ConfigureRoutes(IRouteBuilder routeBuilder)
    {
        routeBuilder.MapRoute("default", "{controller=Test}/{action=Index}/{id?}");
    }
}

上下文

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.IO;

namespace TestSimpleInjector.Model
{
    public class TestDbContext : DbContext
    {
        //public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
        //{
        //}
        public TestDbContext() : base()
        {
        }

        public DbSet<VIR_NetSizeSmsLog> VIR_NetSizeSmsLog { get; set; }

        public IConfigurationRoot Configuration { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
            builder.AddEnvironmentVariables();
            Configuration = builder.Build();
            var conStr = Configuration.GetConnectionString("Test2");
            optionsBuilder.UseSqlServer(conStr);
        }
    }
}

存储库

using System.Collections.Generic;
using TestSimpleInjector.Model;

namespace TestSimpleInjector.Services
{
    public interface ITest
    {
        IEnumerable<VIR_NetSizeSmsLog> GetAll();
    }
}


using System.Collections.Generic;
using TestSimpleInjector.Model;

namespace TestSimpleInjector.Services
{
    public class Test : ITest
    {
        private readonly TestDbContext _context;

        public Test(TestDbContext context)
        {
            _context = context;
        }

        public IEnumerable<VIR_NetSizeSmsLog> GetAll()
        {
            return _context.VIR_NetSizeSmsLog;
        }
    }
}

控制器

using Hangfire;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using TestSimpleInjector.Services;

namespace TestSimpleInjector.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        private readonly ITest _test;

        public ValuesController(ITest test)
        {
            _test = test;
        }

        // GET api/values
        [HttpGet]
        public IEnumerable<string> GetAll()
        {
            var values = _test.GetAll();
            List<string> listValues= new List<string>();
            foreach (var item in values)
            {
                listValues.Add(item.idSms.ToString());
            }
            var jobId = BackgroundJob.Schedule<ITest>(services => services.GetAll(), TimeSpan.FromMinutes(1));
            return listValues;
        }
}

当我尝试测试它时,解决方案编译并运行,该作业被hangfire调度并触发(因此Simple注入器作业激活器似乎正在工作)但除非我通过删除注释在Startup中添加这些服务

//services.AddTransient<ITest, Test>();
//services.AddDbContext<TestDbContext>

当hangfire尝试执行计划的进程时,我收到此错误

System.InvalidOperationException

A suitable constructor for type 'TestSimpleInjector.Services.ITest' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.

System.InvalidOperationException: A suitable constructor for type 'TestSimpleInjector.Services.ITest' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
   at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Hangfire.AspNetCore.AspNetCoreJobActivatorScope.Resolve(Type type)
   at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
   at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_0.<PerformJobWithFilters>b__0()
   at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
   at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable`1 filters)
   at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
   at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)

从我的理解,因为Test的构造函数将dbcontext的具体实例作为参数,因此HF无法理解如何构建它 .

一旦我添加了这两个服务注册,一切正常 . 但是如果我将它们作为服务注册,那么它与那些组件上使用默认DI相同,失去了实现SI的点吗?另外,这是考虑到我没有错过任何其他/实施其他错误的东西

我真的可以使用一些关于正确方法的指针

编辑

在下面的@Steven评论后,我将启动 ConfigurationService 代码更改为

public void ConfigureServices(IServiceCollection services)
        {
            var SimpleInjector = new SimpleInjectorJobActivator(ApplicationContainer, Lifestyle.Scoped);
            // Add services to the collection.
            services.AddHangfire(config =>
                config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection")).UseActivator(SimpleInjector));

            services.AddMvc().AddJsonOptions(opt =>
            {
                opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            });

            services.AddSingleton(Configuration);
            //services.AddTransient<ITest, Test>();
            //services.AddDbContext<TestDbContext>(ServiceLifetime.Transient);

            //var SimpleInjector = new SimpleInjectorJobActivator(ApplicationContainer, Lifestyle.Scoped);
            //GlobalConfiguration.Configuration.UseActivator(SimpleInjector);

            IntegrateSimpleInjector(services);
        }

现在预定的工作开始了,但我得到了

SimpleInjector.ActivationException

The Test is registered as 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope.

SimpleInjector.ActivationException: The Test is registered as 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope.
   at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration`1 registration)
   at SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
   at lambda_method(Closure )
   at SimpleInjector.InstanceProducer.GetInstance()
   at SimpleInjector.Container.GetInstance(Type serviceType)
   at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
   at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_0.<PerformJobWithFilters>b__0()
   at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
   at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable`1 filters)
   at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
   at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)

除非我将LifeStyles of Test和TestDbContext更改为 Transient (完成后,预定作业运行正常) . 然而,这更容易理解(我认为):当Async Scope需要时,SimpleInjector的asp.net版本实际上可以自己启动范围 . 不是在网络核心版本上 . 现在只需要了解创建范围的方式(以及在何处)....

编辑2

想想我可能已经解决了上面编辑中的步骤,利用Hangfire过滤器在作业执行时创建了一个范围

所以,增加了一个新课程

public class SimpleInjectorAsyncScopeFilterAttribute : JobFilterAttribute, IServerFilter
    {
        private static readonly AsyncScopedLifestyle lifestyle = new AsyncScopedLifestyle();

        private readonly Container _container;

        public SimpleInjectorAsyncScopeFilterAttribute(Container container)
        {
            _container = container;
        }

        public void OnPerforming(PerformingContext filterContext)
        {
            AsyncScopedLifestyle.BeginScope(_container);
        }

        public void OnPerformed(PerformedContext filterContext)
        {
            var scope = lifestyle.GetCurrentScope(_container);
            if (scope != null)
                scope.Dispose();
        }
    }

将AsyncScope传递给作业,以便在作业结束时启动和处理它

然后,回到Startup.ConfigurationService我再次将Hangfire注册更改为

services.AddHangfire(config =>
                config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection")).UseActivator(SimpleInjector).UseFilter(new SimpleInjectorAsyncScopeFilterAttribute(ApplicationContainer)));

激活过滤器 .

现在,预定的作业在AsyncScope上运行正常 .

剩下的就是要了解这个实现是否真的可以用于 生产环境 用途,或者是否存在我尚未看到的问题