Làm cách nào để ánh xạ Chế độ xem trở lại Mô hình miền trong một hành động POST?


87

Mọi bài báo tìm thấy trên Internet về cách sử dụng ViewModels và sử dụng Automapper đều đưa ra các nguyên tắc về ánh xạ hướng "Controller -> View". Bạn lấy một mô hình miền cùng với tất cả Danh sách chọn vào một ViewModel chuyên biệt và chuyển nó vào chế độ xem. Đó là rõ ràng và tốt.
Chế độ xem có một biểu mẫu, và cuối cùng chúng ta đang ở trong hành động ĐĂNG. Ở đây, tất cả các Trình kết nối mô hình đều xuất hiện cùng với [rõ ràng là] một Mô hình xem khác [rõ ràng] có liên quan đến ViewModel ban đầu ít nhất là trong một phần của quy ước đặt tên vì mục đích ràng buộc và xác nhận.

Làm cách nào để bạn ánh xạ nó với Mô hình miền của mình?

Hãy để nó là một hành động chèn, chúng ta có thể sử dụng cùng một Automapper. Nhưng nếu đó là một hành động cập nhật thì sao? Chúng tôi phải truy xuất Thực thể miền của mình từ Kho lưu trữ, cập nhật các thuộc tính của nó theo các giá trị trong ViewModel và lưu vào Kho lưu trữ.

PHỤ LỤC 1 (Ngày 9 tháng 2 năm 2010): Đôi khi, chỉ định các thuộc tính của Mô hình là không đủ. Cần thực hiện một số hành động chống lại Mô hình miền theo các giá trị của Mô hình chế độ xem. Tức là, một số phương thức nên được gọi trên Mô hình miền. Có thể, nên có một loại lớp Dịch vụ ứng dụng nằm giữa Bộ điều khiển và Miền để xử lý các Mô hình Chế độ xem ...


Làm thế nào để tổ chức mã này và đặt nó ở đâu để đạt được các mục tiêu sau?

  • giữ cho bộ điều khiển mỏng
  • tôn vinh thực hành SoC
  • tuân theo các nguyên tắc Thiết kế theo hướng miền
  • KHÔ
  • còn tiếp ...

Câu trả lời:


37

Tôi sử dụng giao diện IBuilder và triển khai nó bằng ValueInjecter

public interface IBuilder<TEntity, TViewModel>
{
      TEntity BuildEntity(TViewModel viewModel);
      TViewModel BuildViewModel(TEntity entity);
      TViewModel RebuildViewModel(TViewModel viewModel); 
}

... (triển khai) RebuildViewModel chỉ gọiBuildViewModel(BuilEntity(viewModel))

[HttpPost]
public ActionResult Update(ViewModel model)
{
   if(!ModelState.IsValid)
    {
       return View(builder.RebuildViewModel(model);
    }

   service.SaveOrUpdate(builder.BuildEntity(model));
   return RedirectToAction("Index");
}

btw Tôi không viết ViewModel Tôi viết Input vì nó ngắn hơn nhiều, nhưng điều đó không thực sự quan trọng,
hy vọng nó sẽ giúp

Cập nhật: Hiện tôi đang sử dụng phương pháp này trong Ứng dụng Demo ProDinner ASP.net MVC , nó hiện được gọi là IMapper, cũng có một bản pdf được cung cấp để giải thích chi tiết phương pháp này


Tôi thích cách tiếp cận này. Tuy nhiên, một điều tôi không rõ là việc triển khai IBuilder, đặc biệt là đối với một ứng dụng nhiều tầng. Ví dụ: ViewModel của tôi có 3 SelectLists. Làm cách nào để triển khai trình tạo truy xuất các giá trị danh sách đã chọn từ kho lưu trữ?
Matt Murrell

@Matt Murrell hãy nhìn vào prodinner.codeplex.com Tôi làm điều này ở đó và tôi gọi nó là IMapper ở đó thay vì IBuilder
Omu

6
Tôi thích cách tiếp cận này, tôi đã triển khai một mẫu của nó tại đây: gist.github.com/2379583
Paul Stovell

Theo tôi, nó không tuân theo cách tiếp cận Mô hình miền. Có vẻ như một số phương pháp CRUD cho các yêu cầu không rõ ràng. Chúng ta có nên sử dụng Factories (DDD) và các phương pháp liên quan trong Mô hình miền để chuyển tải một số hành động hợp lý không? Theo cách này, tốt hơn chúng ta nên tải một thực thể từ DB và cập nhật nó theo yêu cầu, phải không? Vì vậy, có vẻ như nó không hoàn toàn chính xác.
Artyom

7

Các công cụ như AutoMapper có thể được sử dụng để cập nhật đối tượng hiện có với dữ liệu từ đối tượng nguồn. Hành động của trình điều khiển để cập nhật có thể giống như sau:

[HttpPost]
public ActionResult Update(MyViewModel viewModel)
{
    MyDataModel dataModel = this.DataRepository.GetMyData(viewModel.Id);
    Mapper<MyViewModel, MyDataModel>(viewModel, dataModel);
    this.Repostitory.SaveMyData(dataModel);
    return View(viewModel);
}

Ngoài những gì hiển thị trong đoạn mã trên:

  • POST dữ liệu để xem mô hình + xác thực được thực hiện trong ModelBinder (có thể được thêm vào với các ràng buộc tùy chỉnh)
  • Xử lý lỗi (tức là bắt các lần ném ngoại lệ truy cập dữ liệu bởi Kho lưu trữ) có thể được thực hiện bằng bộ lọc [HandleError]

Hành động của bộ điều khiển khá mỏng và các mối quan tâm được tách biệt: các vấn đề ánh xạ được giải quyết trong cấu hình AutoMapper, xác thực được thực hiện bởi ModelBinder và truy cập dữ liệu bằng Kho lưu trữ.


6
Tôi không chắc Automapper hữu ích ở đây vì nó không thể đảo ngược việc làm phẳng. Rốt cuộc, Domain Model không phải là một DTO đơn giản như View Model, do đó có thể không đủ để gán một số thuộc tính cho nó. Có thể, một số hành động nên được thực hiện đối với Mô hình miền theo nội dung của Mô hình xem. Tuy nhiên, +1 để chia sẻ cách tiếp cận khá tốt.
Anthony Serdyukov

@Anton ValueInjecter có thể đảo ngược làm phẳng;)
Omu

với cách tiếp cận này, bạn không giữ cho bộ điều khiển mỏng, bạn vi phạm SoC và DRY ... như Omu đã đề cập, bạn nên có một lớp riêng biệt chăm sóc cho nội dung ánh xạ.
Rookian

5

Tôi muốn nói rằng bạn sử dụng lại thuật ngữ ViewModel cho cả hai hướng tương tác với khách hàng. Nếu bạn đã đọc đủ mã ASP.NET MVC trong tự nhiên, bạn có thể đã thấy sự phân biệt giữa ViewModel và EditModel. Tôi nghĩ rằng đó là quan trọng.

ViewModel đại diện cho tất cả thông tin cần thiết để hiển thị một chế độ xem. Điều này có thể bao gồm dữ liệu được hiển thị ở những nơi không tương tác tĩnh và cũng có thể là dữ liệu hoàn toàn để thực hiện kiểm tra để quyết định xem chính xác những gì sẽ hiển thị. Hành động GET của Bộ điều khiển thường chịu trách nhiệm đóng gói ViewModel cho View của nó.

EditModel (hoặc có thể là ActionModel) đại diện cho dữ liệu cần thiết để thực hiện hành động mà người dùng muốn thực hiện cho POST đó. Vì vậy, một EditModel thực sự đang cố gắng mô tả một hành động. Điều này có thể sẽ loại trừ một số dữ liệu khỏi ViewModel và mặc dù có liên quan, tôi nghĩ điều quan trọng là nhận ra chúng thực sự khác nhau.

Một ý tưởng

Điều đó nói rằng bạn có thể rất dễ dàng có một cấu hình AutoMapper để chuyển từ Model -> ViewModel và một cấu hình khác để chuyển từ EditModel -> Model. Sau đó, các hành động Controller khác nhau chỉ cần sử dụng AutoMapper. Địa ngục EditModel có thể có các chức năng trên đó để xác thực các thuộc tính của nó so với mô hình và áp dụng các giá trị đó cho chính Mô hình. Nó không làm bất cứ điều gì khác và bạn vẫn có ModelBinders trong MVC để ánh xạ Yêu cầu tới EditModel.

Một ý tưởng khác

Ngoài ra, điều mà tôi đã nghĩ đến gần đây, loại hoạt động dựa trên ý tưởng về ActionModel là những gì khách hàng đang đăng lại cho bạn thực sự là mô tả về một số hành động mà người dùng đã thực hiện chứ không chỉ là một khối dữ liệu lớn. Điều này chắc chắn sẽ yêu cầu một số Javascript ở phía máy khách để quản lý nhưng tôi nghĩ ý tưởng này rất hấp dẫn.

Về cơ bản khi người dùng thực hiện các hành động trên màn hình mà bạn đã trình bày, Javascript sẽ bắt đầu tạo một danh sách các đối tượng hành động. Một ví dụ có thể là người dùng đang ở màn hình thông tin nhân viên. Họ cập nhật họ và thêm địa chỉ mới vì nhân viên này đã kết hôn gần đây. Dưới các nắp này tạo ra một ChangeEmployeeNamevà một AddEmployeeMailingAddressđối tượng cho một danh sách. Người dùng nhấp vào 'Lưu' để thực hiện các thay đổi và bạn gửi danh sách hai đối tượng, mỗi đối tượng chỉ chứa thông tin cần thiết để thực hiện mỗi hành động.

Bạn sẽ cần một ModelBinder thông minh hơn thì một ModelBinder mặc định nhưng bộ tuần tự JSON tốt sẽ có thể xử lý việc ánh xạ các đối tượng hành động phía máy khách với các đối tượng phía máy chủ. Những cái phía máy chủ (nếu bạn đang ở trong môi trường 2 tầng) có thể dễ dàng có các phương thức hoàn thành hành động trên Model mà chúng làm việc. Vì vậy, hành động Bộ điều khiển kết thúc chỉ nhận được một Id cho cá thể Model để kéo và danh sách các hành động để thực hiện trên đó. Hoặc các hành động có id trong chúng để giữ chúng rất riêng biệt.

Vì vậy, có thể một cái gì đó như thế này được nhận ra ở phía máy chủ:

public interface IUserAction<TModel>
{
     long ModelId { get; set; }
     IEnumerable<string> Validate(TModel model);
     void Complete(TModel model);
}

[Transaction] //just assuming some sort of 2-tier with transactions handled by filter
public ActionResult Save(IEnumerable<IUserAction<Employee>> actions)
{
     var errors = new List<string>();
     foreach( var action in actions ) 
     {
         // relying on ORM's identity map to prevent multiple database hits
         var employee = _employeeRepository.Get(action.ModelId);
         errors.AddRange(action.Validate(employee));
     }

     // handle error cases possibly rendering view with them

     foreach( var action in editModel.UserActions )
     {
         var employee = _employeeRepository.Get(action.ModelId);
         action.Complete(employee);
         // against relying on ORMs ability to properly generate SQL and batch changes
         _employeeRepository.Update(employee);
     }

     // render the success view
}

Điều đó thực sự làm cho hành động đăng lại khá chung chung vì bạn đang dựa vào ModelBinder của mình để lấy cho bạn cá thể IUserAction chính xác và cá thể IUserAction của bạn để thực hiện đúng logic hoặc (nhiều khả năng) gọi vào Mô hình với thông tin.

Nếu bạn đang ở trong môi trường 3 tầng, IUserAction chỉ có thể được tạo ra các DTO đơn giản để bắn qua ranh giới và thực hiện theo một phương pháp tương tự trên tầng ứng dụng. Tùy thuộc vào cách bạn thực hiện mà lớp đó có thể được tách ra rất dễ dàng và vẫn ở trong giao dịch (điều cần lưu ý là yêu cầu / phản hồi của Agatha và tận dụng bản đồ nhận dạng của DI và NHibernate).

Dù sao thì tôi chắc rằng đó không phải là một ý tưởng hoàn hảo, nó sẽ yêu cầu một số JS ở phía khách hàng để quản lý và tôi chưa thể thực hiện một dự án để xem nó diễn ra như thế nào, nhưng bài đăng đang cố gắng nghĩ về cách đến đó và trở lại một lần nữa vì vậy tôi nghĩ rằng tôi sẽ đưa ra suy nghĩ của mình. Tôi hy vọng nó sẽ hữu ích và tôi rất muốn biết những cách khác để quản lý các tương tác.


Hấp dẫn. Về sự khác biệt giữa ViewModel và EditModel ... bạn có kiện rằng đối với một chức năng chỉnh sửa, bạn sẽ sử dụng ViewModel để tạo biểu mẫu và sau đó liên kết với EditModel khi người dùng đăng nó lên không? Nếu vậy, bạn sẽ giải quyết như thế nào với các tình huống mà bạn cần đăng lại biểu mẫu do lỗi xác thực (ví dụ: khi ViewModel chứa các phần tử để điền danh sách thả xuống) - bạn có chỉ bao gồm các phần tử thả xuống trong EditModel không? Trong trường hợp đó, sự khác biệt giữa hai sẽ là gì?
UpTheCreek

Tôi đoán rằng mối quan tâm của bạn là nếu tôi sử dụng EditModel và có lỗi thì tôi phải xây dựng lại ViewModel của mình, điều này có thể rất tốn kém. Tôi muốn nói rằng chỉ cần xây dựng lại ViewModel và đảm bảo rằng nó có một nơi để đặt các thông báo thông báo của người dùng (có thể là cả tích cực và tiêu cực như lỗi xác thực). Nếu đó là vấn đề về hiệu suất, bạn luôn có thể lưu vào bộ nhớ cache của ViewModel cho đến khi yêu cầu tiếp theo của phiên đó kết thúc (có thể là bài đăng của EditModel).
Sean Copenhaver

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.