80

Mọi bài viết được tìm thấy trong Internet về việc sử dụng ViewModels và sử dụng Automapper cung cấp các nguyên tắc về ánh xạ hướng "Điều khiển -> Xem". Bạn lấy một mô hình miền cùng với tất cả các danh sách chọn vào một ViewModel chuyên dụng và chuyển nó vào khung nhìn. Điều đó rõ ràng và tốt đẹp.
Chế độ xem có biểu mẫu và cuối cùng chúng tôi đang thực hiện hành động POST. Ở đây tất cả các Model Binders đều đến hiện trường cùng với [rõ ràng] khác Xem mô hình là [rõ ràng] liên quan với ViewModel gốc ít nhất trong phần quy ước đặt tên vì lợi ích ràng buộc và xác thực.Làm thế nào để ánh xạ Xem mô hình trở lại Mô hình miền trong một hành động POST?

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

Để nó là hành động chèn, chúng tôi 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 ta phải lấy thực thể Domain của chúng ta từ Repository, 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 Repository.

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


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

  • giữ điều khiển mỏng
  • vinh dự thực hành SoC
  • sau Domain-Driven nguyên tắc thiết kế
  • được DRY
  • to be continued ...

Trả lời

6

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 điều khiển cho việc cập nhật có thể trông giống như:

[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ì có thể nhìn thấy trong đoạn mã trên:

  • dữ liệu POST để xem mô hình + xác nhận được thực hiện trong ModelBinder (có thể được exended với bindings tùy chỉnh)
  • Lỗi xử lý (tức là bắt truy cập dữ liệu ngoại lệ ném bởi Repository) có thể được thực hiện bởi [HandleError] lọc

điều khiển hành động là khá t hin và mối quan tâm được tách ra: các vấn đề lập bản đồ đượ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 Repository.

+6

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

+0

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

+0

với cách tiếp cận này bạn không giữ 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 để quan tâm đến công cụ lập bản đồ. – Rookian

34

tôi sử dụng một IBuilder giao diện và thực hiện nó bằng cách sử dụng ValueInjecter

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

...(Thực hiện) RebuildViewModel chỉ gọi BuildViewModel(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 cuz nó là ngắn hơn nhiều, nhưng đó chỉ là không thực sự quan trọng
hy vọng nó sẽ giúp

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

+0

Tôi thích phương pháp này. Một điều tôi không rõ ràng về mặc dù là việc thực hiện IBuilder, đặc biệt là trong ánh sáng của một ứng dụng tầng. Ví dụ, ViewModel của tôi có 3 SelectLists. Việc triển khai trình xây dựng như thế nào lấy ra các giá trị danh sách lựa chọn từ kho lưu trữ? –

+0

@Matt Murrell nhìn vào http://prodinner.codeplex.com Tôi làm điều này trong đó, và tôi gọi nó là IMapper có thay vì IBuilder – Omu

+5

Tôi thích cách tiếp cận này, tôi đã thực hiện một mẫu của nó ở đây: https: // gist. github.com/2379583 –

4

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 của khách hàng. Nếu bạn đã đọc đủ mã ASP.NET MVC trong tự nhiên, bạn có thể thấy sự khác biệt giữa một ViewModel và một EditModel. Tôi nghĩ rằng đó là quan trọng.

ViewModel đại diện cho tất cả thông tin bắt buộc để hiển thị 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ó dữ liệu hoàn toàn để thực hiện kiểm tra để quyết định chính xác những gì sẽ hiển thị. Hành động của Controller GET 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ó lẽ sẽ loại trừ một số dữ liệu từ ViewModel và mặc dù có liên quan tôi nghĩ rằng điều quan trọng là nhận ra chúng thực sự khác nhau.

Một Idea

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

Idea Một

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

Về cơ bản khi người dùng thực hiện các tác vụ trên màn hình bạn đã trình bày, Javascript sẽ bắt đầu tạo 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 của nhân viên. Họ cập nhật họ và thêm địa chỉ mới vì nhân viên gần đây đã kết hôn. Dưới nắp, sản phẩm này tạo ra một đối tượng ChangeEmployeeNameAddEmployeeMailingAddress đối tượng trong 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 từng hành động.

Bạn sẽ cần một ModelBinder thông minh hơn sau đó trình mặc định nhưng JSON serializer tốt sẽ có thể quản lý ánh xạ của các đối tượng hành động phía máy khách đến các đối tượng phía máy chủ. Các máy chủ bên (nếu bạn đang ở trong một 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 Mô hình mà chúng làm việc. Vì vậy, hành động Controller kết thúc chỉ nhận được một Id cho instance Model để kéo và một danh sách các hành động để thực hiện trên 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ó lẽ một cái gì đó như thế này được thực hiện ở 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 
} 

Đó thực sự làm cho việc niêm yết trở lại hành động khá chung chung kể từ khi bạn đang dựa vào ModelBinder của bạn để giúp bạn có được dụ IUserAction chính xác và dụ IUserAction của bạn để thực hiện chính xác logic hoặc (nhiều khả năng) gọi vào Mô hình với thông tin.

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

Dù sao tôi chắc chắn nó không phải là một ý tưởng hoàn hảo, nó sẽ yêu cầu một số JS về phía khách hàng để quản lý, và tôi đã không thể làm một dự án nào để xem nó mở ra như thế nào, nhưng bài viết đã cố gắng suy nghĩ về làm thế nào để đạt được điều đó và trở lại một lần nữa vì vậy tôi figured tôi sẽ đưa ra những suy nghĩ của tôi. Tôi hy vọng nó sẽ giúp và tôi rất thích nghe những cách khác để quản lý các tương tác.

+0

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

+0

Tôi đoán mối lo ngại 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 sẽ nói rằng chỉ cần xây dựng lại ViewModel và chắc chắn rằng nó có một nơi để đưa tin nhắn thông báo của người dùng (có lẽ cả những người tích cực và tiêu cực như lỗi xác nhận). Nếu nó trở thành một vấn đề hiệu suất, bạn luôn có thể cache ViewModel cho đến khi yêu cầu tiếp theo của phiên đó kết thúc (có thể là bài viết của EditModel). –

Các vấn đề liên quan