asp.net – MVC通用ViewModel

簡而言之,我希望能夠將通用的ViewModel傳遞給我的檢視

這是我想要實現的要點的一些簡化程式碼

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>());
    }
}

如果我按如下方式建立我的檢視,則按預期工作

<%@ 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>

但是,如果我想傳遞其他一些IPerson實現,我不想在檢視中直接依賴FakePerson,所以我嘗試將頁面指令更改為

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

但當然這不起作用,所以,經過一整天的磨礪,我有更多的白髮,不知道接下來該做什麼.

請有人幫忙嗎?

[UPDATE]

有人建議我應該使用協變介面;定義非泛型介面並在View中使用它.不幸的是,我嘗試過這一點,但有一個附帶的含義.我希望HtmlHelper函式能夠訪問可能在IPerson派生類中定義的任何資料註釋屬性

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

因此,在使用協變介面時,這種方式可以部分地通過ViewModel訪問派生型別.由於檢視是鍵入到介面,因此看起來屬性不可訪問.

在檢視中,是否有一種方法可以通過反射訪問這些屬性.

或者可以在其他方面輸入View到泛型.

我已成功地使用協變工作,即將View繫結到抽象基類.事實上,我所擁有的是對List<

MyBaseClass>的繫結.然後我建立一個特定的View強型別到每個子類.這照顧了繫結.

但是重新繫結會失敗,因為DefaultModelBinder只知道抽象基類,你會得到一個例外,“無法建立抽象類”.解決方案是在您的基類上有一個屬性,如下所示:

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

將其繫結到檢視中的隱藏輸入.然後用Global.asax中的自定義ModelBinder替換預設的ModelBinder:

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

在您的自定義模型繫結器中,您可以攔截繫結.如果它是針對您已知的抽象型別之一,則解析BindingType屬性並替換模型型別,以便獲取子類的例項:

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);
    }
}

在這裡,我的抽象類是BaseParameter,我將StringValue屬性替換為與子類不同的值(未顯示).

請注意,雖然您可以重新繫結到正確的型別,但是僅與子類關聯的表單值不會自動往返,因為模型繫結器只能看到基類的屬性.在我的例子中,我只需要替換GetValue中的一個值,而不是從子類中獲取它,所以很容易.如果你需要繫結很多子類屬性,你需要做更多的工作並從表單中取出它們(ValueProvider [0])並自己填充例項.

請注意,您可以為特定型別新增新的模型繫結器,以避免泛型型別檢查.

翻譯自:https://stackoverflow.com/questions/4716491/mvc-generic-viewmodel