3

我有一个 UnitOfWork 属性,如下所示:

public class UnitOfWorkAttribute : ActionFilterAttribute
{
    public IDataContext DataContext { get; set; }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {            
        if (filterContext.Controller.ViewData.ModelState.IsValid)
        {
            DataContext.SubmitChanges();
        }

        base.OnActionExecuted(filterContext);
    }
}

如您所见,它具有DataContext由 Castle.Windsor 注入的属性。DataContext具有 PerWebRequest 的生活方式 - 意味着为每个请求重用单个实例。

问题是,有时我DataContext is Disposed在这个属性中遇到异常,我记得 ASP.NET MVC 3 试图以某种方式缓存操作过滤器,那么这可能会导致问题吗?

如果是这样,如何解决这个问题 - 通过不使用任何属性并尝试使用 ServiceLocator 内部方法?

如果它缓存过滤器,是否可以告诉 ASP.NET MVC 不缓存过滤器?

4

2 回答 2

2

我强烈建议不要使用这样的构造。有几个原因:

  1. 提交数据上下文不是控制器(或控制器装饰属性)的责任。
  2. 这会导致大量重复的代码(你必须用这个属性装饰很多方法)。
  3. 在执行(在OnActionExecuted方法中)的那个时刻,提交数据是否实际上是安全的。

尤其是第三点应该引起你的注意。模型有效这一事实并不意味着可以提交数据上下文的更改。看这个例子:

[UnitOfWorkAttribute]
public View MoveCustomer(int customerId, Address address)
{
    try
    {
        this.customerService.MoveCustomer(customerId, address);
    }
    catch { }

    return View();
}

当然这个例子有点幼稚。你几乎不会吞下每一个例外,那是完全错误的。但它确实表明,当不应该保存数据时,操作方法很有可能成功完成。

但除此之外,提交事务真的是 MVC 的一个问题,如果你确定它是,你是否还想用这个属性装饰所有的动作方法。如果您无需在 Controller 级别执行任何操作就实现这一点,会不会更好?因为,在此之后您要添加哪些属性?授权属性?记录属性?追踪属性?它停在哪里?

相反,您可以尝试对需要在事务中运行的所有业务操作进行建模,以一种允许您动态添加此行为的方式进行建模,而无需更改任何现有代码或在各处添加新属性。一种方法是为这些业务操作定义一个接口。例如:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

使用此界面,您的控制器将如下所示:

private readonly ICommandHandler<MoveCustomerCommand> handler;

// constructor
public CustomerController(
    ICommandHandler<MoveCustomerCommand> handler)
{
    this.handler = handler;
}

public View MoveCustomer(int customerId, Address address)
{
    var command = new MoveCustomerCommand
    {
        CustomerId = customerId,
        Address = address,
    };

    this.handler.Handle(command);

    return View();
}

对于系统中的每个业务操作,您定义一个类(一个DTOParameter Object)。在示例中的MoveCustomerCommand类。此类仅包含数据。实现是在一个实现的类中定义的ICommandHandler<MoveCustomerCommand>。例如:

public class MoveCustomerCommandHandler
    : ICommandHandler<MoveCustomerCommand>
{
    private readonly IDataContext context;

    public MoveCustomerCommandHandler(IDataContext context)
    {
        this.context = context;
    }

    public void Handle(MoveCustomerCommand command)
    {
        // TODO: Put logic here.
    }
}

这看起来像是一大堆额外的无用代码,但这实际上非常有用(如果你仔细观察,它实际上并没有那么多额外的代码)。

有趣的是,您现在可以定义一个装饰器来处理系统中所有命令处理程序的事务:

public class TransactionalCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private readonly IDataContext context;
    private readonly ICommandHandler<TCommand> decoratedHandler;

    public TransactionalCommandHandlerDecorator(IDataContext context,
        ICommandHandler<TCommand> decoratedHandler)
    {
        this.context = context;
        this.decoratedHandler = decoratedHandler;
    }

    public void Handle(TCommand command)
    {
        this.decoratedHandler.Handle(command);

        this.context.SubmitChanges();
    }
}

这并不比你的代码多多少UnitOfWorkAttribute,但不同的是,这个处理程序可以包装任何实现并注入任何控制器,而控制器不需要知道这一点。在执行命令后直接是真正知道是否可以保存更改的唯一安全位置。

您可以在本文中找到有关这种设计应用程序的方式的更多信息:同时...在我的架构的命令端

于 2012-11-07T21:49:27.857 回答
0

今天我偶然发现了问题的原始问题。
从问题中可以看出,过滤器具有由 注入的属性,Castle.Windsor因此使用的人ASP.NET MVC知道,要使其工作,您需要具有IFilterProvider实现,该实现将能够使用 IoC 容器进行依赖注入。

FilterAttributeFilterProvider所以我开始研究它的实现,并注意到它是从FilterAttributeFilterProvider构造函数派生而来的:

public FilterAttributeFilterProvider(bool cacheAttributeInstances)

因此,您可以控制是否缓存您的属性实例。

禁用此缓存后,站点被NullReferenceExceptions.

问题是,在我们添加Castle.Windsor过滤器提供程序之后,原始过滤器没有被删除。因此,当启用缓存时,IoC 过滤器提供程序正在创建实例,并且默认过滤器提供程序正在重用它们,并且所有依赖项属性都填充了值 - 这并不明显,除了过滤器在缓存被禁用后运行两次的事实,默认provider 需要自己创建实例,因此未填充依赖属性,这就是NullRefereceExceptions发生的原因。

于 2012-12-20T14:00:49.377 回答