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によって注入されたプロパティがあります。DataContextPerWebRequestのライフスタイルがあります。つまり、リクエストごとに1つのインスタンスが再利用されます。

時々DataContext is Disposed、この属性で例外が発生し、ASP.NET MVC 3が何らかの方法でアクションフィルターをキャッシュしようとしていることを覚えています。それが問題の原因になる可能性がありますか?

もしそうなら、どのように問題を解決するのですか?プロパティを使用せず、メソッド内でServiceLocatorを使用しようとしますか?

ASP.NET MVCがフィルターをキャッシュする場合、フィルターをキャッシュしないように指示することはできますか?

4

2 に答える 2

2

このような構成を使用しないことを強くお勧めします。いくつかの理由で:

  1. データコンテキストをコミットするのは、コントローラー(またはコントローラーで装飾された属性)の責任ではありません。
  2. これにより、多くの重複コードが発生します(この属性を使用して多くのメソッドを装飾する必要があります)。
  3. (メソッド内の)実行のその時点でOnActionExecuted、データをコミットすることが実際に安全かどうか。

特に3番目のポイントはあなたの注意を引くべきでした。モデルが有効であるという単なる事実は、データコンテキストの変更を送信しても問題がないことを意味するものではありません。この例を見てください:

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

    return View();
}

もちろん、この例は少しナイーブです。すべての例外を飲み込むことはほとんどありません。それはまったく間違っているでしょう。しかし、それが示しているのは、データを保存してはならないときに、アクションメソッドが正常に終了する可能性が非常に高いことです。

しかし、これに加えて、トランザクションをコミットすることは本当にMVCの問題であり、そうだと判断した場合でも、すべてのアクションメソッドをこの属性で装飾する必要があります。コントローラーレベルで何もしなくても、これを実装するだけでいいのではないでしょうか。なぜなら、この後にどの属性を追加するのですか?承認属性?ロギング属性?属性をトレースしますか?どこで止まりますか?

代わりに試すことができるのは、トランザクションで実行する必要のあるすべてのビジネスオペレーションをモデル化することです。これにより、既存のコードを変更したり、新しい属性をあちこちに追加したりすることなく、この動作を動的に追加できます。これを行う方法は、これらのビジネスオペレーションのインターフェイスを定義することです。例えば:

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

システム内のビジネスオペレーションごとに、クラス(DTOおよびパラメータオブジェクト)を定義します。例では、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ため、それを使用する人は、依存性注入にIoCコンテナーを使用できる実装 ASP.NET MVCが必要であることを知っています。IFilterProvider

だから私はそれの実装を見始めました、そしてそれが派生していてコンストラクターFilterAttributeFilterProviderを持っていることに気づきましたFilterAttributeFilterProvider

public FilterAttributeFilterProvider(bool cacheAttributeInstances)

したがって、属性インスタンスをキャッシュするかどうかを制御できます。

このキャッシュを無効にした後、サイトが吹き飛ばされNullReferenceExceptionsたので、見落とされて望ましくない副作用を引き起こしたもう1つのことを見つけることができました。

Castle.Windsorフィルタープロバイダーを追加した後、元のフィルターは削除されませんでした。したがって、キャッシュが有効になっている場合、IoCフィルタープロバイダーはインスタンスを作成し、デフォルトのフィルタープロバイダーはインスタンスを再利用し、すべての依存関係プロパティは値で埋められていました。これは、キャッシュが無効にされた後、デフォルトでフィルターが2回実行されているという事実を除いて、はっきりとはわかりませんでした。プロバイダーはそれ自体でインスタンスを作成する必要があったため、依存関係のプロパティが埋められなかったため、NullRefereceExceptions発生しました。

于 2012-12-20T14:00:49.377 に答える