Praktik Terbaik ViewModel

238

Dari pertanyaan ini , sepertinya masuk akal untuk memiliki controller membuat ViewModel yang lebih akurat mencerminkan model yang coba ditampilkan, tetapi saya ingin tahu tentang beberapa konvensi (saya baru dengan pola MVC , jika itu belum jelas).

Pada dasarnya, saya punya pertanyaan berikut:

  1. Saya biasanya suka memiliki satu kelas / file. Apakah ini masuk akal dengan ViewModel jika hanya dibuat untuk memberikan data dari pengontrol ke tampilan?
  2. Jika ViewModel memiliki file sendiri, dan Anda menggunakan struktur direktori / proyek untuk menjaga hal-hal terpisah, di mana file ViewModel berada? Di direktori Controllers ?

Itu pada dasarnya untuk saat ini. Saya mungkin memiliki beberapa pertanyaan lagi, tetapi ini telah mengganggu saya selama sekitar satu jam terakhir, dan saya dapat menemukan panduan yang konsisten di tempat lain.

EDIT: Melihat contoh aplikasi NerdDinner di CodePlex, sepertinya ViewModels adalah bagian dari Controllers , tetapi masih membuat saya tidak nyaman karena mereka tidak ada dalam file mereka sendiri.

jerhinesmith
sumber
66
Saya tidak akan menyebut NerdDinner sebagai contoh "Praktik Terbaik". Intuisi Anda sangat membantu Anda. :)
Ryan Montgomery

Jawaban:

211

Saya membuat apa yang saya sebut "ViewModel" untuk setiap tampilan. Saya meletakkannya di folder bernama ViewModels di proyek Web MVC saya. Saya menamai mereka setelah controller dan action (atau view) yang mereka wakili. Jadi jika saya perlu meneruskan data ke tampilan Daftar pada pengontrol Keanggotaan saya membuat kelas MembershipSignUpViewModel.cs dan memasukkannya ke dalam folder ViewModels.

Kemudian saya menambahkan properti dan metode yang diperlukan untuk memfasilitasi transfer data dari controller ke tampilan. Saya menggunakan Automapper untuk mendapatkan dari Model View saya ke Model Domain dan kembali lagi jika perlu.

Ini juga berfungsi dengan baik untuk ViewModels komposit yang berisi properti yang dari jenis ViewModels lainnya. Misalnya jika Anda memiliki 5 widget pada halaman indeks di pengontrol keanggotaan, dan Anda membuat Model View untuk setiap tampilan parsial - bagaimana Anda meneruskan data dari tindakan Indeks ke parsial? Anda menambahkan properti ke MembershipIndexViewModel dari tipe MyPartialViewModel dan ketika merender sebagian Anda akan melewati Model.MyPartialViewModel.

Melakukannya dengan cara ini memungkinkan Anda untuk menyesuaikan sebagian properti ViewModel tanpa harus mengubah tampilan Indeks sama sekali. Itu masih baru saja lewat di Model.MyPartialViewModel sehingga ada sedikit kesempatan bahwa Anda harus melalui seluruh rantai parsial untuk memperbaiki sesuatu ketika semua yang Anda lakukan adalah menambahkan properti ke ViewModel parsial.

Saya juga akan menambahkan namespace "MyProject.Web.ViewModels" ke web.config sehingga memungkinkan saya untuk merujuk mereka dalam tampilan apa pun tanpa pernah menambahkan pernyataan impor eksplisit pada setiap tampilan. Hanya membuatnya sedikit lebih bersih.

Ryan Montgomery
sumber
3
Bagaimana jika Anda ingin POST dari tampilan sebagian dan mengembalikan seluruh tampilan (jika terjadi kesalahan model)? Dalam tampilan sebagian Anda tidak memiliki akses ke model induk.
Cosmo
5
@ Cosmo: Kemudian POST ke tindakan yang dapat mengembalikan seluruh tampilan jika terjadi kesalahan model. Di sisi server, Anda memiliki cukup untuk membuat ulang model induk.
Tomas Aschan
Bagaimana dengan tindakan login [POST] dan login [GET]? dengan model tampilan yang berbeda?
Bart Calixto
Biasanya, login [GET] tidak memanggil ViewModel karena tidak perlu memuat data apa pun.
Andre Figueiredo
Saran bagus. Di mana seharusnya akses data, pemrosesan dan pengaturan properti model / VM pergi? Dalam kasus saya, kami akan memiliki beberapa data yang berasal dari database CMS lokal dan beberapa yang berasal dari layanan web, yang perlu diproses / dimanipulasi sebelum ditetapkan pada model. Menempatkan semua itu di controller menjadi sangat berantakan.
xr280xr
124

Memisahkan kelas berdasarkan kategori (Controllers, ViewModels, Filter dll.) Adalah omong kosong.

Jika Anda ingin menulis kode untuk bagian Beranda situs web Anda (/) kemudian buat folder bernama Rumah, dan letakkan di sana HomeController, IndexViewModel, AboutViewModel, dll. Dan semua kelas terkait yang digunakan oleh tindakan Rumah.

Jika Anda telah berbagi kelas, seperti ApplicationController, Anda dapat meletakkannya di root proyek Anda.

Mengapa memisahkan hal-hal yang terkait (HomeController, IndexViewModel) dan menyatukan hal-hal yang tidak memiliki hubungan sama sekali (HomeController, AccountController)?


Saya menulis posting blog tentang topik ini.

Max Toro
sumber
13
Banyak hal akan menjadi sangat berantakan dengan cepat jika Anda melakukan ini.
UpTheCreek
14
Tidak, berantakan adalah untuk menempatkan semua pengontrol dalam satu dir / namespace. Jika Anda memiliki 5 pengontrol, masing-masing menggunakan 5 viewmodels, maka Anda punya 25 viewmodels. Namespace adalah mekanisme untuk mengatur kode, dan seharusnya tidak berbeda di sini.
Max Toro
41
@ Max Toro: kaget kamu begitu banyak diputuskan. Setelah beberapa waktu bekerja di ASP.Net MVC, saya merasa sangat sakit karena memiliki semua ViewModels di satu tempat, semua pengontrol di tempat lain, dan semua Views di tempat lain. MVC adalah trio potongan terkait, mereka yang digabungkan - mereka saling mendukung. Aku merasa seperti solusi bisa saya jauh lebih terorganisir jika Controller, ViewModels, dan Tampilan untuk diberikan bagian bersama-sama hidup dalam direktori yang sama. MyApp / Akun / Controller.cs, MyApp / Akun / Buat / ViewModel.cs, MyApp / Akun / Buat / View.cshtml, dll.
quentin-starin
13
@RyanJMcGowan memisahkan masalah bukanlah pemisahan kelas.
Max Toro
12
@RyanJMcGowan tidak peduli bagaimana Anda mendekati pengembangan, masalahnya adalah apa yang Anda dapatkan, khususnya untuk aplikasi besar. Setelah dalam mode pemeliharaan Anda tidak memikirkan semua model lalu semua pengontrol, Anda menambahkan satu fungsi pada satu waktu.
Max Toro
21

Saya menyimpan kelas aplikasi saya dalam sub folder yang disebut "Core" (atau perpustakaan kelas terpisah) dan menggunakan metode yang sama dengan aplikasi sampel KIGG tetapi dengan sedikit perubahan untuk membuat aplikasi saya lebih KERING.

Saya membuat kelas BaseViewData di / Core / ViewData / di mana saya menyimpan properti umum situs lebar.

Setelah ini, saya juga membuat semua tampilan saya kelas ViewData di folder yang sama yang kemudian berasal dari BaseViewData dan memiliki properti tampilan tertentu.

Lalu saya membuat ApplicationController yang berasal dari semua pengontrol saya. ApplicationController memiliki Metode GetViewData generik sebagai berikut:

protected T GetViewData<T>() where T : BaseViewData, new()
    {
        var viewData = new T
        {
           Property1 = "value1",
           Property2 = this.Method() // in the ApplicationController
        };
        return viewData;
    }

Akhirnya, dalam tindakan Pengontrol saya, saya melakukan hal berikut untuk membangun Model ViewData saya

public ActionResult Index(int? id)
    {
        var viewData = this.GetViewData<PageViewData>();
        viewData.Page = this.DataContext.getPage(id); // ApplicationController
        ViewData.Model = viewData;
        return View();
    }

Saya pikir ini bekerja dengan sangat baik dan itu membuat pandangan Anda rapi dan pengendali Anda kurus.

Menandai
sumber
13

Kelas ViewModel hadir untuk merangkum beberapa bagian data yang diwakili oleh instance kelas menjadi satu objek yang mudah dikelola yang dapat Anda sampaikan ke View Anda.

Masuk akal untuk memiliki kelas ViewModel Anda di file mereka sendiri, di direktori sendiri. Dalam proyek saya, saya memiliki sub-folder dari folder Model yang disebut ViewModels. Di situlah ViewModels saya (mis. ProductViewModel.cs) Hidup.

JMS
sumber
13

Tidak ada tempat yang baik untuk menyimpan model Anda. Anda dapat menyimpannya dalam perakitan terpisah jika proyeknya besar dan ada banyak ViewModels (Objek Transfer Data). Anda juga dapat menyimpannya di folder terpisah dari proyek situs. Sebagai contoh, di Oxite mereka ditempatkan di proyek Oxite yang berisi banyak kelas juga. Pengendali di Oxite dipindahkan ke proyek yang terpisah dan pandangan dalam proyek yang terpisah juga.
Dalam CodeCampServer, ViewModels diberi nama * Form dan ditempatkan di proyek UI di folder Models.
Di proyek MvcPress mereka ditempatkan di proyek Data, yang juga berisi semua kode untuk bekerja dengan database dan sedikit lebih banyak (tapi saya tidak merekomendasikan pendekatan ini, itu hanya untuk sampel)
Jadi Anda bisa melihat ada banyak sudut pandang. Saya biasanya menyimpan ViewModels (objek DTO) saya di proyek situs. Tetapi ketika saya memiliki lebih dari 10 model, saya lebih suka memindahkannya ke perakitan terpisah. Biasanya dalam kasus ini saya akan memindahkan pengontrol untuk memisahkan rakitan juga.
Pertanyaan lain adalah bagaimana cara memetakan semua data dengan mudah dari model ke ViewModel Anda. Saya sarankan untuk melihat di pustaka AutoMapper . Saya sangat menyukainya, itu semua kotor untuk saya.
Dan saya juga menyarankan untuk melihat proyek SharpArchitecture . Ini memberikan arsitektur yang sangat baik untuk proyek-proyek dan berisi banyak kerangka kerja keren dan bimbingan dan komunitas yang hebat.

zihotki
sumber
8
ViewModels! = DTO
Bart Calixto
6

inilah cuplikan kode dari praktik terbaik saya:

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

Kami membuang semua ViewModels kami di folder Models (semua logika bisnis kami ada di proyek ServiceLayer yang terpisah)

Bip bip
sumber
4

Secara pribadi saya akan menyarankan jika ViewModel adalah hal sepele kemudian gunakan kelas yang terpisah.

Jika Anda memiliki lebih dari satu model tampilan maka saya sarankan masuk akal untuk mempartisi setidaknya dalam direktori. jika model tampilan kemudian dibagikan maka ruang nama yang tersirat dalam direktori membuatnya lebih mudah untuk pindah ke majelis baru.

Praha Sangha
sumber
2

Dalam kasus kami, kami memiliki Model bersama dengan Pengendali dalam proyek yang terpisah dari Views.

Sebagai aturan praktis, kami telah mencoba untuk memindahkan dan menghindari sebagian besar ViewData ["..."] hal-hal ke ViewModel sehingga kami menghindari coran dan string sihir, yang merupakan hal yang baik.

ViewModel juga memiliki beberapa properti umum seperti informasi pagination untuk daftar atau informasi header halaman untuk menggambar remah roti dan judul. Pada saat ini, kelas dasar menyimpan terlalu banyak informasi menurut pendapat saya dan kami dapat membaginya dalam tiga bagian, informasi paling dasar dan penting untuk 99% halaman pada model tampilan dasar, dan kemudian model untuk daftar dan model untuk formulir yang menyimpan data spesifik untuk skenario itu dan mewarisi dari basis.

Akhirnya, kami menerapkan model tampilan untuk setiap entitas untuk berurusan dengan informasi spesifik.

Marc Climent
sumber
0

kode di controller:

    [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
    }

kode dalam model tampilan:

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       
    }

proyek:

  • DevJet.Web (proyek web ASP.NET MVC)

  • DevJet.Web.App.Dictionary (proyek Perpustakaan Kelas terpisah)

    dalam proyek ini, saya membuat beberapa folder seperti: DAL, BLL, BO, VM (folder untuk model tampilan)

CT
sumber
Hai, dapatkah Anda membagikan apa struktur kelas Entry?
Dinis Cruz
0

Buat kelas dasar model tampilan yang memiliki properti yang biasanya diperlukan seperti hasil operasi dan data kontekstual, Anda juga dapat menempatkan data pengguna saat ini dan peran

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

Di kelas pengendali dasar memiliki metode seperti PopulateViewModelBase () metode ini akan mengisi data kontekstual dan peran pengguna. HasError dan ErrorMessage, atur properti ini jika ada pengecualian saat menarik data dari layanan / db. Bind properti ini pada tampilan untuk menunjukkan kesalahan. Peran pengguna dapat digunakan untuk menampilkan bagian sembunyikan pada tampilan berdasarkan pada peran.

Untuk mengisi model tampilan dalam berbagai tindakan, dapat dibuat konsisten dengan memiliki basis controller dengan metode abstrak FillModel

class BaseController :BaseController 
{
   public PopulateViewModelBase(ViewModelBase model) 
{
   //fill up common data. 
}
abstract ViewModelBase FillModel();
}

Di pengontrol

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;
}
}
Ajay Kelkar
sumber