如何在单个 webapi 请求中使用单个对象并在所有子进程中填充其属性?
How to use single object and fill its properties throughout all sub process in single webapi request?
我有一个 Asp.net WebApi 项目,我正在使用简单注入器进行依赖注入。
我的项目中有一些消息、消息处理程序和装饰器。我正在使用装饰器模式。
我需要保留每个进程的详细信息,我需要将它们保存到 database.For 示例中,我想保留以下详细信息:请求开始时,子进程发生了什么,进程完成时,是什么请求主体,什么是响应主体等。首先我需要将详细信息写入一个对象,然后我将把这个对象保存到数据库中。
假设这个流程细节管理员 class 的名字是 ProcessDetail。所以它有一些属性 RequestBody、ResponseBody、StartTime、EndTime 等...
如何为装饰器模式的每个步骤保留相同的 ProcessDetail 对象并为每个进程填充它?
在下面的代码中,消息(命令)作为 webapi 请求到达服务器。命令处理器处理这个命令并执行相关的命令处理程序。
但首先,需要装饰器执行,如验证、跟踪、交易装饰器执行。最后执行消息命令处理程序。
例如,我想为所有这些进程保留相同的 ProcessDetail 对象。我的要求是,当验证处理程序执行时,我想写一些东西到 ProcessDetail 对象,然后当跟踪处理程序执行或执行时,我想写一些东西到同一个 ProcessDetail 对象(我的意思是实例),最后当相关命令处理程序执行或执行时, 我想写一些东西到同一个 ProcessDetail 对象。
因此,我需要一个 ProcessDetail 对象实例来为所有子流程填充它。
主要思想是,从所有执行部分获取一些细节并将这些细节写入 ProcessDetail 对象实例。最后,我将通过 entity framework 或其他方式将此 ProcessDetails 对象实例写入数据库。
我的代码示例在这里:
命令处理程序
public interface ICommandHandler<in TCommand, out TResult>
where TCommand : ICommand<TResult>
{
TResult Handle(TCommand command);
}
命令处理器
public interface ICommandProcessor
{
object Process(object command);
TResult Process<TResult>(ICommand<TResult> command);
}
命令
public interface ICommand<TResult>
{
}
TracingDecorator
public class TracingCommandHandlerDecorator<TCommand, TResult>
: ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly ICommandHandler<TCommand, TResult> _innerHandler;
public TracingCommandHandlerDecorator(
ICommandHandler<TCommand, TResult> innerHandler)
{
_innerHandler = innerHandler;
}
public TResult Handle(TCommand command)
{
try
{
Debug.Write(command);
var result = _innerHandler.Handle(command);
Debug.Write(result);
return result;
}
catch (Exception ex)
{
Debug.Write(ex);
throw ex;
}
}
}
验证装饰器
public class ValidatingCommandHandlerDecorator<TCommand, TResult>
: ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly ICommandHandler<TCommand, TResult> _innerHandler;
public ValidatingCommandHandlerDecorator(
ICommandHandler<TCommand, TResult> innerHandler)
{
_innerHandler = innerHandler;
}
public TResult Handle(TCommand command)
{
var context = new ValidationContext(command, null);
Validator.ValidateObject(command, context);
return _innerHandler.Handle(command);
}
}
事务命令装饰器
public class TransactionalCommandHandlerDecorator<TCommand, TResult>
: ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly ICommandHandler<TCommand, TResult> _innerHandler;
private readonly OneDeskDbContext _dbContext;
public TransactionalCommandHandlerDecorator(
ICommandHandler<TCommand, TResult> innerHandler,
OneDeskDbContext dbContext)
{
_innerHandler = innerHandler;
_dbContext = dbContext;
}
public TResult Handle(TCommand command)
{
try
{
var result = _innerHandler.Handle(command);
_dbContext.Commit();
return result;
}
catch (Exception ex)
{
_dbContext.Rolback();
throw ex;
}
}
}
Global.asax
Container = new Container();
Container.RegisterSingle<ICommandProcessor, IocCommandProcessor>();
Container.RegisterManyForOpenGeneric(typeof(ICommandHandler<,>),
Assembly.GetExecutingAssembly());
Container.RegisterDecorator(typeof(ICommandHandler<,>),
typeof(TransactionalCommandHandlerDecorator<,>));
Container.RegisterDecorator(typeof(ICommandHandler<,>),
typeof(TracingCommandHandlerDecorator<,>));
Container.RegisterDecorator(typeof(ICommandHandler<,>),
typeof(ValidatingCommandHandlerDecorator<,>));
Container.RegisterSingle<ISmsService, DummpySmsService>();
Container.RegisterWebApiRequest<OneDeskDbContext, OneDeskDbContext>();
Container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
Container.Verify();
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(Container);
我假设此 ProcessDetail
对象是您希望保留在数据库中的某种实体(使用 EF 或其他 O/RM 某种类型)。
如何执行此操作在一定程度上取决于您的具体情况。如果命令处理程序没有嵌套(我的首选方法)并且您不必在发生故障时存储对象,装饰器可能如下所示:
public TResult Handle(TCommand command) {
var detail = new ProcessDetail {
StartTime = DateTime.Now,
RequestBody = HttpContext.Current.Request.RawUrl
};
dbContext.ProcessDetails.Add(detail);
var result = _innerHandler.Handle(command);
// The ResponseBody is not something you can set at this state.
detail.EndTime = DateTime.Now;
return result;
}
如果 ResponseBody
应包含发送回客户端的响应,则这是在执行装饰器时尚不可用的内容。如果您需要存储此数据,则必须使用不同的截取点。例如 Web API 消息处理程序。
万一发生故障时还需要存储对象,则不能在同一个dbContext
内操作,因为那个dbContext会处于invalide状态,因为它很可能有未保存的更改,您不想保留。在这种情况下,您需要抽象日志记录逻辑,并且在提取的组件中需要创建一个仅用于该组件的新 dbcontext:
public TResult Handle(TCommand command) {
DateTime startTime = DateTime.Now;
Exception exception = null;
try {
return _innerHandler.Handle(command);
}
catch (Exception ex) {
exception = ex;
throw;
}
finally {
_requestLogger.Log(command, startTime, exception);
}
}
现在请求记录器组件可以创建 ProcessDetail
实例并将其存储在自己的事务中。
如果命令处理程序是嵌套的,这意味着装饰器也会嵌套。有多种方法可以处理这个问题。例如,您可以使用每个请求的生活方式注册请求记录器,并让它在每个请求中只记录一次。或者您可以在装饰器中启动生命周期范围或执行上下文范围并执行相同的操作。或者你可以让装饰器检测到它在包装时被调用,在这种情况下让它什么都不做:
private readonly ScopeCounter<RequestLogCommandHandlerDecorator> scopeCounter;
public TResult Handle(TCommand command) {
using (this.scopeCounter.BeginScope()) {
if (this.scopeCounter.IsOuterScope) {
return HandleWithRequestLog(command);
} else {
return _innerHandler.Handle(command);
}
}
}
private TResult HandleWithRequestLog(TCommand command) {
// some as before
}
可以按如下方式创建 ScopeCounter:
public class ScopeCounter<T> {
private int count;
public IDisposable BeginScope() {
this.count++;
return new Decounter { counter = this };
}
public bool IsOuterScope { get { return this.count == 1; } }
private sealed class Decounter : IDisposable {
internal ScopeCounter<T> counter;
public void Dispose() {
if (counter != null) {
counter.count--;
this.counter = null;
}
}
}
}
您可以根据请求注册此 class:
container.RegisterOpenGeneric(typeof(ScopeCounter<>), typeof(ScopeCounter<>),
new WebApiRequestLifestyle());
关于您的设计的一些注意事项:
- 不要使 ICommandHandler 变体(带有 in 和 out 关键字),除非您打算实际使用它。命令通常只映射到一个处理程序,在这种情况下您不需要差异。在那种情况下,那些 in 和 out 关键字会造成混淆和误导。
- 您应该在 TracingCommandHandlerDecorator 和 TransactionalCommandHandlerDecorator 中执行
throw;
而不是 throw ex;
以防止丢失大部分堆栈跟踪。
我有一个 Asp.net WebApi 项目,我正在使用简单注入器进行依赖注入。
我的项目中有一些消息、消息处理程序和装饰器。我正在使用装饰器模式。
我需要保留每个进程的详细信息,我需要将它们保存到 database.For 示例中,我想保留以下详细信息:请求开始时,子进程发生了什么,进程完成时,是什么请求主体,什么是响应主体等。首先我需要将详细信息写入一个对象,然后我将把这个对象保存到数据库中。
假设这个流程细节管理员 class 的名字是 ProcessDetail。所以它有一些属性 RequestBody、ResponseBody、StartTime、EndTime 等...
如何为装饰器模式的每个步骤保留相同的 ProcessDetail 对象并为每个进程填充它?
在下面的代码中,消息(命令)作为 webapi 请求到达服务器。命令处理器处理这个命令并执行相关的命令处理程序。 但首先,需要装饰器执行,如验证、跟踪、交易装饰器执行。最后执行消息命令处理程序。
例如,我想为所有这些进程保留相同的 ProcessDetail 对象。我的要求是,当验证处理程序执行时,我想写一些东西到 ProcessDetail 对象,然后当跟踪处理程序执行或执行时,我想写一些东西到同一个 ProcessDetail 对象(我的意思是实例),最后当相关命令处理程序执行或执行时, 我想写一些东西到同一个 ProcessDetail 对象。
因此,我需要一个 ProcessDetail 对象实例来为所有子流程填充它。 主要思想是,从所有执行部分获取一些细节并将这些细节写入 ProcessDetail 对象实例。最后,我将通过 entity framework 或其他方式将此 ProcessDetails 对象实例写入数据库。
我的代码示例在这里:
命令处理程序
public interface ICommandHandler<in TCommand, out TResult>
where TCommand : ICommand<TResult>
{
TResult Handle(TCommand command);
}
命令处理器
public interface ICommandProcessor
{
object Process(object command);
TResult Process<TResult>(ICommand<TResult> command);
}
命令
public interface ICommand<TResult>
{
}
TracingDecorator
public class TracingCommandHandlerDecorator<TCommand, TResult>
: ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly ICommandHandler<TCommand, TResult> _innerHandler;
public TracingCommandHandlerDecorator(
ICommandHandler<TCommand, TResult> innerHandler)
{
_innerHandler = innerHandler;
}
public TResult Handle(TCommand command)
{
try
{
Debug.Write(command);
var result = _innerHandler.Handle(command);
Debug.Write(result);
return result;
}
catch (Exception ex)
{
Debug.Write(ex);
throw ex;
}
}
}
验证装饰器
public class ValidatingCommandHandlerDecorator<TCommand, TResult>
: ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly ICommandHandler<TCommand, TResult> _innerHandler;
public ValidatingCommandHandlerDecorator(
ICommandHandler<TCommand, TResult> innerHandler)
{
_innerHandler = innerHandler;
}
public TResult Handle(TCommand command)
{
var context = new ValidationContext(command, null);
Validator.ValidateObject(command, context);
return _innerHandler.Handle(command);
}
}
事务命令装饰器
public class TransactionalCommandHandlerDecorator<TCommand, TResult>
: ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly ICommandHandler<TCommand, TResult> _innerHandler;
private readonly OneDeskDbContext _dbContext;
public TransactionalCommandHandlerDecorator(
ICommandHandler<TCommand, TResult> innerHandler,
OneDeskDbContext dbContext)
{
_innerHandler = innerHandler;
_dbContext = dbContext;
}
public TResult Handle(TCommand command)
{
try
{
var result = _innerHandler.Handle(command);
_dbContext.Commit();
return result;
}
catch (Exception ex)
{
_dbContext.Rolback();
throw ex;
}
}
}
Global.asax
Container = new Container();
Container.RegisterSingle<ICommandProcessor, IocCommandProcessor>();
Container.RegisterManyForOpenGeneric(typeof(ICommandHandler<,>),
Assembly.GetExecutingAssembly());
Container.RegisterDecorator(typeof(ICommandHandler<,>),
typeof(TransactionalCommandHandlerDecorator<,>));
Container.RegisterDecorator(typeof(ICommandHandler<,>),
typeof(TracingCommandHandlerDecorator<,>));
Container.RegisterDecorator(typeof(ICommandHandler<,>),
typeof(ValidatingCommandHandlerDecorator<,>));
Container.RegisterSingle<ISmsService, DummpySmsService>();
Container.RegisterWebApiRequest<OneDeskDbContext, OneDeskDbContext>();
Container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
Container.Verify();
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(Container);
我假设此 ProcessDetail
对象是您希望保留在数据库中的某种实体(使用 EF 或其他 O/RM 某种类型)。
如何执行此操作在一定程度上取决于您的具体情况。如果命令处理程序没有嵌套(我的首选方法)并且您不必在发生故障时存储对象,装饰器可能如下所示:
public TResult Handle(TCommand command) {
var detail = new ProcessDetail {
StartTime = DateTime.Now,
RequestBody = HttpContext.Current.Request.RawUrl
};
dbContext.ProcessDetails.Add(detail);
var result = _innerHandler.Handle(command);
// The ResponseBody is not something you can set at this state.
detail.EndTime = DateTime.Now;
return result;
}
如果 ResponseBody
应包含发送回客户端的响应,则这是在执行装饰器时尚不可用的内容。如果您需要存储此数据,则必须使用不同的截取点。例如 Web API 消息处理程序。
万一发生故障时还需要存储对象,则不能在同一个dbContext
内操作,因为那个dbContext会处于invalide状态,因为它很可能有未保存的更改,您不想保留。在这种情况下,您需要抽象日志记录逻辑,并且在提取的组件中需要创建一个仅用于该组件的新 dbcontext:
public TResult Handle(TCommand command) {
DateTime startTime = DateTime.Now;
Exception exception = null;
try {
return _innerHandler.Handle(command);
}
catch (Exception ex) {
exception = ex;
throw;
}
finally {
_requestLogger.Log(command, startTime, exception);
}
}
现在请求记录器组件可以创建 ProcessDetail
实例并将其存储在自己的事务中。
如果命令处理程序是嵌套的,这意味着装饰器也会嵌套。有多种方法可以处理这个问题。例如,您可以使用每个请求的生活方式注册请求记录器,并让它在每个请求中只记录一次。或者您可以在装饰器中启动生命周期范围或执行上下文范围并执行相同的操作。或者你可以让装饰器检测到它在包装时被调用,在这种情况下让它什么都不做:
private readonly ScopeCounter<RequestLogCommandHandlerDecorator> scopeCounter;
public TResult Handle(TCommand command) {
using (this.scopeCounter.BeginScope()) {
if (this.scopeCounter.IsOuterScope) {
return HandleWithRequestLog(command);
} else {
return _innerHandler.Handle(command);
}
}
}
private TResult HandleWithRequestLog(TCommand command) {
// some as before
}
可以按如下方式创建 ScopeCounter:
public class ScopeCounter<T> {
private int count;
public IDisposable BeginScope() {
this.count++;
return new Decounter { counter = this };
}
public bool IsOuterScope { get { return this.count == 1; } }
private sealed class Decounter : IDisposable {
internal ScopeCounter<T> counter;
public void Dispose() {
if (counter != null) {
counter.count--;
this.counter = null;
}
}
}
}
您可以根据请求注册此 class:
container.RegisterOpenGeneric(typeof(ScopeCounter<>), typeof(ScopeCounter<>),
new WebApiRequestLifestyle());
关于您的设计的一些注意事项:
- 不要使 ICommandHandler 变体(带有 in 和 out 关键字),除非您打算实际使用它。命令通常只映射到一个处理程序,在这种情况下您不需要差异。在那种情况下,那些 in 和 out 关键字会造成混淆和误导。
- 您应该在 TracingCommandHandlerDecorator 和 TransactionalCommandHandlerDecorator 中执行
throw;
而不是throw ex;
以防止丢失大部分堆栈跟踪。