如何在单个webapi请求中使用单个对象并在所有子流程中填充其属性?

时间:2015-04-25 10:53:58

标签: c# design-patterns architecture asp.net-web-api2 simple-injector

我有一个Asp.net WebApi项目,我使用Simple Injector进行依赖注入。

我的项目中有一些消息,消息处理程序,装饰器。我正在使用装饰模式。

我需要保留每个进程的详细信息,我需要将它们保存到数据库中。例如,我想保留以下详细信息:当请求启动时,子进程发生了什么以及进程何时完成,请求体是什么,什么是响应体等。首先我需要向对象写入详细信息,然后我将此对象保存到数据库。

想象一下,这个进程详细管理器类名是ProcessDetail。所以它有一些属性RequestBody,ResponseBody,StartTime,EndTime等...

如何为装饰器模式的每个步骤保留相同的ProcessDetail对象并为每个进程填充它?

在以下代码中,message(命令)作为webapi请求进入服务器。命令处理器处理此命令并执行相关的命令处理程序。 但首先,需要的装饰器执行,如验证,跟踪,事务装饰器执行。最后消息命令处理程序执行。

例如,我想为所有这些进程保留相同的ProcessDetail对象。我的要求是,当执行验证处理程序时,我想写一些东西给ProcessDetail对象,然后当跟踪处理程序执行或执行时,我想写一些东西到同一个ProcessDetail对象(我的意思是实例),最后当相关的命令处理程序执行或执行时,我想写一些东西到同一个ProcessDetail对象。

因此,我需要一个ProcessDetail对象实例来为所有子进程填充它。 主要思想是从所有执行部分获取一些细节,并将这些细节写入ProcessDetail对象实例。最后,我将通过实体框架或其他方式将此ProcessDetails对象实例写入数据库。

我的代码示例在这里:

CommandHandler

public interface ICommandHandler<in TCommand, out TResult>
    where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}

CommandProcessor

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);
    }
}

TransactionalCommand装饰器

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);

1 个答案:

答案 0 :(得分:1)

我认为这个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将处于无效状态,因为它可能未保存变化,你不想坚持。在这种情况下,您需要抽象日志记录逻辑,并且在提取的组件中需要创建一个仅用于该组件的新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;
            }
        }
    }
}

您可以按要求注册此课程:

container.RegisterOpenGeneric(typeof(ScopeCounter<>), typeof(ScopeCounter<>),
    new WebApiRequestLifestyle());

虽然您的设计很少注意到:

  • 除非您计划实际使用它,否则不要使ICommandHandler变量(带有输入和输出关键字)。命令通常映射到一个处理程序,在这种情况下,您不需要方差。在这种情况下,那些进出的关键词令人困惑和误导。
  • 您应该在TracingCommandHandlerDecorator和TransactionalCommandHandlerDecorator中执行throw;而不是throw ex;,以防止丢失大部分堆栈跟踪。