Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 211
mflair2000 Avatar asked Apr 09 '15 15:04

mflair2000


People also ask

How do you render a partial view inside a view in MVC?

Partial function which renders the Partial View. The name of the View and the object of the CustomerModel class are passed to the @Html. Partial function. In order to add Partial View, you will need to Right Click inside the Controller class and click on the Add View option in order to create a View for the Controller.

How do I render partial view?

Rendering a Partial View You can render the partial view in the parent view using the HTML helper methods: @html. Partial() , @html. RenderPartial() , and @html. RenderAction() .

What is partial view MVC?

A partial view is a Razor markup file ( . cshtml ) without an @page directive that renders HTML output within another markup file's rendered output. The term partial view is used when developing either an MVC app, where markup files are called views, or a Razor Pages app, where markup files are called pages.

What is difference between partial view and view?

Views are the general result of a page that results in a display. It's the highest level container except the masterpage. While a partial view is for a small piece of content that may be reused on different pages, or multiple times in a page.


2 Answers

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)
}
like image 87
Craig Curtis Avatar answered Sep 21 '22 22:09

Craig Curtis


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)

like image 39
haim770 Avatar answered Sep 24 '22 22:09

haim770