ViewModel のベスト プラクティス
-
21-08-2019 - |
質問
から この質問, 、コントローラーに ビューモデル これは、ビューが表示しようとしているモデルをより正確に反映していますが、いくつかの規則について興味があります(まだ明らかではないにしても、私は MVC パターンを初めて使用します)。
基本的に、次のような質問がありました。
- 通常、私は 1 つのクラス/ファイルを使用することを好みます。これは意味がありますか? ビューモデル コントローラーからビューにデータを渡すためだけに作成されている場合は?
- もし ビューモデル は独自のファイルに属しており、ディレクトリ/プロジェクト構造を使用して物事を分離しています。 ビューモデル ファイルの所属?の中に コントローラー ディレクトリ?
今のところ基本的にはそれだけです。他にもいくつか質問があるかもしれませんが、ここ 1 時間ほどこのことに悩まされており、他の場所で一貫したガイダンスを見つけることができるようです。
編集:サンプルを見てみると NerdDinnerアプリ CodePlex では、ViewModel は コントローラー, しかし、それでも、それらが独自のファイルにないのは不快です。
解決
私は、各ビューのための「ViewModelに」と呼ぶものを作成します。私は私のMVC Webプロジェクトでのviewmodelsというフォルダに入れて。私はそれが表すコントローラとアクション(またはビュー)の後に名前を付けます。だから私は、私はMembershipSignUpViewModel.csクラスを作成してのviewmodelsフォルダにそれを置く会員コントローラ上のサインアップビューにデータを渡す必要がある場合。
次に、Iビューのコントローラからのデータの転送を容易にするために必要なプロパティとメソッドを追加します。私は、必要に応じて、再びドメインモデルとの私のViewModelから取得するAutomapperを使用します。
このは、他のviewmodelsの型であるプロパティを含む複合のviewmodelsに適しています。たとえば、あなたはメンバーシップ・コントローラ内のインデックスページの5つのウィジェットを持っている場合、あなたは、各部分図のためのViewModelを作成しました - どのようにあなたはパーシャルにindexアクションからデータを渡すのですか?あなたはタイプMyPartialViewModelのMembershipIndexViewModelにプロパティを追加し、部分的にレンダリングするとき、あなたはModel.MyPartialViewModelに渡します。
それをやって、この方法は、あなたがすべてでIndexビューを変更することなく、部分のViewModelのプロパティを調整することができます。あなたがやっているすべての部分のViewModelにプロパティを追加したときに何かを修正するパーシャルのチェーン全体を通過する必要がありますチャンスの少ないがあるので、それはまだちょうどModel.MyPartialViewModelに渡します。
私はこれまで、各ビューの明示的なimport文を追加することなく、任意のビューでそれらを参照することを可能にするように私はまた、web.configファイルに名前空間「MyProject.Web.ViewModels」を追加します。ちょうどそれ少しきれいになります。
他のヒント
カテゴリ(コントローラ、のviewmodels、フィルタ等)によってクラスを分離無意味である。
あなたのウェブサイトのホームセクション(/)のためのコードを書きたい場合は、[ホームという名前のフォルダを作成し、そこにHomeController、IndexViewModel、AboutViewModel、などとホームアクションによって使用されるすべての関連クラスを入れます。
あなたはApplicationControllerにように、共有クラスを持っている場合は、あなたのプロジェクトのルートにそれを置くことができます。
なぜ別の関連しているもの(にHomeController、IndexViewModel)およびすべての(にHomeController、AccountController)に関係ありません一緒に物事を保つ?
<時間>私は約ブログ記事を書きましたこのトピックます。
私はアプリケーション クラスを「Core」というサブ フォルダー (または別のクラス ライブラリ) に保存し、同じメソッドを使用します。 キグ サンプル アプリケーションですが、アプリケーションをより DRY にするために若干の変更が加えられています。
/Core/ViewData/ に BaseViewData クラスを作成し、サイト全体の共通プロパティを保存します。
この後、同じフォルダーにすべてのビュー ViewData クラスも作成します。これらのクラスは BaseViewData から派生し、ビュー固有のプロパティを持ちます。
次に、すべてのコントローラーの派生元となる ApplicationController を作成します。ApplicationController には、次のような汎用の GetViewData メソッドがあります。
protected T GetViewData<T>() where T : BaseViewData, new()
{
var viewData = new T
{
Property1 = "value1",
Property2 = this.Method() // in the ApplicationController
};
return viewData;
}
最後に、コントローラー アクションで次の操作を行って ViewData モデルを構築します。
public ActionResult Index(int? id)
{
var viewData = this.GetViewData<PageViewData>();
viewData.Page = this.DataContext.getPage(id); // ApplicationController
ViewData.Model = viewData;
return View();
}
これは非常にうまく機能し、ビューを整頓し、コントローラーをスリムに保つことができると思います。
AのViewModelクラスは、あなたのビューに渡すことができるオブジェクトを管理しやすいものにクラスのインスタンスによって表される複数のデータをカプセル化することがあります。
これは、独自のディレクトリに、自分のファイルにあなたのViewModelクラスを持つことが理にかなって。私のプロジェクトでは、私はのviewmodelsと呼ばれるモデルフォルダのサブフォルダを持っています。私のviewmodels(例えばProductViewModel.cs
)が住んでいるのです。
であなたのモデルを維持するためには良い場所はありません。プロジェクトが大きいとのviewmodels(データ転送オブジェクト)が多い場合は、別のアセンブリでそれらを保つことができます。また、あなたはサイトプロジェクトの別のフォルダにそれらを保つことができます。たとえば、 Oxite の中で、彼らはあまりにも様々なクラスの多くが含まれていOxiteプロジェクトに配置されています。 Oxiteのコントローラは、別のプロジェクトに移動され、ビューはあまりにも別のプロジェクトである。
CodeCampServerするのviewmodelsでフォーム*命名され、それらはモデルフォルダ内のUIプロジェクトに配置されている。
また、データベースともう少しで動作するように、すべてのコードが含まれ、それらがデータプロジェクトに配置されている MvcPress のプロジェクトでは、
(しかし、私はそれだけでサンプルのためだ、このアプローチを推奨していませんでした)
だから、ビューの多くのポイントがあります見ることができます。私は通常、サイトのプロジェクトで私のviewmodels(DTOオブジェクト)を保ちます。私は10の以上のモデルを持っている場合しかし、私は、アセンブリを分離するためにそれらを移動することを好みます。通常、この場合には、私はあまりにも別のアセンブリにコントローラを移動しています。
もう一つの問題は、簡単にあなたのViewModelにモデルからのすべてのデータをマップする方法です。私は AutoMapper のライブラリを見てすることをお勧めします。私はそれが非常に多く、それは私のためにすべての汚い仕事をしたいと。
そして私はまた、私は SharpArchitecture のプロジェクトを見てお勧めします。これは、プロジェクトのための非常に優れたアーキテクチャを提供し、それはクールなフレームワークやガイダンスと偉大なコミュニティの多くが含まれています。
ここで私のベストプラクティスからコードスニペットです:
public class UserController : Controller
{
private readonly IUserService userService;
private readonly IBuilder<User, UserCreateInput> createBuilder;
private readonly IBuilder<User, UserEditInput> editBuilder;
public UserController(IUserService userService, IBuilder<User, UserCreateInput> createBuilder, IBuilder<User, UserEditInput> editBuilder)
{
this.userService = userService;
this.editBuilder = editBuilder;
this.createBuilder = createBuilder;
}
public ActionResult Index(int? page)
{
return View(userService.GetPage(page ?? 1, 5));
}
public ActionResult Create()
{
return View(createBuilder.BuildInput(new User()));
}
[HttpPost]
public ActionResult Create(UserCreateInput input)
{
if (input.Roles == null) ModelState.AddModelError("roles", "selectati macar un rol");
if (!ModelState.IsValid)
return View(createBuilder.RebuildInput(input));
userService.Create(createBuilder.BuilEntity(input));
return RedirectToAction("Index");
}
public ActionResult Edit(long id)
{
return View(editBuilder.BuildInput(userService.GetFull(id)));
}
[HttpPost]
public ActionResult Edit(UserEditInput input)
{
if (!ModelState.IsValid)
return View(editBuilder.RebuildInput(input));
userService.Save(editBuilder.BuilEntity(input));
return RedirectToAction("Index");
}
}
私たちは、モデルフォルダに当社のviewmodelsのすべてを投げる(当社のビジネスロジックのすべてが別々のServiceLayerプロジェクトである)
ViewModelには何が、その後別のクラスを使用些細であれば個人的に、私はお勧めしたい。
複数のビューモデルを持っている場合は、そして、私はそれが、少なくともディレクトリに分割する意味をなす示唆しています。ビューモデルが後で共有されているならば、ディレクトリ内の暗黙のネームスペースは、簡単に新しいアセンブリに移動することができます。
私たちのケースでは、ビューとは別のプロジェクトでのコントローラと一緒にモデルを持っています。
親指のルールとして、私たちはViewModelににViewDataを[「...」]原料のほとんどを移動し、避けるために試してみたので、我々は良いことである、鋳物と魔法の文字列を避けます。
ViewModelには、同様にパン粉やタイトルを描画するためのリストやページのヘッダ情報のためのページ付け情報などのいくつかの一般的な性質を保持しています。この時点で、基本クラスは、私の意見ではあまりにも多くの情報を保持し、我々は3枚、ベース・ビューモデルのページの99%のための最も基本的かつ必要な情報でそれを分割して、リストのモデルとモデルもそのシナリオに固有のデータを保持し、ベース一方から継承。フォームの
最後に、我々は特定の情報を扱うために、各エンティティのビューモデルを実装します。
コードのコントローラ:
[HttpGet]
public ActionResult EntryEdit(int? entryId)
{
ViewData["BodyClass"] = "page-entryEdit";
EntryEditViewModel viewMode = new EntryEditViewModel(entryId);
return View(viewMode);
}
[HttpPost]
public ActionResult EntryEdit(Entry entry)
{
ViewData["BodyClass"] = "page-entryEdit";
#region save
if (ModelState.IsValid)
{
if (EntryManager.Update(entry) == 1)
{
return RedirectToAction("EntryEditSuccess", "Dictionary");
}
else
{
return RedirectToAction("EntryEditFailed", "Dictionary");
}
}
else
{
EntryEditViewModel viewModel = new EntryEditViewModel(entry);
return View(viewModel);
}
#endregion
}
コードビューモデル:
public class EntryEditViewModel
{
#region Private Variables for Properties
private Entry _entry = new Entry();
private StatusList _statusList = new StatusList();
#endregion
#region Public Properties
public Entry Entry
{
get { return _entry; }
set { _entry = value; }
}
public StatusList StatusList
{
get { return _statusList; }
}
#endregion
#region constructor(s)
/// <summary>
/// for Get action
/// </summary>
/// <param name="entryId"></param>
public EntryEditViewModel(int? entryId)
{
this.Entry = EntryManager.GetDetail(entryId.Value);
}
/// <summary>
/// for Post action
/// </summary>
/// <param name="entry"></param>
public EntryEditViewModel(Entry entry)
{
this.Entry = entry;
}
#endregion
}
プロジェクト:
DevJet.WebのASP.NET MVCウェブ プロジェクト)
DevJet.ます。アプリです。辞書( 別のクラス図書館プロジェクト)
このプロジェクトかフォルダのように:ダル, BLL, BO VM(フォルダビューモデル)
あなたはまた、現在のユーザーデータとロールを置くことができ、一般的に操作の結果とコンテキストデータのような特性を必要としていたビューモデルの基本クラスを作成します。
class ViewModelBase
{
public bool HasError {get;set;}
public string ErrorMessage {get;set;}
public List<string> UserRoles{get;set;}
}
ベースコントローラクラスのPopulateViewModelBase(のようなメソッドを持っている)、この方法は、コンテキストデータとユーザの役割を埋めるだろう。 例外がある場合、サービス/ DBからデータを引っ張りながらHasErrorとにErrorMessageは、これらのプロパティを設定します。エラーを表示するビューでこれらのプロパティをバインドします。 ユーザーの役割は、役割に基づいたビューの非表示部分を表示するために使用することができます。
異なる取得アクションのビューモデルを設定するために、それは抽象メソッドFillModel有するベースコントローラを有することによって一致させることができる
class BaseController :BaseController
{
public PopulateViewModelBase(ViewModelBase model)
{
//fill up common data.
}
abstract ViewModelBase FillModel();
}
コントローラで
class MyController :Controller
{
public ActionResult Index()
{
return View(FillModel());
}
ViewModelBase FillModel()
{
ViewModelBase model=;
string currentAction = HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString();
try
{
switch(currentAction)
{
case "Index":
model= GetCustomerData();
break;
// fill model logic for other actions
}
}
catch(Exception ex)
{
model.HasError=true;
model.ErrorMessage=ex.Message;
}
//fill common properties
base.PopulateViewModelBase(model);
return model;
}
}