ViewModelのベストプラクティス


238

この質問から、ビューが表示しようとしているモデルをより正確に反映するViewModelをコントローラーに作成させるのは理にかなっているように見えますが、いくつかの規則に興味があります(MVCパターンは初めてです)。 、それがまだ明らかでない場合)。

基本的に、私は次の質問をしました:

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

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

編集: CodePlexの サンプルNerdDinnerアプリを見ると、ViewModelがControllersの一部であるように見えますが、それでも、それらが独自のファイルに含まれていないことに不快に感じます。


66
NerdDinnerを「ベストプラクティス」の例と正確に呼ぶことはしません。あなたの直感はあなたによく役立ちます。:)
Ryan Montgomery

回答:


211

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

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

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

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

また、名前空間「MyProject.Web.ViewModels」をweb.configに追加して、各ビューに明示的なインポートステートメントを追加することなく、どのビューでもそれらを参照できるようにします。少しだけきれいにします。


3
部分的なビューからPOSTし、ビュー全体を返す場合(モデルエラーの場合)はどうなりますか?部分ビュー内では、親モデルにアクセスできません。
Cosmo

5
@Cosmo:次に、モデルエラーの場合にビュー全体を返すことができるアクションに POSTします。サーバー側では、親モデルを再作成するのに十分です。
Tomas Aschan、2011年

ログイン[POST]およびログイン[GET]アクションについてはどうですか?異なるビューモデルで?
Bart Calixto

通常、ログイン[GET]はデータをロードする必要がないため、ViewModelを呼び出さないでください。
Andre Figueiredo 2013年

素晴らしいアドバイス。モデル/ VMプロパティのデータアクセス、処理、設定はどこに行えばよいですか?私の場合、ローカルのCMSデータベースからのデータとWebサービスからのデータがあり、モデルに設定する前に処理/操作する必要があります。これらすべてをコントローラーに入れると、かなり面倒になります。
xr280xr 2015年

124

カテゴリ(コントローラ、ViewModels、フィルタなど)によってクラスを分離することは意味がありません。

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

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

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


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


13
これを行うと、物事はかなりすぐに乱雑になります。
UpTheCreek 2009年

14
いいえ、厄介なのは、すべてのコントローラーを1つのdir /名前空間に配置することです。5つのコントローラーがあり、それぞれが5つのビューモデルを使用している場合、25のビューモデルがあります。名前空間は、コードを編成するためのメカニズムであり、ここでは違いはありません。
Max Toro、

41
@Max Toro:あまりにも多くの反対投票を受けたことに驚きました。ASP.Net MVCでしばらく作業した後、すべてのViewModelを1つの場所に配置し、すべてのコントローラーを別の場所に配置し、すべてのビューを別の場所に配置することに多くの苦痛を感じています。MVCは関連する部分のトリオであり、それら結合されています-それらは互いにサポートしています。私がすることができます私は解決策のように感じるくらい多くのコントローラー、のviewmodels、同じディレクトリ内の指定されたセクションのライブ一緒のビュー場合に編成します。MyApp / Accounts / Controller.cs、MyApp / Accounts / Create / ViewModel.cs、MyApp / Accounts / Create / View.cshtmlなど
quentin-starin

13
@RyanJMcGowan懸念の分離は、クラスの分離ではありません。
Max Toro

12
@RyanJMcGowanは、開発にどのようにアプローチしても、特に大規模なアプリの場合、問題は最終的に何をもたらすかです。メンテナンスモードになると、すべてのモデルではなくすべてのコントローラーについて考える必要がなくなり、一度に1つの機能を追加します。
Max Toro

21

アプリケーションクラスを "Core"(または別のクラスライブラリ)と呼ばれるサブフォルダーに保持し、KIGGサンプルアプリケーションと同じメソッドを使用しますが、若干の変更を加えてアプリケーションをより乾燥させます。

共通のサイト全体のプロパティを格納する/ Core / ViewData /にBaseViewDataクラスを作成します。

この後、同じViewViewDataクラスからすべてのビュー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();
    }

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


13

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

独自のディレクトリ内の独自のファイルにViewModelクラスを置くことは理にかなっています。私のプロジェクトには、ViewModelsというModelsフォルダーのサブフォルダーがあります。ここに私のViewModels(例ProductViewModel.cs)が存在します。


13

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


8
ViewModels!= DTO
Bart Calixto 2013年

6

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

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

5

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


4

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

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


2

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

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

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

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


0

コントローラのコード:

    [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(ビューモデル用のフォルダー)などのフォルダーをいくつか作成しました


こんにちは、Entryクラスの構造を教えてください。
Dinis Cruz、

0

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

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;
}
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.