Thực hành tốt nhất của ViewModel


238

Từ câu hỏi này , có vẻ hợp lý khi có bộ điều khiển tạo ViewModel phản ánh chính xác hơn mô hình mà chế độ xem đang cố hiển thị, nhưng tôi tò mò về một số quy ước (Tôi chưa quen với mẫu MVC , nếu nó đã không rõ ràng).

Về cơ bản, tôi đã có những câu hỏi sau:

  1. Tôi thường muốn có một lớp / tập tin. Điều này có hợp lý với ViewModel không nếu nó chỉ được tạo để chuyển dữ liệu từ bộ điều khiển sang chế độ xem?
  2. Nếu ViewModel thuộc về tệp riêng của nó và bạn đang sử dụng cấu trúc thư mục / dự án để tách biệt mọi thứ, thì tệp ViewModel thuộc về đâu? Trong thư mục Bộ điều khiển ?

Về cơ bản là bây giờ. Tôi có thể có thêm một vài câu hỏi sắp tới, nhưng điều này đã làm phiền tôi trong một giờ qua hoặc lâu hơn, và tôi dường như có thể tìm thấy hướng dẫn nhất quán ở nơi khác.

EDIT: Nhìn vào ứng dụng NerdDinner mẫu trên CodePlex, có vẻ như ViewModels là một phần của Bộ điều khiển , nhưng điều đó vẫn khiến tôi không thoải mái khi chúng không có trong các tệp của riêng họ.


66
Tôi sẽ không chính xác gọi NerdDinner là một ví dụ "Thực tiễn tốt nhất". Trực giác của bạn phục vụ bạn tốt. :)
Ryan Montgomery

Câu trả lời:


211

Tôi tạo cái mà tôi gọi là "ViewModel" cho mỗi chế độ xem. Tôi đặt chúng vào một thư mục có tên ViewModels trong dự án MVC Web của tôi. Tôi đặt tên cho chúng theo bộ điều khiển và hành động (hoặc xem) mà chúng đại diện. Vì vậy, nếu tôi cần chuyển dữ liệu sang chế độ xem Đăng ký trên bộ điều khiển Thành viên, tôi tạo một lớp MemberhipSignUpViewModel.cs và đặt nó vào thư mục ViewModels.

Sau đó, tôi thêm các thuộc tính và phương thức cần thiết để tạo điều kiện cho việc chuyển dữ liệu từ bộ điều khiển sang dạng xem. Tôi sử dụng Automapper để lấy từ ViewModel của mình sang Mô hình miền và quay lại nếu cần.

Điều này cũng hoạt động tốt đối với các ViewModels tổng hợp có chứa các thuộc tính thuộc loại ViewModels khác. Chẳng hạn, nếu bạn có 5 widget trên trang chỉ mục trong bộ điều khiển thành viên và bạn đã tạo ViewModel cho mỗi chế độ xem một phần - làm thế nào để bạn chuyển dữ liệu từ hành động Index sang các phần? Bạn thêm một thuộc tính vào MemberhipIndexViewModel loại MyPartialViewModel và khi kết xuất một phần bạn sẽ vượt qua trong Model.MyPartialViewModel.

Làm theo cách này cho phép bạn điều chỉnh các thuộc tính ViewModel một phần mà không phải thay đổi chế độ xem Chỉ mục. Nó vẫn chỉ chuyển qua Model.MyPartialViewModel nên ít có khả năng bạn sẽ phải trải qua toàn bộ chuỗi hạt để sửa một cái gì đó khi tất cả những gì bạn đang làm là thêm một thuộc tính vào ViewModel một phần.

Tôi cũng sẽ thêm không gian tên "MyProject.Web.ViewModels" vào web.config để cho phép tôi tham chiếu chúng trong bất kỳ chế độ xem nào mà không cần thêm câu lệnh nhập rõ ràng trên mỗi chế độ xem. Chỉ cần làm cho nó sạch hơn một chút.


3
Điều gì xảy ra nếu bạn muốn POST từ chế độ xem một phần và trả về toàn bộ chế độ xem (trong trường hợp có lỗi mô hình)? Trong chế độ xem một phần, bạn không có quyền truy cập vào mô hình cha.
Cosmo

5
@Cosmo: Sau đó, POST cho một hành động có thể trả về toàn bộ khung nhìn trong trường hợp có lỗi mô hình. Về phía máy chủ, bạn có đủ để tạo lại mô hình cha.
Tomas Aschan

Còn hành động đăng nhập [POST] và đăng nhập [GET] thì sao? với các chế độ xem khác nhau?
Bart Calix đến

Thông thường, đăng nhập [GET] không gọi ViewModel vì không cần tải bất kỳ dữ liệu nào.
Andre Figueiredo

Lời khuyên tuyệt vời. Truy cập dữ liệu, xử lý và cài đặt các thuộc tính mô hình / VM sẽ đi đâu? Trong trường hợp của tôi, chúng tôi sẽ có một số dữ liệu đến từ cơ sở dữ liệu CMS cục bộ và một số đến từ các dịch vụ web, chúng sẽ cần được xử lý / thao tác trước khi được đặt trên một mô hình. Đặt tất cả những thứ đó vào bộ điều khiển trở nên khá lộn xộn.
xr280xr

124

Tách các lớp theo thể loại (Bộ điều khiển, ViewModels, Bộ lọc, v.v.) là vô nghĩa.

Nếu bạn muốn viết mã cho phần Home của trang web của bạn (/) thì hãy tạo một thư mục có tên Home và đặt HomeContoder, IndexViewModel, AboutViewModel, v.v. và tất cả các lớp liên quan được sử dụng bởi các hành động Home.

Nếu bạn đã chia sẻ các lớp, như ApplicationContoder, bạn có thể đặt nó ở thư mục gốc của dự án.

Tại sao phải tách những thứ có liên quan (HomeContoder, IndexViewModel) và giữ những thứ không liên quan đến nhau (HomeContoder, AccountContoder)?


Tôi đã viết một bài blog về chủ đề này.


13
Mọi thứ sẽ trở nên khá lộn xộn khá nhanh nếu bạn làm điều này.
UpTheCux

14
Không, lộn xộn là đặt tất cả các bộ điều khiển trong một không gian tên / thư mục. Nếu bạn có 5 bộ điều khiển, mỗi bộ sử dụng 5 chế độ xem, thì bạn đã có 25 bộ điều khiển. Không gian tên là cơ chế để tổ chức mã và không nên khác biệt ở đây.
Max Toro

41
@Max Toro: ngạc nhiên khi bạn bị hạ bệ rất nhiều. Sau một thời gian làm việc trên ASP.Net MVC, tôi cảm thấy rất đau đớn khi có tất cả các ViewModels ở một nơi, tất cả các bộ điều khiển ở một nơi khác và tất cả các Chế độ xem ở một nơi khác. MVC là một bộ ba phần liên quan, chúng được ghép nối - chúng hỗ trợ lẫn nhau. Tôi cảm thấy như một giải pháp có thể cho tôi nhiều hơn tổ chức nếu Controller, ViewModels, và xem cho cùng sống phần được đưa ra trong cùng thư mục. MyApp / Tài khoản / Trình điều khiển.cs, MyApp / Tài khoản / Tạo / ViewModel.cs, MyApp / Tài khoản / Tạo / View.cshtml, v.v.
-starin

13
@RyanJMcGowan tách mối quan tâm không phải là tách lớp.
Max Toro

12
@RyanJMcGowan cho dù bạn tiếp cận vấn đề phát triển như thế nào thì vấn đề là gì, đặc biệt đối với các ứng dụng lớn. Khi ở chế độ bảo trì, bạn không nghĩ về tất cả các kiểu máy rồi tất cả các bộ điều khiển, bạn thêm một chức năng một lần.
Max Toro

21

Tôi giữ các lớp ứng dụng của mình trong một thư mục con có tên là "Core" (hoặc thư viện lớp riêng biệt) và sử dụng các phương thức tương tự như ứng dụng mẫu KIGG nhưng với một số thay đổi nhỏ để làm cho các ứng dụng của tôi DRY hơn.

Tôi tạo một lớp BaseViewData trong / Core / ViewData / nơi tôi lưu trữ các thuộc tính rộng của trang web chung.

Sau này, tôi cũng tạo tất cả các lớp ViewData trong cùng một thư mục, sau đó xuất phát từ BaseViewData và có các thuộc tính xem cụ thể.

Sau đó, tôi tạo một ApplicationContoder mà tất cả các bộ điều khiển của tôi xuất phát từ đó. ApplicationContoder có một Phương thức GetViewData chung như sau:

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

Cuối cùng, trong hành động Trình điều khiển của tôi, tôi làm như sau để xây dựng Mô hình ViewData của mình

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

Tôi nghĩ rằng điều này hoạt động thực sự tốt và nó giữ cho quan điểm của bạn gọn gàng và bộ điều khiển của bạn gầy.


13

Một lớp ViewModel có mặt để đóng gói nhiều phần dữ liệu được thể hiện bởi các thể hiện của các lớp thành một đối tượng dễ quản lý mà bạn có thể chuyển đến Chế độ xem của mình.

Sẽ có ý nghĩa khi có các lớp ViewModel của bạn trong các tệp riêng của chúng, trong thư mục riêng. Trong các dự án của tôi, tôi có một thư mục con của thư mục Mô hình có tên là ViewModels. Đó là nơi ViewModels của tôi (ví dụ ProductViewModel.cs) sống.


13

Không có nơi nào tốt để giữ các mô hình của bạn. Bạn có thể giữ chúng trong cụm riêng nếu dự án lớn và có nhiều ViewModels (Đối tượng truyền dữ liệu). Ngoài ra, bạn có thể giữ chúng trong thư mục riêng của dự án trang web. Ví dụ, trong Oxite, chúng được đặt trong dự án Oxite chứa rất nhiều lớp khác nhau. Bộ điều khiển trong Oxite được chuyển sang dự án riêng và các khung nhìn cũng nằm trong dự án riêng.
Trong CodeCampServer ViewModels có tên * Form và chúng được đặt trong dự án UI trong thư mục Mô hình.
Trong MvcPress dự án chúng được đặt trong dự án Dữ liệu, cũng chứa tất cả mã để làm việc với cơ sở dữ liệu và một chút nữa (nhưng tôi không đề xuất phương pháp này, nó chỉ dành cho một mẫu)
Vì vậy, bạn có thể thấy có nhiều quan điểm. Tôi thường giữ ViewModels (đối tượng DTO) của mình trong dự án trang web. Nhưng khi tôi có hơn 10 mô hình, tôi thích chuyển chúng sang lắp ráp riêng. Thông thường trong trường hợp này tôi cũng đang di chuyển bộ điều khiển để tách lắp ráp.
Một câu hỏi khác là làm thế nào để dễ dàng ánh xạ tất cả dữ liệu từ mô hình vào ViewModel của bạn. Tôi đề nghị nên xem AutoMapper thư viện . Tôi thích nó rất nhiều, nó làm tất cả công việc bẩn thỉu cho tôi.
Và tôi cũng đề nghị xem xét dự án SharpArch architecture . Nó cung cấp kiến ​​trúc rất tốt cho các dự án và nó chứa rất nhiều khung và hướng dẫn tuyệt vời và cộng đồng tuyệt vời.


8
ViewModels! = DTO
Bart Calix đến

6

đây là đoạn mã từ các thực tiễn tốt nhất của tôi:

    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

Chúng tôi ném tất cả ViewModels của mình vào thư mục Mô hình (tất cả logic nghiệp vụ của chúng tôi nằm trong một dự án ServiceLayer riêng)


4

Cá nhân tôi đề nghị nếu ViewModel là bất cứ thứ gì ngoại trừ tầm thường thì hãy sử dụng một lớp riêng.

Nếu bạn có nhiều hơn một mô hình xem thì tôi khuyên bạn nên phân vùng nó trong ít nhất một thư mục. nếu mô hình khung nhìn được chia sẻ sau đó thì không gian tên được ngụ ý trong thư mục giúp việc di chuyển đến một cụm mới dễ dàng hơn.


2

Trong trường hợp của chúng tôi, chúng tôi có các Mô hình cùng với Bộ điều khiển trong một dự án tách biệt với Chế độ xem.

Theo nguyên tắc thông thường, chúng tôi đã cố gắng di chuyển và tránh hầu hết các công cụ ViewData ["..."] cho ViewModel để chúng tôi tránh các vật đúc và chuỗi ma thuật, đó là một điều tốt.

ViewModel cũng chứa một số thuộc tính phổ biến như thông tin phân trang cho danh sách hoặc thông tin tiêu đề của trang để vẽ mẩu bánh mì và tiêu đề. Tại thời điểm này, lớp cơ sở chứa quá nhiều thông tin theo ý kiến ​​của tôi và chúng tôi có thể chia nó thành ba phần, thông tin cơ bản và cần thiết nhất cho 99% các trang trên mô hình chế độ xem cơ sở, sau đó là mô hình cho danh sách và mô hình cho các biểu mẫu chứa dữ liệu cụ thể cho các kịch bản đó và kế thừa từ cơ sở.

Cuối cùng, chúng tôi triển khai mô hình xem cho từng thực thể để xử lý thông tin cụ thể.


0

mã trong bộ điều khiển:

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

mã trong mô hình xem:

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       
    }

dự án:

  • DevJet.Web (dự án web ASP.NET MVC)

  • DevJet.Web.App.Dixi (một dự án Thư viện lớp riêng biệt)

    trong dự án này, tôi đã tạo một số thư mục như: DAL, BLL, BO, VM (thư mục để xem mô hình)


Xin chào, bạn có thể chia sẻ cấu trúc của lớp Entry là gì không?
Dinis Cruz

0

Tạo một lớp cơ sở mô hình xem có các thuộc tính thường được yêu cầu như kết quả của hoạt động và dữ liệu theo ngữ cảnh, bạn cũng có thể đặt dữ liệu và vai trò người dùng hiện tại

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

Trong lớp trình điều khiển cơ sở có một phương thức như PopViewViewModelBase () phương thức này sẽ điền vào dữ liệu theo ngữ cảnh và vai trò người dùng. HasError và ErrorMessage, đặt các thuộc tính này nếu có ngoại lệ trong khi lấy dữ liệu từ dịch vụ / db. Ràng buộc các thuộc tính này trên xem để hiển thị lỗi. Vai trò người dùng có thể được sử dụng để hiển thị phần ẩn trên chế độ xem dựa trên vai trò.

Để đưa các mô hình khung nhìn vào các hành động get khác nhau, nó có thể được thực hiện nhất quán bằng cách có bộ điều khiển cơ sở với phương thức trừu tượng FillModel

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

Trong bộ điều khiển

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;
}
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.