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上运行正常 .
剩下的就是要了解这个实现是否真的可以用于 生产环境 用途,或者是否存在我尚未看到的问题