简体   繁体   中英

Polymorphic ViewModel collection and rendering in MVC partial Views

I'm having a problem with a polymorphic collection of ViewModels in my MVC application. I received this via a web service call and i need to iterate through them and give them their own partial view, based on the object type.

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

public sealed class OntarioViewModel : ProvinceViewModel { }

public sealed class QuebecViewModel : ProvinceViewModel {}

In my view i am trying to iterate through them and assign a partial view. I have to do a lot of type casting here to make it work. If I try and move this to a controller action and pass in the abstract type, i will get an error that we cannot create an instance of abstract class.

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

I have strongly type View, so that i can access the different properties.

How would i go about solving this in a more elegant way? Would I need to create a new surrogate base class for the abstract class to create a instance of it easier?

You can achieve this with display templates. Create a display template for each type in the DisplayTemplates folder within your Controller's Views directory:

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

Display each model using the DisplayFor helper in your view:

@model ICollection<ProvinceViewModel>

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

Upon encountering the same problem in the past, I have created the following solution:

First, decorate your (concrete) view-model with ExportMetadata attribute that denotes the view name to be used. For example:

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

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

Then extend your HtmlHelper with the following Partial method:

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

Then use it:

@Html.Partial(currentProvince);

And in case your partials reside in some sub-directory:

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

(If you need help registering the custom HTML helper see https://stackoverflow.com/a/5052790 )

I had a similar requirement and this is how I managed to solve this issue. My viewmodel (BusinessEventEmailViewModel ) has a list of interfaces (IBusinessEventEmail) resolved at runtime with unity. A IBusinessEventEmail has an EventCode property.

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

In my view, I render the partial view using a naming convention :

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

Then, I have a XXXEventEmail implementing IBusinessEventEmail with the EventCode XXX and a partial view _XXX.cshtml

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM