web-dev-qa-db-ja.com

ViewModelのベストプラクティス

この質問 から、コントローラーにViewModelを作成させて、ビューは表示しようとしていますが、私はいくつかの規則に興味があります(MVCパターンがまだ明らかでない場合、MVCパターンは初めてです)。

基本的に、次の質問がありました。

  1. 通常、1つのクラス/ファイルが必要です。これは、コントローラからビューにデータを渡すためだけに作成されている場合、ViewModelで意味がありますか?
  2. ViewModelが独自のファイルに属し、ディレクトリ/プロジェクト構造を使用して物事を分離している場合、ViewModelファイルが属しますか? Controllersディレクトリに?

基本的にはこれで終わりです。さらに質問がいくつかあるかもしれませんが、これはここ1時間ほど悩まされており、他の場所でも一貫したガイダンスを見つけることができます。

EDIT:CodePlexのサンプル NerdDinnerアプリ を見ると、ViewModelsは Controllers ですが、それでもそれらが自分のファイルに含まれていないのは不快です。

234
jerhinesmith

ビューごとに「ViewModel」と呼ばれるものを作成します。 MVC WebプロジェクトのViewModelsというフォルダーにそれらを配置します。それらが表すコントローラーとアクション(またはビュー)にちなんで名前を付けます。したがって、MembershipコントローラーのSignUpビューにデータを渡す必要がある場合は、MembershipSignUpViewModel.csクラスを作成し、ViewModelsフォルダーに配置します。

次に、必要なプロパティとメソッドを追加して、コントローラーからビューへのデータの転送を促進します。 Automapperを使用して、ViewModelからドメインモデルに移動し、必要に応じて再び戻ります。

これは、他のViewModelのタイプのプロパティを含む複合ViewModelでもうまく機能します。たとえば、メンバーシップコントローラーのインデックスページに5つのウィジェットがあり、部分ビューごとにViewModelを作成した場合-インデックスアクションからパーシャルにデータを渡す方法を教えてください。タイプMyPartialViewModelのMembershipIndexViewModelにプロパティを追加し、パーシャルをレンダリングするときにModel.MyPartialViewModelに渡します。

このようにすることで、Indexビューをまったく変更せずに、部分的なViewModelプロパティを調整できます。まだModel.MyPartialViewModelに渡されるだけなので、パーシャルViewModelにプロパティを追加するだけで何かを修正するためにパーシャルのチェーン全体を通過する必要はほとんどありません。

また、web.configに名前空間「MyProject.Web.ViewModels」を追加して、各ビューに明示的なimportステートメントを追加することなく、任意のビューで参照できるようにします。ちょっとすっきりさせます。

208
Ryan Montgomery

カテゴリ(コントローラー、ViewModel、フィルターなど)でクラスを分離するのは無意味です。

Webサイト(/)のHomeセクションのコードを作成する場合は、Homeという名前のフォルダーを作成し、HomeController、IndexViewModel、AboutViewModelなど、およびHomeアクションで使用されるすべての関連クラスをそこに配置します。

ApplicationControllerなどの共有クラスがある場合は、プロジェクトのルートに配置できます。

関連するもの(HomeController、IndexViewModel)を分離し、まったく関係のないもの(HomeController、AccountController)をまとめるのはなぜですか?


このトピックについて ブログ投稿 を書きました。

122
Max Toro

「Core」(または別のクラスライブラリ)というサブフォルダーにアプリケーションクラスを保持し、 KIGG サンプルアプリケーションと同じメソッドを使用しますが、アプリケーションをDRYにするために若干の変更を加えます。

/ Core/ViewData /にBaseViewDataクラスを作成し、一般的なサイト全体のプロパティを保存します。

この後、同じフォルダーにすべてのView 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();
    }

これは非常にうまく機能し、ビューを整頓し、コントローラーを細く保ちます。

21
Mark

ViewModelクラスは、クラスのインスタンスで表される複数のデータを、ビューに渡すことができる管理しやすいオブジェクトにカプセル化するためにあります。

ViewModelクラスを独自のディレクトリにある独自のファイルに格納するのは理にかなっています。私のプロジェクトには、ViewModelsと呼ばれるModelsフォルダーのサブフォルダーがあります。それが私のViewModel(たとえばProductViewModel.cs)が存在する場所です。

14
JMS

モデルを保持するのに適した場所はありません。プロジェクトが大きく、ViewModel(データ転送オブジェクト)が多数ある場合、モデルを別のアセンブリに保持できます。また、サイトプロジェクトの別のフォルダーに保存することもできます。たとえば、 Oxite では、さまざまなクラスも多く含まれるOxiteプロジェクトに配置されます。 Oxiteのコントローラーは別のプロジェクトに移動され、ビューも別のプロジェクトにあります。
CodeCampServer ViewModelsは* Formという名前で、ModelsフォルダーのUIプロジェクトに配置されます。
MvcPress プロジェクトでは、データプロジェクトに配置されます。このプロジェクトには、データベースを操作するためのすべてのコードが含まれています(ただし、このアプローチはお勧めしませんでした。サンプル用です)
だから、多くの視点があることがわかります。私は通常、ViewModel(DTOオブジェクト)をサイトプロジェクトに保持します。しかし、10を超えるモデルがある場合、それらを別のアセンブリに移動することを好みます。通常、この場合、コントローラーを移動してアセンブリを分離します。
別の質問は、すべてのデータをモデルからViewModelに簡単にマップする方法です。 AutoMapper ライブラリをご覧になることをお勧めします。私はそれがとても好きです、それは私のためにすべての汚い仕事をします。
そして、私は SharpArchitecture プロジェクトを見ることをお勧めします。プロジェクトに非常に優れたアーキテクチャを提供し、多くのクールなフレームワークとガイダンス、優れたコミュニティが含まれています。

12
zihotki

これが私のベストプラクティスのコードスニペットです。

    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");
        }
}
6
Omu

すべてのViewModelをModelsフォルダーにスローします(ビジネスロジックはすべて別のServiceLayerプロジェクトにあります)

5
Beep beep

個人的には、ViewModelがささいなものではない場合、別のクラスを使用することをお勧めします。

複数のビューモデルがある場合は、少なくとも1つのディレクトリにパーティション化することをお勧めします。ビューモデルが後で共有される場合、ディレクトリで暗示される名前空間により、新しいアセンブリへの移動が容易になります。

4
Preet Sangha

この場合、ビューとは別のプロジェクトにコントローラーとモデルがあります。

経験則として、ほとんどのViewData ["..."]をViewModelに移動して回避しようとしたため、キャストやマジックストリングを回避しています。これは良いことです。

ViewModelには、パンくずリストとタイトルを描画するためのリストのページネーション情報やページのヘッダー情報など、いくつかの一般的なプロパティも保持されます。この時点で、基本クラスは私の意見ではあまりにも多くの情報を保持しており、基本ビューモデルのページの99%の最も基本的で必要な情報、リストのモデル、モデルの3つの部分に分けることができますそのシナリオの特定のデータを保持し、基本シナリオから継承するフォームの場合。

最後に、特定の情報を処理するために、各エンティティのビューモデルを実装します。

2
Marc Climent

コントローラーのコード:

    [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 Webプロジェクト)

  • DevJet.Web.App.Dictionary(別のクラスライブラリプロジェクト)

    このプロジェクトでは、DAL、BLL、BO、VM(ビューモデルのフォルダー)などのフォルダーを作成しました。

0
C.T.

操作の結果やコンテキストデータなどの一般的に必要なプロパティを持つビューモデルの基本クラスを作成します。現在のユーザーデータとロールを配置することもできます。

class ViewModelBase 
{
  public bool HasError {get;set;} 
  public string ErrorMessage {get;set;}
  public List<string> UserRoles{get;set;}
}

基本コントローラークラスにはPopulateViewModelBase()などのメソッドがあり、このメソッドはコンテキストデータとユーザーロールを埋めます。 HasErrorおよびErrorMessageは、service/dbからデータをプルしている間に例外がある場合、これらのプロパティを設定します。エラーを表示するには、これらのプロパティをビューにバインドします。ユーザー役割を使用して、役割に基づいてビューに非表示セクションを表示できます。

ビューモデルにさまざまなgetアクションを設定するには、ベースコントローラーに抽象メソッド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;
}
}
0
Ajay Kelkar