Using MVC - Inheritance

JanL@kentico.com[email protected] Czech RepublicMember, Administrator, Kentico Staff admin

This is a series of articles about using MVC, mainly in relation to Kentico Cloud. Let's highlight some of the MVC artifacts and patterns that are worth using!

Contents:

Implementing Inheritance

How would you go around displaying data of various inheriting types without duplicating the markup code? This article describes how we've dealt with the problem.

The Problem

Suppose you have a base type:

public abstract class Product
{
    public string ProductName { get; set; }
}

… and another type that inherits from it:

public class Coffee : Product
{
    // A place where the coffee was grown
    public string Farm { get; set; }
}

You certainly want to leverage the benefits of strongly typed views and the DRY principle. The markup for properties of the base type should be in one single file.

But the problem is that once the @model directive is used in Razor, the MVC runtime expects data of that specific type to always be passed onto the view (not of the inheriting types).

Hence, it is not possible to have a view (or template) like that:

@model Product

<div>@Html.DisplayFor(vm => vm.ProductName)</div>

@if (Model is Coffee)
{
    <div>@Html.DisplayFor(vm => vm.Farm)</div>
}

Could Display Templates Be Used?

Display templates should not be used to hold the markup of the base type and inheriting types. It is because of two reasons.

First, they should always be strongly typed. Given that the @model directive in Razor always expects data of a specific type, the strongly typed templates wouldn't be usable.

Second, templates were designed to be used automatically by MVC runtime to render data of its corresponding CLR type. As such, template files should always represent a standalone unit of markup. But we need the exact opposite - a file that represents either the properties of the abstract base type or just the properties of specific inheriting types. None of these two are standalone by any means.

We've found two ways to get around this. A basic solution and another one that makes use of MVC layouts.

Basic Implementation

It builds upon using the @model dynamic directive in the view. With that, you can pass various types to the view. You can also refer to members of those types that are down in the inheritance chain (covariant types).

@model dynamic

@{
    bool isCoffee = Model is Coffee;
}

<div>@Html.DisplayFor(vm => vm.ProductName)</div>

@if (isCoffee)
{
    <div>@Html.DisplayFor(vm => vm.Farm)</div>
}

In addition to that, you can move the type-specific markup to standalone files using partial views (not templates):

@model dynamic

@{
    bool isCoffee = Model is Coffee;
}

<div>@Html.DisplayFor(vm => vm.ProductName)</div>

@if (isCoffee)
{
    Html.RenderPartial("CoffeePartial");
}

These partial views can be strongly typed:

@model Coffee

<div>@Html.DisplayFor(vm => vm.Farm)</div>

That's it. But, while this may suffice in ad-hoc cases, there is a better solution with layouts.

Implementation Based on Layouts

It works in an opposite direction. You define views for specific covariant types. The views, in turn, refer to MVC layout files. Layouts contain markup of the common properties of contravariant types. In the controller, you invoke views, not layouts.

The above code examples can be altered in the following way. The view would be:

@model Coffee

@{
    Layout = "~/Views/Shared/_ProductLayout.cshtml
}

@section ProductSpecificProperties
{
    <div>@Html.DisplayFor(vm => vm.Farm)</div>
}

(In our MVC sample site, we have it implemented in the Coffee.cshtml file.)

This is what the layout would look like:

@model Product

@{
    Layout = "~/Views/Shared/_Layout.cshtml
}

<div>@Html.DisplayFor(vm => vm.ProductName)</div>

@Html.RenderSection("ProductSpecificProperties", false)

(In our sample site, we have the _ProductLayout.cshtml file.)

In the controller method, you should have a simple mechanism to pick the right view based on the type of the item to be displayed:

public async Task<ActionResult> Detail()
{
    var item = GetItemFromRepository();

    return View(item.GetType().Name, item);
}

(In our sample site, we have the ProductController.cs file.)

Wrapping Up

Do you like these solutions? Would you like such a solution to also support collections of items of various types? That way, you would get the flexibility of templates. In the comments, you can tell us how you would implement it. Eventually, we can come up with a solution ourselves. Let us know!

Tagged:
Sign In or Register to comment.