使用简单的喷油器与下面介绍的命令模式和这里所描述的查询模式 。 对于其中一个命令,我有2个处理程序实现。 第一个是“正常”的实现,同步执行:
public class SendEmailMessageHandler
: IHandleCommands<SendEmailMessageCommand>
{
public SendEmailMessageHandler(IProcessQueries queryProcessor
, ISendMail mailSender
, ICommandEntities entities
, IUnitOfWork unitOfWork
, ILogExceptions exceptionLogger)
{
// save constructor args to private readonly fields
}
public void Handle(SendEmailMessageCommand command)
{
var emailMessageEntity = GetThisFromQueryProcessor(command);
var mailMessage = ConvertEntityToMailMessage(emailMessageEntity);
_mailSender.Send(mailMessage);
emailMessageEntity.SentOnUtc = DateTime.UtcNow;
_entities.Update(emailMessageEntity);
_unitOfWork.SaveChanges();
}
}
另一种是像这样的命令装饰,但明确地包装了以前的类一个单独的线程来执行命令:
public class SendAsyncEmailMessageHandler
: IHandleCommands<SendEmailMessageCommand>
{
public SendAsyncEmailMessageHandler(ISendMail mailSender,
ILogExceptions exceptionLogger)
{
// save constructor args to private readonly fields
}
public void Handle(SendEmailMessageCommand command)
{
var program = new SendAsyncEmailMessageProgram
(command, _mailSender, _exceptionLogger);
var thread = new Thread(program.Launch);
thread.Start();
}
private class SendAsyncEmailMessageProgram
{
internal SendAsyncEmailMessageProgram(
SendEmailMessageCommand command
, ISendMail mailSender
, ILogExceptions exceptionLogger)
{
// save constructor args to private readonly fields
}
internal void Launch()
{
// get new instances of DbContext and query processor
var uow = MyServiceLocator.Current.GetService<IUnitOfWork>();
var qp = MyServiceLocator.Current.GetService<IProcessQueries>();
var handler = new SendEmailMessageHandler(qp, _mailSender,
uow as ICommandEntities, uow, _exceptionLogger);
handler.Handle(_command);
}
}
}
有一段时间simpleinjector是在喊我,告诉我,它发现的2个实现IHandleCommands<SendEmailMessageCommand>
我发现了以下工作,但不能确定它是否是最好的/最佳方式。 我想明确地登记这一个接口使用异步执行:
container.RegisterManyForOpenGeneric(typeof(IHandleCommands<>),
(type, implementations) =>
{
// register the async email handler
if (type == typeof(IHandleCommands<SendEmailMessageCommand>))
container.Register(type, implementations
.Single(i => i == typeof(SendAsyncEmailMessageHandler)));
else if (implementations.Length < 1)
throw new InvalidOperationException(string.Format(
"No implementations were found for type '{0}'.",
type.Name));
else if (implementations.Length > 1)
throw new InvalidOperationException(string.Format(
"{1} implementations were found for type '{0}'.",
type.Name, implementations.Length));
// register a single implementation (default behavior)
else
container.Register(type, implementations.Single());
}, assemblies);
我的问题:这是正确的方式,或者是有什么好? 例如,我想重用Simpleinjector所有其他类型的抛出,而不必明确地把他们丢在回调现有例外。
更新回复史蒂芬的答案
我已经更新了我的问题更加明确。 我已经这样实现了它的原因是因为作为操作的一部分,命令更新一个System.Nullable<DateTime>
所谓财产SentOnUtc
在DB实体后MailMessage
已成功发送。
的ICommandEntities
和IUnitOfWork
都由一个实体框架中实现DbContext
class.The DbContext
每HTTP上下文注册,使用这里所描述的方法 :
container.RegisterPerWebRequest<MyDbContext>();
container.Register<IUnitOfWork>(container.GetInstance<MyDbContext>);
container.Register<IQueryEntities>(container.GetInstance<MyDbContext>);
container.Register<ICommandEntities>(container.GetInstance<MyDbContext>);
该默认行为RegisterPerWebRequest
在simpleinjector维基扩展方法是注册一个瞬态的实例时HttpContext
为null(这将在新推出的线程)。
var context = HttpContext.Current;
if (context == null)
{
// No HttpContext: Let's create a transient object.
return _instanceCreator();
...
这就是为什么启动方法使用Service Locator模式获得的单个实例DbContext
,然后直接传送到同步命令处理程序的构造函数。 为了使_entities.Update(emailMessageEntity)
和_unitOfWork.SaveChanges()
线工作,两者都必须使用相同的DbContext实例。
注意:理想情况下,发送电子邮件应该由一个独立的投票工作人员来处理。 这个命令基本上是一个队列结算所。 在DB中EmailMessage实体已经全部发送电子邮件所需的信息。 这个命令只是抓住从数据库中未发送的一个,将其发送,然后记录动作的日期时间。 这样的命令可以通过查询从一个不同的进程/应用程序来执行,但我不会接受这个问题这样回答。 现在,我们需要在某种HTTP请求事件的触发它揭开序幕这个命令。
确实有更简单的方法来做到这一点。 例如,而不是注册BatchRegistrationCallback
正如你在上次的代码段一样,你可以利用的OpenGenericBatchRegistrationExtensions.GetTypesToRegister
方法。 该方法由内部使用RegisterManyForOpenGeneric
方法,并允许您将它们发送到之前过滤返回类型RegisterManyForOpenGeneric
过载:
var types = OpenGenericBatchRegistrationExtensions
.GetTypesToRegister(typeof(IHandleCommands<>), assemblies)
.Where(t => !t.Name.StartsWith("SendAsync"));
container.RegisterManyForOpenGeneric(
typeof(IHandleCommands<>),
types);
但我认为这将是更好地使你的设计进行一些更改。 当你改变你的异步命令处理程序到一个通用的装饰,你完全完全消除问题。 这样一个普通的装饰看起来是这样的:
public class SendAsyncCommandHandlerDecorator<TCommand>
: IHandleCommands<TCommand>
{
private IHandleCommands<TCommand> decorated;
public SendAsyncCommandHandlerDecorator(
IHandleCommands<TCommand> decorated)
{
this.decorated = decorated;
}
public void Handle(TCommand command)
{
// WARNING: THIS CODE IS FLAWED!!
Task.Factory.StartNew(
() => this.decorated.Handle(command));
}
}
请注意,这是装饰,因为有缺陷的原因,我将在后面解释,但我们与此去教育的缘故。
使得这类通用的,可以重复使用这种类型的多个命令。 因为这种类型是通用的,在RegisterManyForOpenGeneric
将跳过这个(因为它不能猜测通用型)。 这使您可以按如下所示注册的装饰:
container.RegisterDecorator(
typeof(IHandleCommands<>),
typeof(SendAsyncCommandHandler<>));
在你的情况然而,你不希望这个装饰周围的所有处理包裹(如前面的注册一样)。 有一个RegisterDecorator
重载函数取一个判断,它允许你指定应用这个装饰:
container.RegisterDecorator(
typeof(IHandleCommands<>),
typeof(SendAsyncCommandHandlerDecorator<>),
c => c.ServiceType == typeof(IHandleCommands<SendEmailMessageCommand>));
施加这个谓词,则SendAsyncCommandHandlerDecorator<T>
将仅被施加到IHandleCommands<SendEmailMessageCommand>
处理程序。
另一种选择(我喜欢)是注册的一个封闭的通用版本SendAsyncCommandHandlerDecorator<T>
版本。 这样您就不必指定谓词:
container.RegisterDecorator(
typeof(IHandleCommands<>),
typeof(SendAsyncCommandHandler<SendEmailMessageCommand>));
但是正如我指出的,对于给定装饰的代码是有缺陷的 ,因为你应该总是建立在一个新的线程一个新的依赖关系图,并从不依赖通过从线程线程(原来的装饰一样)。 这个在这篇文章中的更多信息: 如何在多线程应用程序依赖注入工作 。
因此,答案其实是比较复杂的,因为这一般装饰真的应该是取代了原来的命令处理器(或者甚至可能是装饰包裹的处理程序链)的代理。 这个代理必须能够建立一个新的对象图在一个新的线程。 此代理应该是这样的:
public class SendAsyncCommandHandlerProxy<TCommand>
: IHandleCommands<TCommand>
{
Func<IHandleCommands<TCommand>> factory;
public SendAsyncCommandHandlerProxy(
Func<IHandleCommands<TCommand>> factory)
{
this.factory = factory;
}
public void Handle(TCommand command)
{
Task.Factory.StartNew(() =>
{
var handler = this.factory();
handler.Handle(command);
});
}
}
虽然简单的喷油器没有内置在解决支持Func<T>
的工厂, RegisterDecorator
方法是例外。 这样做的原因是,这将是非常乏味的无框架的支持来注册Func键依赖装饰。 换句话说,注册时SendAsyncCommandHandlerProxy
与RegisterDecorator
方法,简单的进样器将自动注入Func<T>
委托,它可以创建装饰类型的新实例。 由于代理只refences一(单)工厂(和无状态),我们甚至可以把它注册为单例:
container.RegisterSingleDecorator(
typeof(IHandleCommands<>),
typeof(SendAsyncCommandHandlerProxy<SendEmailMessageCommand>));
显然,可以混合使用该注册与其他RegisterDecorator
注册。 例:
container.RegisterManyForOpenGeneric(
typeof(IHandleCommands<>),
typeof(IHandleCommands<>).Assembly);
container.RegisterDecorator(
typeof(IHandleCommands<>),
typeof(TransactionalCommandHandlerDecorator<>));
container.RegisterSingleDecorator(
typeof(IHandleCommands<>),
typeof(SendAsyncCommandHandlerProxy<SendEmailMessageCommand>));
container.RegisterDecorator(
typeof(IHandleCommands<>),
typeof(ValidatableCommandHandlerDecorator<>));
该登记包装用任何命令处理程序TransactionalCommandHandlerDecorator<T>
任选地与异步代理装饰它,并总是与包装它ValidatableCommandHandlerDecorator<T>
这允许你做验证同步(在同一个线程),并在验证成功时的命令处理上一个新的线程,旋转,在该线程事务中运行。
因为你的一些依赖注册每个Web请求,这意味着在没有web请求,这是他们的方式,这是在简单的喷油器实现(好像是这样,他们将得到一个新的(瞬态)的实例抛出一个异常当你开始一个新的线程来运行代码)。 当你实现你的EF多个接口DbContext
,这意味着简单的喷油器会为每个构造注入接口创建一个新的实例,如你所说,这将是一个问题。
你需要重新配置DbContext
,因为一个纯粹的每个Web请求不会做。 有几种解决方案,但我认为最好是让一个混合PerWebRequest / PerLifetimeScope实例。 你需要在每终生范围扩展包这一点。 另外请注意,也是一个扩展包的每个Web请求 ,所以你不必使用任何自定义代码。 当你这样做,你可以定义下列注册:
container.RegisterPerWebRequest<DbContext, MyDbContext>();
container.RegisterPerLifetimeScope<IObjectContextAdapter,
MyDbContext>();
// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<MyDbContext>(() =>
{
if (HttpContext.Current != null)
return (MyDbContext)container.GetInstance<DbContext>();
else
return (MyDbContext)container
.GetInstance<IObjectContextAdapter>();
});
更新简单喷油器2现在有生活方式的明确概念,这使得先前的注册更加容易。 因此,下面的登记劝:
var hybrid = Lifestyle.CreateHybrid(
lifestyleSelector: () => HttpContext.Current != null,
trueLifestyle: new WebRequestLifestyle(),
falseLifestyle: new LifetimeScopeLifestyle());
// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<MyDbContext, MyDbContext>(hybrid);
由于简单的注射器只允许登记一次类型(它不支持键控登记),所以不可能同时具有PerWebRequest生活方式,以及一个PerLifetimeScope生活方式来注册MyDbContext。 所以我们要骗一点,所以我们做两个注册(每一个生活方式),并选择不同的服务类型(的DbContext和IObjectContextAdapter)。 服务类型并不重要,但MyDbContext必须实现/从服务类型(随时实现虚拟接口上的继承MyDbContext
如果这是方便)。
除了这两个注册,我们需要一个第三注册,映射,这使我们能够获得适当的生活方式了。 这是Register<MyDbContext>
其中回来基于该操作是否是HTTP请求或不内部执行适当的实例。
你AsyncCommandHandlerProxy
将不得不开始新的生命周期范围内,这是做如下:
public class AsyncCommandHandlerProxy<T>
: IHandleCommands<T>
{
private readonly Func<IHandleCommands<T>> factory;
private readonly Container container;
public AsyncCommandHandlerProxy(
Func<IHandleCommands<T>> factory,
Container container)
{
this.factory = factory;
this.container = container;
}
public void Handle(T command)
{
Task.Factory.StartNew(() =>
{
using (this.container.BeginLifetimeScope())
{
var handler = this.factory();
handler.Handle(command);
}
});
}
}
需要注意的是容器添加为依赖性AsyncCommandHandlerProxy
。
现在,任何MyDbContext
时所解决的实例HttpContext.Current
为null,将获得每终身范围实例,而不是一个新的瞬态的实例。
文章来源: Simpleinjector: Is this the right way to RegisterManyForOpenGeneric when I have 2 implementations and want to pick one?