2010-02-15 27 views
14

Câu hỏi này được lấy cảm hứng từ cuộc đấu tranh của tôi với ASP.NET MVC, nhưng tôi nghĩ nó cũng áp dụng cho các tình huống khác.Làm thế nào để "DRY up" C# thuộc tính trong mô hình và ViewModels?

Hãy nói rằng tôi có một mô hình ORM tạo và hai ViewModels (một cho một "chi tiết" cái nhìn và một cho một "chỉnh sửa" quan điểm):

Mẫu

public class FooModel // ORM generated 
{ 
    public int Id { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string EmailAddress { get; set; } 
    public int Age { get; set; } 
    public int CategoryId { get; set; } 
} 

Display ViewModel

public class FooDisplayViewModel // use for "details" view 
{ 
    [DisplayName("ID Number")] 
    public int Id { get; set; } 

    [DisplayName("First Name")] 
    public string FirstName { get; set; } 

    [DisplayName("Last Name")] 
    public string LastName { get; set; } 

    [DisplayName("Email Address")] 
    [DataType("EmailAddress")] 
    public string EmailAddress { get; set; } 

    public int Age { get; set; } 

    [DisplayName("Category")] 
    public string CategoryName { get; set; } 
} 

Sửa ViewModel

public class FooEditViewModel // use for "edit" view 
{ 
    [DisplayName("First Name")] // not DRY 
    public string FirstName { get; set; } 

    [DisplayName("Last Name")] // not DRY 
    public string LastName { get; set; } 

    [DisplayName("Email Address")] // not DRY 
    [DataType("EmailAddress")] // not DRY 
    public string EmailAddress { get; set; } 

    public int Age { get; set; } 

    [DisplayName("Category")] // not DRY 
    public SelectList Categories { get; set; } 
} 

Lưu ý rằng các thuộc tính trên Chế độ xem không phải là DRY - nhiều thông tin được lặp lại. Bây giờ hãy tưởng tượng kịch bản này nhân với 10 hoặc 100, và bạn có thể thấy rằng nó có thể nhanh chóng trở nên khá tẻ nhạt và dễ bị lỗi để đảm bảo tính nhất quán trên ViewModels (và do đó trên các Views).

Tôi làm cách nào để "TẮT" mã này?

Trước khi bạn trả lời, "Chỉ cần đặt tất cả các thuộc tính trên FooModel", tôi đã thử điều đó, nhưng nó không hoạt động vì tôi cần giữ cho ViewModels "phẳng". Nói cách khác, tôi không thể chỉ soạn mỗi ViewModel với một Model - tôi cần ViewModel của mình chỉ có các thuộc tính (và các thuộc tính) cần được View sử dụng, và View không thể burrow vào các thuộc tính con nhận được các giá trị.

Cập nhật

LukLed của câu trả lời gợi ý sử dụng thừa kế. Điều này chắc chắn làm giảm số lượng mã không DRY, nhưng nó không loại bỏ nó. Lưu ý rằng, trong ví dụ của tôi ở trên, thuộc tính DisplayName cho thuộc tính Category cần phải được viết hai lần vì loại dữ liệu của thuộc tính khác nhau giữa Chế độ xem hiển thị và chỉnh sửa. Đây sẽ không phải là một vấn đề lớn trên quy mô nhỏ, nhưng khi kích thước và độ phức tạp của một dự án tăng lên (hãy tưởng tượng nhiều thuộc tính hơn, nhiều thuộc tính hơn cho mỗi thuộc tính, nhiều lượt xem hơn cho mỗi mô hình), vẫn có khả năng "lặp lại chính mình" một số tiền hợp lý. Có lẽ tôi đang dùng DRY quá xa ở đây, nhưng tôi vẫn muốn có tất cả "tên thân thiện", kiểu dữ liệu, quy tắc xác thực, v.v. chỉ được gõ một lần.

Trả lời

7

Tôi sẽ giả định rằng bạn thực hiện điều này để tận dụng lợi thế của Trình biên tập HtmlHelpers và DisplayFor và không muốn chi phí của việc kê khai giống nhau 4000 lần trong suốt ứng dụng.

Cách dễ nhất để DRY này là triển khai ModelMetadataProvider của riêng bạn.ModelMetadataProvider là những gì đang đọc các thuộc tính đó và trình bày chúng cho các trình trợ giúp mẫu. MVC2 đã cung cấp triển khai DataAnnotationsModelMetadataProvider để làm cho mọi thứ trở nên kế thừa từ đó khiến mọi thứ trở nên dễ dàng.

Để giúp bạn bắt đầu, đây là một ví dụ đơn giản mà phá vỡ tên thuộc tính ngoài camelcased vào không gian, FirstName => Tên:

public class ConventionModelMetadataProvider : DataAnnotationsModelMetadataProvider 
{ 
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) 
    { 
     var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); 

     HumanizePropertyNamesAsDisplayName(metadata); 

     if (metadata.DisplayName.ToUpper() == "ID") 
      metadata.DisplayName = "Id Number"; 

     return metadata; 
    } 

    private void HumanizePropertyNamesAsDisplayName(ModelMetadata metadata) 
    { 
     metadata.DisplayName = HumanizeCamel((metadata.DisplayName ?? metadata.PropertyName)); 
    } 

    public static string HumanizeCamel(string camelCasedString) 
    { 
     if (camelCasedString == null) 
      return ""; 

     StringBuilder sb = new StringBuilder(); 

     char last = char.MinValue; 
     foreach (char c in camelCasedString) 
     { 
      if (char.IsLower(last) && char.IsUpper(c)) 
      { 
       sb.Append(' '); 
      } 
      sb.Append(c); 
      last = c; 
     } 
     return sb.ToString(); 
    } 
} 

Sau đó, tất cả các bạn phải làm là đăng ký nó như thêm tùy chỉnh của riêng bạn ViewEngine hoặc ControllerFactory bên trong ứng dụng Bắt đầu Global.asax của:

ModelMetadataProviders.Current = new ConventionModelMetadataProvider(); 

Bây giờ chỉ cần để cho bạn thấy tôi không gian lận này là mô hình xem tôi đang sử dụng để có được những HtmlHelper cùng * Đối với kinh nghiệm như ViewModel trang trí của bạn.. :

public class FooDisplayViewModel // use for "details" view 
    { 
     public int Id { get; set; } 

     public string FirstName { get; set; } 

     public string LastName { get; set; } 

     [DataType("EmailAddress")] 
     public string EmailAddress { get; set; } 

     public int Age { get; set; } 

     [DisplayName("Category")] 
     public string CategoryName { get; set; } 
    } 
+0

Cảm ơn, jfar và +1. Có, chính xác, tôi đang cố gắng sử dụng DisplayFor() và EditorFor() (mặc dù, ngay cả trong trường hợp tôi không thể, tôi vẫn muốn DRY lên ViewModels của tôi). Ý tưởng của bạn sẽ loại bỏ rất nhiều nhu cầu cho các thuộc tính, đó là một trợ giúp tuyệt vời. Tôi tự hỏi, mặc dù, nếu tôi cũng có thể thêm một tài sản tùy chỉnh (và một thuộc tính tùy chỉnh song song) mà sẽ cho biết liệu để giàn giáo một tài sản cụ thể cho một ViewModel cụ thể. Điều này sẽ cho phép tôi có một ViewModel xử lý tất cả các khung nhìn, có nghĩa là tôi sẽ không bao giờ hoặc gần như không bao giờ cần phải lặp lại các thuộc tính. – devuxer

+0

Giới hạn duy nhất là các thuộc tính mặc định của ModelMetadata. Nếu bạn cần thêm thông tin và tạo MyModelMetadata: ModelMetatdata, bạn cũng sẽ tạo ViewPage tùy chỉnh của riêng mình với thuộc tính MyModelMetadata tùy chỉnh HOẶC truyền ViewData.ModelMetadata bên trong bất kỳ tệp .aspx hoặc .ascx nào bạn đang sử dụng. – jfar

7

Declare BaseModel, kế thừa và thêm các thuộc tính khác:

public class BaseFooViewModel 
{ 
    [DisplayName("First Name")] 
    public string FirstName { get; set; } 

    [DisplayName("Last Name")] 
    public string LastName { get; set; } 

    [DisplayName("Email Address")] 
    [DataType("EmailAddress")] 
    public string EmailAddress { get; set; } 
} 

public class FooDisplayViewModel : BaseFooViewModel 
{ 
    [DisplayName("ID Number")] 
    public int Id { get; set; } 
} 

public class FooEditViewModel : BaseFooViewModel 

EDIT

Về loại. Không nên chỉnh sửa mô hình xem có public string CategoryName { get; set; }public List<string> Categories { get; set; } thay vì SelectList không? Bằng cách này bạn có thể đặt public string CategoryName { get; set; } trong lớp cơ sở và giữ DRY. Chế độ xem chỉnh sửa sẽ tăng cường lớp học bằng cách thêm List<string>.

+0

Tôi đã chơi xung quanh bằng cách sử dụng kế thừa trước đó. Nó chắc chắn giúp bạn gần gũi hơn, nhưng nó thiếu tính linh hoạt. Một vấn đề là một số thuộc tính vẫn cần được lặp lại, như thuộc tính 'DisplayName' cho' Danh mục' trong ví dụ của tôi. Rõ ràng, đây không phải là một vấn đề lớn trên quy mô nhỏ, nhưng trên quy mô lớn, bạn có thể kết thúc với nhiều mục gấp đôi hoặc ba của cùng một 'DisplayName' cho các ViewModels khác nhau (và đó chỉ là một trong vài thuộc tính bạn có thể muốn đặt cho một thuộc tính nhất định). – devuxer

+0

@DanM: Tôi đã thêm nhận xét về Danh mục. – LukLed

+0

@LukLed, tôi không chắc tôi sẽ theo dõi bạn. Tôi đang sử dụng 'SelectList' vì, khi khung nhìn được dàn dựng, một' DropDownList' sẽ được tạo với danh sách đúng và mục được chọn ban đầu. Tôi thậm chí có thể treo lên 'DataValueField' và' DataTextField' cho các trường hợp danh sách thực sự là một bảng cơ sở dữ liệu. Nếu tôi chỉ sử dụng một 'Danh sách ', tôi không tin rằng quan điểm của tôi sẽ được dàn dựng chính xác. – devuxer

1

Vì LukLed cho biết bạn có thể tạo lớp cơ sở mà các mô hình Xem và chỉnh sửa bắt nguồn từ hoặc bạn cũng có thể chỉ lấy được một mô hình xem từ mô hình khác. Trong nhiều ứng dụng, mô hình Chỉnh sửa về cơ bản giống với Xem cộng với một số nội dung bổ sung (như các danh sách chọn), do đó, có thể có ý nghĩa khi lấy mô hình Chỉnh sửa từ mô hình Xem.

Hoặc, nếu bạn lo lắng về "vụ nổ lớp", bạn có thể sử dụng cùng một kiểu xem cho cả hai và chuyển các nội dung bổ sung (như SelectLists) thông qua Chế độ xem. Tôi không khuyên bạn nên tiếp cận này bởi vì tôi nghĩ rằng nó gây nhầm lẫn để vượt qua một số tiểu bang thông qua các mô hình và tiểu bang khác thông qua ViewData, nhưng nó là một lựa chọn.

Một tùy chọn khác là chỉ cần nắm lấy các mô hình riêng biệt. Tôi là tất cả về giữ DRY logic, nhưng tôi ít lo lắng về một vài tài sản dư thừa trong DTO của tôi (đặc biệt là trên các dự án sử dụng thế hệ mã để tạo ra 90% các mô hình xem cho tôi).

1

Điều đầu tiên tôi chú ý - bạn có 2 kiểu xem. Xem câu trả lời của tôi here để biết chi tiết về điều này.

Những điều khác mà lưu ý trong tâm trí đã được đề cập (cách tiếp cận cổ điển để áp dụng DRY - thừa kế và quy ước).


Tôi đoán tôi đã quá mơ hồ. Ý tưởng của tôi là tạo mô hình xem cho mỗi mô hình miền và sau đó - kết hợp chúng ở các mô hình xem theo từng chế độ xem cụ thể. Trong trường hợp của bạn: =>

public class FooViewModel { 
    strange attributes everywhere tralalala 
    firstname,lastname,bar,fizz,buzz 
} 

public class FooDetailsViewModel { 
    public FooViewModel Foo {get;set;} 
    some additional bull**** if needed 
} 

public class FooEditViewModel { 
    public FooViewModel Foo {get;set;} 
    some additional bull**** if needed 
} 

Điều đó cho phép chúng ta tạo ra mô hình điểm phức tạp hơn (nghĩa là mỗi lần xem) quá =>

public class ComplexViewModel { 
    public PaginationInfo Pagination {get;set;} 
    public FooViewModel Foo {get;set;} 
    public BarViewModel Bar {get;set;} 
    public HttpContext lol {get;set;} 
} 

Bạn có thể thấy hữu ích this question của tôi.

hmm ... hóa ra tôi thực sự đã đề xuất tạo 3 kiểu xem. Dù sao, đoạn mã đó phản ánh cách tiếp cận của tôi.

Một mẹo khác - tôi sẽ sử dụng cơ chế lọc theo quy tắc & (ví dụ theo loại), chế độ xem dữ liệu với selectList cần thiết (khung mvc có thể liên kết tự động selectList từ viewData theo tên hoặc gì đó).

Và một mẹo khác - nếu bạn sử dụng AutoMapper để quản lý mô hình chế độ xem của mình, nó có tính năng đẹp - nó có thể flatten object graph.Do đó - bạn có thể tạo mô hình xem (cho mỗi lượt xem) trực tiếp có các đạo cụ của mô hình xem (cho mỗi mô hình miền) bất kể bạn muốn đi sâu bao nhiêu (Haack said).

+0

Cảm ơn, Arnis. Có vẻ như bạn đang nói rằng tôi nên có mô hình xem * thứ ba * (ví dụ: 'FooEditPostViewModel'). Tôi đã thực sự nghĩ về việc đó vào những thời điểm khác nhau, nhưng nó chỉ làm trầm trọng thêm những vấn đề tôi đang gặp phải với các thuộc tính. Tuy nhiên, tôi bị hấp dẫn bởi Fluent MetadataProvider. Sẽ nhìn vào đó. – devuxer

+0

@DanM đã cập nhật một chút câu trả lời của tôi. –

+0

Arnis, cảm ơn các chi tiết bổ sung. Một điều tôi muốn làm là tự động giàn giáo (sử dụng 'DisplayFor()' và 'EditorFor()'). Cho dù tôi sử dụng bố cục hoặc thừa kế, mọi thứ kết thúc theo thứ tự sai hoặc hiển thị dưới dạng thụt lề khi họ không nên. – devuxer

0

Các tên hiển thị này (giá trị) có thể được hiển thị trong một lớp tĩnh khác với nhiều trường const. Sẽ không giúp bạn tiết kiệm nhiều trường hợp DisplayNameAttribute nhưng nó sẽ làm cho một sự thay đổi tên nhanh chóng và dễ dàng để thực hiện. Rõ ràng điều này không hữu ích cho các thuộc tính meta khác.

Nếu tôi nói với nhóm của mình, họ sẽ phải tạo một mô hình mới cho mỗi hoán vị nhỏ của cùng một dữ liệu (và sau đó viết các định nghĩa tự động cho chúng) họ sẽ nổi loạn và lynch tôi. Tôi muốn mô hình siêu dữ liệu được, quá mức sử dụng một số nhận thức. Ví dụ việc tạo thuộc tính bắt buộc chỉ có hiệu lực trong kịch bản "Add" (Model == null). Đặc biệt là tôi thậm chí sẽ không viết hai khung nhìn để xử lý thêm/chỉnh sửa. Tôi sẽ có một cái nhìn để xử lý cả hai người trong số họ và nếu tôi bắt đầu có các lớp mô hình khác nhau tôi muốn gặp rắc rối với khai báo lớp cha mẹ của tôi .. các ... ViewPage bit.

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