1

私が取り組んでいるプロジェクトには、<form>sが含まれていない「読み取り専用」ページがたくさんあります。また、コントローラーから多くの読み取り専用データが取り込まれるフォームページもたくさんあります。

通常、ViewModelsを使用し、ビューごとに1つのViewModelがあり、ViewModelにはそのビューのすべてのデータが含まれています。問題があることを除いて、それは十分に公平に思えます:

私の頭の中では、ViewModelは、ビューからコントローラーに返送されるデータ全体の表現およびカプセル化であると考えていますが、ViewModelには、コントローラーによって入力されたデータ(SelectListItem [] Html.DropDownListFor()データなど)が含まれている場合があります。 )ビューでデータを入力してコントローラーに送り返すことはできません。

もちろん、そのデータをViewModelの一部として使用し、コントローラーのHttpPost処理メソッドでビューを返す前に手動で再入力することは可能ですが、コントローラーのコードが不必要に複雑になると思います(UpdateModelを使用する必要があります)。 ()アクションメソッドの引数としてモデルを指定したときに発生する自動更新の代わりに)。

これに対する私の解決策は、型指定されたViewDataオブジェクトです。代わりにTDataのインスタンスを返すために、プロパティViewPage<TModel>を与えViewPage2<TModel,TData> where TData : ViewDataDictionary<TModel>てオーバーライド(またはシャドウ)することから派生します。.ViewData

私の質問は2つあります。

  • ViewPageのサブクラス化は簡単に思えますが、ViewPage2<TModel,TData>クラスの初期化を処理するロジックをどこに配置すればよいですか?
  • 私のアプローチに何か問題がありますか?
4

1 に答える 1

0

ASP.NET MVCをあまりいじることなく、ViewData/ViewModelアプローチを実装する方法を見つけました。

これが私がそれをした方法です:

これはViewPage2、強く型付けされたViewDataオブジェクトをビューに公開する私のクラスです。残念ながら、新しいViewDataDictionaryを作成するViewPageの動作を回避するには、リフレクションを使用する必要がありますが、静的にキャッシュされたFieldInfoオブジェクトを使用すると、ルーティングでのMVCの動的コントローラー/アクションルックアップよりもコストがかかりません。

public class ViewPage2<TModel,TData> : ViewPage<TModel> where TData : ViewDataDictionary<TModel> {

    public ViewPage2() : base() {
    }

    private Boolean _dataPresent;
    private TData   _data;

    public new TData ViewData {
        get {
            if( _dataPresent && _data == null ) {
                _data = (TData)base.ViewData;
            }
            return _data;
        }
    }

    // Cached in static class state for performance.
    private static readonly FieldInfo _viewPage1ViewData;
    private static readonly FieldInfo _viewPage2ViewData;

    static ViewPage2() {

        Type viewPage1     = typeof(ViewPage<TModel>);
        _viewPage1ViewData = viewPage1.GetField("_viewData", BindingFlags.Instance | BindingFlags.NonPublic );

        Type viewPage2     = typeof(ViewPage);
        _viewPage2ViewData = viewPage2.GetField("_viewData", BindingFlags.Instance | BindingFlags.NonPublic );
    }

    protected override void SetViewData(ViewDataDictionary viewData) {

        // ViewPage<TModel> creates a new ViewDataDictionary<TModel> when this method is called, even if viewData is of the correct type.

        // The trick is to reimplement SetViewData and set base._viewData and basebase._viewData

        if( viewData is TData ) {

            _viewPage1ViewData.SetValue( this, viewData );
            _viewPage2ViewData.SetValue( this, viewData );

            _dataPresent = true;

        } else {

            base.SetViewData( viewData );
        }

    }
}

次に、*。aspxファイルごとに(私はWebFormViewEngineを使用します)、@Pageディレクティブを変更するだけです。

<%@ Page Language="C#" MasterPageFile="~/Site.Master" Inherits="Me.ViewPage2<Me.FormModel,Me.FormData>" %>

少し面倒にするために、2つの汎用型指定子を認めますが、これは1回だけ設定する必要があるものです。

次に、各コントローラーでこれを行うだけです。

public ActionResult Edit() {
    FormData data = new FormData();
    data.SomeStronglyTypedField = "foo";
    this.ViewData = data;
}

各ビューで、強く型付けされたビューデータを利用できるようになりました。

<p><%= ViewData.SomeStronglyTypedField %></p>

また、ViewDataはモデルの一部ではないため、POSTで送信されたデータを処理する際の関心の分離と、自動バインドの利点があります。

 [HttpPost]
 public ActionResult Edit(EditModel model) {
     if( !ModelState.IsValid ) {

        // See how I can return the model object without modifying it. All I need to do is re-create the View data.

        FormData data = new FormData();
        data.SomeStronglyTypedField = "foo";
        this.ViewData = data;

        return View( model );
     }
     // persist to DB here
     return RedirectToAction("View");
 }

実際には、ViewDataオブジェクトの自動作成と設定を処理する共通のBaseControllerクラスを使用しているためFormData data...、すべてのアクションでこれらの3行(など)は必要ありません。

于 2012-07-09T16:29:35.493 に答える