24

現在、ドメイン オブジェクトをビューに渡し、POST からそれらに直接バインドしています。誰もがこれは悪いと言うので、ViewModel の概念を追加しようとしています。

ただし、これを非常にエレガントに行う方法を見つけることができません。非常に面倒なコントローラーアクションで終わらないための他の人の解決策を知りたいです。

たとえば、「人を追加」機能の典型的なプロセスは次のようになります。

  1. 空の Person ビューモデルを表すビューの GET リクエストを作成する
  2. ポストバック (イン) 有効なデータ
  3. コントローラーは、投稿されたデータを人物ビューモデルにバインドします
  4. バインディングが失敗した場合、(1) と同じアクションを実行する必要がありますが、空のオブジェクトではなく、いくつかのデータを使用してエラーを発生させます。
  5. バインディングが成功した場合は、プロパティを VM から実際のモデルにマップする必要があります
  6. モデルを検証する
  7. 検証に合格した場合: 個人を保存し、コミットし、ユーザーの詳細をディスプレイ VM にマップして、ビューに返します。
  8. 検証が失敗した場合は、(1) と同じアクションを実行しますが、いくつかのデータとエラーがあります

これらすべてをコントローラー アクションで実行する (GET を無視する) ことは、確かに SRP や DRY ではありません。

このプロセスを分割して、SRP に準拠し、クリーンでモジュール化され、何よりもテスト可能になる方法を考えようとしています。

これに対する人々の解決策は何ですか?

カスタムコントローラーアクションインボーカーを使用して、懸念を個々のメソッド、スマートモデルバインダー、および単純なブルートフォースに分離することを試してきましたが、満足のいく解決策にまだ出会っていません。

PSそれは非常に複雑になるので、なぜ私が気にする必要があるのか​​ を納得させてください

4

3 に答える 3

6

私も同じような不快感を覚えました。それを回避する私の唯一の方法は、次のことを行うことでした:

  1. ビューモデルをバインドして検証するためのバインダーを作成する
  2. データベースからエンティティを取得するためのバインダーを作成します (またはコントローラーでこれを行うだけです)。
  3. スーパークラスで継承された Save メソッドを呼び出します。このメソッドは、ビューモデルと更新されるエンティティを受け取り、手順にリストしたすべての作業を行います。

アクション メソッドは次のようになります。

public ActionResult Whatever(TViewModel viewModel, TEntity entity)
{
    return Save(viewModel, entity);
}

ベースコントローラーには、次のような一般的な定義があります。

public abstract BaseController<TEntity, TViewModel>
    where TEntity : Entity
    where TViewModel : ViewModel

コンストラクターには 2 つの依存関係があります。1 つはエンティティ リポジトリ用で、もう 1 つはモデル マッパー用です。

protected BaseController(IRepository<TEntity> repository, IMapper<TEntity, TViewModel> mapper)

これを配置すると、次のように、サブクラスのコントローラー アクションから呼び出すことができる保護された Save メソッドを記述できます。

protected ActionResult Save(TViewModel viewModel, TEntity entity)
{
    if (!ModelState.IsValid)
        return View(viewModel);

    _mapper.Map(viewModel, entity);
    if (!entity.IsValid)
    {
        // add errors to model state
        return View(viewModel);
    }

    try
    {
        _repository.Save(entity);
        // either redirect with static url or add virtual method for defining redirect in subclass.
    }
    catch (Exception)
    {
        // do something here with the exception
        return View(viewModel);
    }
}

テスト容易性に関しては、有効/無効なビュー モデルとエンティティを渡す save メソッドをテストできます。モデル マッパーの実装、ビュー モデルの有効な状態、およびエンティティの有効な状態を個別にテストできます。

同じことを行うために多くのコントローラーを作成している場合、基本コントローラーをジェネリックにすることで、ドメイン内の各エンティティ/ビューモデルの組み合わせに対してこのパターンを繰り返すことができます。

これについて他の人が何を言おうとしているのか、非常に興味があります。素晴らしい質問です。

于 2009-08-25T00:41:28.203 に答える
2

MVVM (ViewModel) パターンは間違いなく最適なパターンです。数日前に POSTing back to an action について同様の質問がありました。リンクは次のとおりです: MVVM and ModelBinders in the ASP.NET MVC Framework

その結果、Bind 属性を使用して、必要な複合型をポストバックできるようになりました。

于 2009-08-18T16:28:25.827 に答える
0

valueinjecterのダウンロードにあるasp.net mvcサンプルアプリケーションには多くの優れたソリューションがあります(ビューモデルをエンティティに/からマップするために使用するマッパー、フォームコレクション/リクエストをエンティティにマップすることもできます)

ここに1つあります:

    public class TinyController :Controller
        {
            private readonly IModelBuilder<Person, PersonViewModel> modelBuilder;

            public TinyController()
            {
                modelBuilder = new PersonModelBuilder();
            }

            public ActionResult Index()
            {
                return View(modelBuilder.BuildModel(new PersonRepository().Get()));
            }

            [HttpPost]
            public ActionResult Index(PersonViewModel model)
            {
                if (!ModelState.IsValid)
                    return View(modelBuilder.RebuildModel(model));

                   var entity = modelBuilder.BuildEntity(model);
...
//save it or whatever
            }
        }
于 2010-06-11T07:45:22.687 に答える