MVC通用视图模型 [英] MVC generic ViewModel

查看:100
本文介绍了MVC通用视图模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

总之我希望能够到一个通用的视图模型传递到我的意见

In short I would like to be able to pass a generic ViewModel into my views

下面是什么,我想实现的要点的一些简化code

Here is some simplified code of the gist of what I am trying to achieve

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

如果我创造我的看法如下所有的作品如预期

If I create my view as follows all works as expected

<%@ 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直接依赖,所以我试图改变页面指令

However I do not want to depend directly on FakePerson in the view in the event that I want to pass some other IPerson implementation, so I tried to change the page directive to

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

但是,当然,这并不工作,所以,混日子的一整天后,我有更多的头发花白,不知道下一步怎么办。

But of course that does not work, so, after a whole day of mucking about, I have more grey hair and no clue what to do next.

任何人可以帮助请。

[更新]

一些建议已经sugested,我应该使用一个协变界面;定义了一个非通用接口,并在视图中使用。 Unfortuanetly我已经尝试了这一点,但有一个aditional的含义。我想的HtmlHelper功能,以便能够访问那些在IPerson派生类中定义的任何数据注解的属性

Some advise has sugested that I should use a covariant interface; define a non-generic interface and use it in the View. Unfortuanetly I have tried this but there is one aditional implication. I would like the HtmlHelper function to be able to access any data annotation attributes that may be defined in the IPerson derived class

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

所以在使用协接口这样做的工作,部分原因是通过视图模型访问派生类型;由于图像被输入到接口,它出现在属性不可访问。

So while using a covariant interface this way does work, partly, to access the derived type through the ViewModel; since the view is typed to the interface, it appears the attributes are not accessible.

有可能是一个办法,在视图中,以访问这些属性,也许与反思。
或者,莫不是anotherway键入观对通用的。

Is there perhaps a way, in the view, to get access to these attributes, maybe with reflection. Or could there be anotherway to type the View to the generic.

推荐答案

我已经顺利拿到协的工作,那就是查看绑定到一个抽象基类。其实,我有什么是绑定到一个List&LT; MyBaseClass取代。然后,我创建一个特定视图强类型的每个子类。这需要绑定的照顾。

I have successfully got covariant to work, that is, binding the View to an abstract base class. In fact, what I have is a binding to a List<MyBaseClass>. Then I create a specific View strongly typed to each subclass. That takes care of the binding.

但重新绑定将失败,因为DefaultModelBinder只知道抽象基类,你会得到一样,无法创建抽象类的异常。解决的办法是有一个像这样对你的基类的属性:

But rebinding will fail because the DefaultModelBinder only knows about abstract base class and you will get an exception like, 'Cannot create abstract class'. The solution is to have a property on your base class like this:

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

绑定,要在你看来一个隐藏的输入。然后您用自定义更换默认的模型绑定器在Global.asax中:

Bind that to a hidden input in your view. Then your replace your default ModelBinder with a custom one in Global.asax:

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

和您的自定义模型绑定你拦截绑定。如果这是你的已知的抽象类型之一,您解析bindingType属性,所以你得到子类的实例替换模型类型:

And in your custom model binder you intercept the bind. If it is for one of your known abstract types, you parse the BindingType property and replace the model type so you get an instance of the subclass:

    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属性

Here, my abstract class is BaseParameter and I am replacing the StringValue property with a different value from the subclass (not shown).

请注意,虽然可以重新绑定到正确的类型,只与子类相关的表单值将不会自动roundtripped因为ModelBinder的只看到在基类中的属性。就我而言,我只曾在的GetValue更换一次值和子类得到它,而不是,所以很容易。如果您需要绑定大量的子类的属性,你需要做更多的工作,并取出来的形式(ValueProvider [0])和自己填充实例。

Note that although you can rebind to the correct type, the form values associated only with the subclass will not be automatically roundtripped because the modelbinder only see the properties on the base class. In my case, I only had to replace one value in GetValue and get it instead from the subclass, so it was easy. If you need to bind lots of subclass properties you will need to do a bit more work and fetch them out the form (ValueProvider[0]) and populate the instance yourself.

请注意,您可以添加一个新的模型绑定为特定类型的,所以你能避免泛型类型检查。

Note that you can add a new model binder for a specific type so you could avoid the generic type checking.

这篇关于MVC通用视图模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆