MVC局部视图中的多态ViewModel集合和渲染

时间:2015-04-09 15:31:55

标签: c# asp.net-mvc polymorphism abstract-class

我在MVC应用程序中遇到ViewModel的多态集合问题。我通过Web服务调用收到了这个,我需要迭代它们并根据对象类型给它们自己的局部视图。

public abstract class ProvinceViewModel
{
    public string Code { get; set; }
}

public sealed class OntarioViewModel : ProvinceViewModel { }

public sealed class QuebecViewModel : ProvinceViewModel {}

在我看来,我试图迭代它们并分配局部视图。我必须在这里进行大量的类型转换才能使其正常工作。如果我尝试将其移动到控制器操作并传入抽象类型,我将得到一个错误,我们无法创建抽象类的实例。

ICollection<ProvinceViewModel>  ProvinceList; // collection receive via service

@for (int i = 0, c = ProvinceList.Count; i < c; i++)
{
    var currentProvince = this.Model.ElementAt(i);
    @switch (additionalRegistry.Code)
    {
        case "QC":
            @Html.Partial("AlbertaDetail", (QuebecViewModel)currentProvince)
            break;                              
        case "ON":
            @Html.Partial("OntarioDetail", (OntarioViewModel)currentProvince)
            break;
        default:
            @Html.Partial("ProvinceDetail", ProvinceViewModel)
            break;
    }
}

我强烈键入View,以便我可以访问不同的属性。

我将如何以更优雅的方式解决这个问题?我是否需要为抽象类创建一个新的代理基类来更容易地创建它的实例?

3 个答案:

答案 0 :(得分:2)

您可以使用显示模板实现此目的。在Controller的Views目录中的DisplayTemplates文件夹中为每种类型创建一个显示模板:

+-- Views
    +-- Provinces
        +-- DisplayTemplates
            +-- OntarioViewModel.cshtml
            +-- QuebecViewModel.cshtml

在视图中使用DisplayFor帮助器显示每个模型:

@model ICollection<ProvinceViewModel>

@foreach (var province in Model)
{
    @Html.DisplayFor(_ => province)
}

答案 1 :(得分:1)

在过去遇到同样的问题时,我创建了以下解决方案:

首先,使用ExportMetadata属性装饰您的(具体)视图模型,该属性表示要使用的视图名称。例如:

[ExportMetadata("View", "Ontario")]
public sealed class OntarioViewModel : ProvinceViewModel { }

[ExportMetadata("View", "Quebec")]
public sealed class QuebecViewModel : ProvinceViewModel {}

然后使用以下HtmlHelper方法扩展您的Partial

public static MvcHtmlString Partial<T>(this HtmlHelper htmlHelper, T model, string prefix = null)
{
    var modelType = typeof (T);
    var partialAttr = modelType.GetCustomAttributes<ExportMetadataAttribute>().SingleOrDefault(x => x.Name == "View");

    if (partialAttr == null)
        throw new Exception(modelType.Name + " doesn't define any view to be used");

    var partialName = (prefix ?? String.Empty) + partialAttr.Value;

    return htmlHelper.Partial(partialName, model, htmlHelper.ViewData);
}

然后使用它:

@Html.Partial(currentProvince);

如果你的部分位于某个子目录中:

@Html.Partial(currentProvince, "Partials/")

(如果您需要帮助注册自定义HTML帮助器,请参阅https://stackoverflow.com/a/5052790

答案 2 :(得分:0)

我有类似的要求,这就是我设法解决这个问题的方法。 我的viewmodel(BusinessEventEmailViewModel)有一个在运行时使用unity解析的接口列表(IBusinessEventEmail)。 IBusinessEventEmail具有EventCode属性。

public class BusinessEventEmailViewModel : MailHeaderViewModel
{
    #region members
    public List<IBusinessEventEmail> Events { get; set; }

在我看来,我使用命名约定渲染局部视图:

Html.RenderPartial("~/Views/Shared/Email/_" + businessEvent.EventCode + ".cshtml", businessEvent);

然后,我有一个XXXEventEmail实现IBusinessEventEmail与EventCode XXX和部分视图_XXX.cshtml