内容简介:翻译自:https://stackoverflow.com/questions/4716491/mvc-generic-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到泛型.
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
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。