ここで説明するコマンドパターンとここで説明するクエリパターンで単純なインジェクターを使用します。コマンドの1つに、2つのハンドラー実装があります。1つ目は、同期的に実行される「通常の」実装です。
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();
}
}
もう1つはコマンドデコレータに似ていますが、前のクラスを明示的にラップして、別のスレッドでコマンドを実行します。
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>
。私は次のことがうまくいくことを発見しましたが、それが最良/最適な方法であるかどうかはわかりません。非同期実装を使用するために、この1つのインターフェースを明示的に登録したいと思います。
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によってスローされた既存の例外を、コールバックで明示的にスローするのではなく、他のすべての実装に再利用したいと思います。
スティーブンの答えへの返信を更新する
質問をより明確にするために更新しました。私がこのように実装した理由は、操作の一部として、コマンドが正常に送信された後にdbエンティティでSystem.Nullable<DateTime>
呼び出されたプロパティを更新するためです。SentOnUtc
MailMessage
ICommandEntities
とIUnitOfWork
は両方ともエンティティフレームワーククラスによって実装されDbContext
ます。これは、ここで説明するメソッド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 wikiのextensionメソッドのデフォルトの動作は、HttpContext
がnullの場合に一時インスタンスを登録することです(これは新しく起動されたスレッドにあります)。
var context = HttpContext.Current;
if (context == null)
{
// No HttpContext: Let's create a transient object.
return _instanceCreator();
...
これが、Launchメソッドがサービスロケーターパターンを使用しての単一インスタンスを取得しDbContext
、それを同期コマンドハンドラーコンストラクターに直接渡す理由です。_entities.Update(emailMessageEntity)
と行が機能する_unitOfWork.SaveChanges()
には、両方が同じDbContextインスタンスを使用している必要があります。
注:理想的には、電子メールの送信は別のポーリングワーカーが処理する必要があります。このコマンドは基本的にキュークリアリングハウスです。db内のEmailMessageエンティティには、電子メールの送信に必要なすべての情報がすでに含まれています。このコマンドは、データベースから未送信のコマンドを取得して送信し、アクションの日時を記録します。このようなコマンドは、別のプロセス/アプリからポーリングすることで実行できますが、この質問に対するそのような回答は受け付けません。今のところ、ある種のhttpリクエストイベントがそれをトリガーしたときに、このコマンドを開始する必要があります。