2011-01-17 40 views
8

Nói tóm lại tôi muốn để có thể vượt qua một ViewModel chung vào quan điểm của tôiMVC generic ViewModel

Dưới đây là một số mã đơn giản của các ý chính của những gì tôi đang cố gắng để đạt

public interface IPerson 
{ 
    string FirstName {get;} 
    string LastName {get;} 
} 

public class FakePerson : IPerson 
{ 
    public FakePerson() 
    { 
     FirstName = "Foo"; 
     LastName = "Bar"; 
    } 

    public string FirstName {get; private set;} 
    public string LastName {get; private set;} 
} 

public class HomeViewModel<T> 
    where T : IPerson, new() 
{ 
    public string SomeOtherProperty{ get; set;} 
    public T Person { get; private set; } 

    public HomeViewModel() 
    { 
     Person = new T(); 
    } 
} 

public class HomeController : Controller { 
    public ViewResult Index() { 
     return View(new HomeViewModel<FakePerson>()); 
    } 
} 

Nếu tôi tạo chế độ xem của tôi như sau tất cả các công trình như mong đợi

<%@ Page Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage<HomeViewModel<FakePerson>>" %> 
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 
    <%: Html.DisplayFor(m=>m.Person.FirstName) %> 
    <%: Html.DisplayFor(m=>m.Person.LastName) %> 
</asp:Content> 

Tuy nhiên tôi không muốn phụ thuộc trực tiếp vào FakePerson trong trường hợp tôi muốn chuyển một số triển khai IPerson khác, vì vậy tôi đã cố gắng thay đổi chỉ thị trang thành

<%@ Page Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage<HomeViewModel<IPerson>>" %> 

Nhưng tất nhiên điều đó không hiệu quả, vì vậy, sau cả ngày mút, tôi có mái tóc màu xám hơn và không biết phải làm gì tiếp theo.

Ai đó có thể giúp bạn.

[CẬP NHẬT]

Một số tư vấn đã sugested rằng tôi nên sử dụng giao diện covariant; xác định một giao diện không chung chung và sử dụng nó trong khung nhìn. Không may là tôi đã thử điều này nhưng có một hàm ý về mặt quảng cáo. Tôi muốn các chức năng HtmlHelper để có thể truy cập vào bất kỳ thuộc tính dữ liệu chú thích có thể được định nghĩa trong lớp IPerson có nguồn gốc

public class FakePerson : IPerson 
{ 
    public FakePerson() 
    { 
     FirstName = "Foo"; 
     LastName = "Bar"; 
    } 

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

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

Vì vậy, khi sử dụng một giao diện hiệp biến cách này không làm việc, một phần, để truy cập vào loại có nguồn gốc qua ViewModel; vì khung nhìn được gõ vào giao diện, nó xuất hiện các thuộc tính không thể truy cập được.

Có cách nào đó, theo quan điểm, để có quyền truy cập vào các thuộc tính này, có thể có sự phản ánh. Hoặc có thể có một cách khác để nhập Chế độ xem vào chung chung.

+0

Làm thế nào bạn có thể mong đợi xem của bạn để truy cập nguồn gốc dữ liệu, nhưng bạn không muốn làm cho nó phụ thuộc vào dữ liệu có nguồn gốc? Đặt một cách khác, nếu bạn truy cập dữ liệu duy nhất cho FakePerson, thì chế độ xem của bạn phụ thuộc vào FakePerson và như vậy bạn nên làm cho mô hình của mình là FakePerson. Nếu bạn vượt qua trong một số đối tượng khác, sau đó nó sẽ có lỗi khi truy cập FakePerson. –

+0

Bạn không thể sử dụng chú thích dữ liệu với giao diện, vì thuộc tính không được kế thừa (xem http://stackoverflow.com/questions/540749/can-ac-sharp-class-inherit-attributes-from-its-interface) –

Trả lời

0

Mã của bạn gần như chính xác; bạn chỉ cần vượt qua HomeViewModel<IPerson> (không phải FakePerson) trong bộ điều khiển.

Bạn cũng có thể sử dụng giao diện covariant cho Mô hình trong chế độ xem, nhưng đó có thể là quá mức cần thiết.

+0

Cảm ơn đã phản ứng nhanh chóng. Ý của bạn là mã số – ricardo

+0

public class HomeController: Controller { public ViewResult Index() { return View (new HomeViewModel ()); } } – ricardo

+0

Chế độ xem công khaiResult Index() {return View (new HomeViewModel ()) ;. Điều này cho một lỗi thời gian chạy vì IPerson không phải là một loại cụ thể và điểm là để có thể vượt qua một lớp dẫn xuất của IPerson để xem – ricardo

0

Làm cho lớp ViewModel của bạn triển khai giao diện không chung chung (hoặc chung chung biến đổi), sau đó sửa đổi giao diện để lấy giao diện đó thay vì lớp bê tông.

+0

Cảm ơn bạn. Tôi đã xem xét điều này và chỉ thử nó một lần nữa cho biện pháp tốt, nhưng approuch này không cung cấp cho tôi tất cả các tính năng tôi đã hy vọng.Vui lòng xem cập nhật về câu hỏi của tôi. – ricardo

2

Tôi đã nhận thành công biến đổi để hoạt động, tức là ràng buộc Chế độ xem với lớp cơ sở trừu tượng. Trong thực tế, những gì tôi có là một ràng buộc với một Danh sách <MyBaseClass>. Sau đó, tôi tạo một View cụ thể được đánh mạnh mẽ cho từng lớp con. Điều đó đảm nhận sự ràng buộc.

Nhưng việc khôi phục sẽ không thành công vì DefaultModelBinder chỉ biết về lớp cơ sở trừu tượng và bạn sẽ nhận được một ngoại lệ như 'Không thể tạo lớp trừu tượng'. Giải pháp là có thuộc tính trên lớp cơ sở của bạn như sau:

public virtual string BindingType 
    { 
     get 
     { 
      return this.GetType().AssemblyQualifiedName; 
     } 
    } 

Liên kết với đầu vào bị ẩn trong chế độ xem của bạn. Sau đó, bạn thay thế ModelBinder mặc định của bạn bằng một tùy chỉnh trong Global.asax:

// Replace default model binder with one that can deal with BaseParameter, etc. 
    ModelBinders.Binders.DefaultBinder = new CustomModelBinder(); 

Và trong mô hình tùy chỉnh của bạn, bạn chặn đánh chặn. Nếu nó là dành cho một trong các loại trừu tượng được biết đến, bạn phân tích tài sản BindingType và thay thế các loại mô hình để bạn có được một thể hiện của lớp con:

public class CustomModelBinder : DefaultModelBinder 
{ 
    private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType) 
    { 
     if (modelType.IsInterface || modelType.IsAbstract) 
     { 
      // This is our convention for specifying the actual type of a base type or interface. 
      string key = string.Format("{0}.{1}", bindingContext.ModelName, Constants.UIKeys.BindingTypeProperty);    
      var boundValue = bindingContext.ValueProvider.GetValue(key); 

      if (boundValue != null && boundValue.RawValue != null) 
      { 
       string newTypeName = ((string[])boundValue.RawValue)[0].ToString(); 
       logger.DebugFormat("Found type override {0} for Abstract/Interface type {1}.", modelType.Name, newTypeName); 

       try 
       { 
        modelType = System.Type.GetType(newTypeName); 
       } 
       catch (Exception ex) 
       { 
        logger.ErrorFormat("Error trying to create new binding type {0} to replace original type {1}. Error: {2}", newTypeName, modelType.Name, ex.ToString()); 
        throw; 
       } 
      } 
     } 

     return base.CreateModel(controllerContext, bindingContext, modelType); 
    } 

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) 
    { 
     if (propertyDescriptor.ComponentType == typeof(BaseParameter)) 
     { 
      string match = ".StringValue"; 
      if (bindingContext.ModelName.EndsWith(match)) 
      { 
       logger.DebugFormat("Try override for BaseParameter StringValue - looking for real type's Value instead."); 
       string pattern = match.Replace(".", @"\."); 
       string key = Regex.Replace(bindingContext.ModelName, pattern, ".Value"); 
       var boundValue = bindingContext.ValueProvider.GetValue(key); 
       if (boundValue != null && boundValue.RawValue != null) 
       { 
       // Do some work here to replace the base value with a subclass value... 
        return value; 
       } 
      } 
     } 

     return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); 
    } 
} 

Ở đây, lớp trừu tượng của tôi là BaseParameter và tôi thay thế stringValue thuộc tính có giá trị khác với lớp con (không được hiển thị).

Lưu ý rằng mặc dù bạn có thể khôi phục lại đúng loại, các giá trị biểu mẫu liên kết chỉ với lớp con sẽ không được tự động roundtripped vì modelbinder chỉ thấy các thuộc tính trên lớp cơ sở. Trong trường hợp của tôi, tôi chỉ phải thay thế một giá trị trong GetValue và nhận nó thay vì từ lớp con, vì vậy nó rất dễ dàng. Nếu bạn cần phải liên kết nhiều thuộc tính lớp con, bạn sẽ cần phải thực hiện thêm một chút công việc và lấy chúng ra khỏi biểu mẫu (ValueProvider [0]) và điền vào bản thân cá thể đó.

Lưu ý rằng bạn có thể thêm một trình kết nối mô hình mới cho một loại cụ thể để bạn có thể tránh việc kiểm tra loại chung.

0

Tôi đã tạo ra một giao diện

public interface ITestModel 
    { 
     string FirstName { get; set; } 
     string LastName { get; set; } 
     string Address { get; set; } 
     int Age { get; set; } 
    } 

Tạo lớp và được thừa kế từ giao diện này

class TestModel : ITestModel 
    { 
     public string FirstName { get; set; } 
     public string LastName { get; set; } 
     public string Address { get; set; } 
     public int Age { get; set; } 

    } 

tạo thể hiện của lớp trong điều khiển

public ActionResult TestMethod() 
     { 
      ITestModel testModel; 
      testModel = new TestModel(); 
      testModel.FirstName = "joginder"; 
      testModel.Address = "Lovely Home"; 
      testModel.LastName = "singh"; 
      testModel.Age = 36; 
      return View("testMethod", testModel); 
     } 

Xem tạo như cách này

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TestProject.ITestModel>" %> 

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> 
    TestMethod 
</asp:Content> 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 

    <h2>TestMethod</h2> 

    <fieldset> 
     <legend>Fields</legend> 

     <div class="display-label">FirstName</div> 
     <div class="display-field"><%: Model.FirstName %></div> 

     <div class="display-label">LastName</div> 
     <div class="display-field"><%: Model.LastName %></div> 

     <div class="display-label">Address</div> 
     <div class="display-field"><%: Model.Address %></div> 

     <div class="display-label">Age</div> 
     <div class="display-field"><%: Model.Age %></div> 

    </fieldset> 
    <p> 
     <%: Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> | 
     <%: Html.ActionLink("Back to List", "Index") %> 
    </p> 

</asp:Content> 

Bây giờ tôi có thể vượt qua bất kỳ đối tượng của bất kỳ giao diện tương tự

Tôi hy vọng nó sẽ giúp :)

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