asp.net – MVC通用ViewModel

栏目: ASP.NET · 发布时间: 6年前

内容简介:翻译自: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到泛型.

我已成功地使用协变工作,即将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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python for Everyone

Python for Everyone

Cay S. Horstmann、Rance D. Necaise / John Wiley & Sons / 2013-4-26 / GBP 181.99

Cay Horstmann's" Python for Everyone "provides readers with step-by-step guidance, a feature that is immensely helpful for building confidence and providing an outline for the task at hand. "Problem S......一起来看看 《Python for Everyone》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试