Search code examples
asp.net-coredisplay-templates

How to use DisplayTemplates in ASP.Net Core Razor Pages with Model Inheritance?


Trying to get DisplayTemplates in ASP.Net Core 2.2 to work with classes that inherit from a base class similar to this question How to handle an entity model with inheritance in a view?

The Principal DisplayTemplate is being used for all items in the list, what am I missing?

PageModel

public class IndexModel : PageModel
{
    public List<Principal> Principals { get; set; } = new List<Principal>();

    public void OnGet()
    {
        Principals.Add(new Principal { Id = 1, Name = "Principal 1" });
        Principals.Add(new UserPrincipal { Id = 1, Name = "User 1", Age = 30 });
        Principals.Add(new GroupPrincipal { Id = 1, Name = "Group 1", Members = 5 });
        Principals.Add(new UserPrincipal { Id = 1, Name = "User 2", Age = 40 });
        Principals.Add(new GroupPrincipal { Id = 1, Name = "Group 2", Members = 3 });
    }
}

public class Principal
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class UserPrincipal : Principal
{
    public int Age { get; set; }
}

public class GroupPrincipal : Principal
{
    public int Members { get; set; }
}

RazorPage

@page
@model IndexModel

@foreach(var principal in Model.Principals)
{
    @Html.DisplayFor(p => principal)
}

~/Pages/Shared/DisplayTemplates/Principal.cshtml

@model Principal
<div>
    <h4>Principal</h4>
    @Model.Name
</div>

~/Pages/Shared/DisplayTemplates/UserPrincipal.cshtml

@model UserPrincipal
<div>
    <h4>User</h4>
    @Model.Name, Age @Model.Age
</div>

~/Pages/Shared/DisplayTemplates/GroupPrincipal.cshtml

@model GroupPrincipal
<div>
    <h4>Group</h4>
    @Model.Name, Members @Model.Members
</div>


Solution

  • The reason

    The Principal DisplayTemplate is being used for all items in the list,

    That's because the expression in @Html.DisplayFor(expression) won't be executed at all. They are parsed statically instead.

    For example, if we have an expression m.a.b.c where a is null at runtime, the @Html.DisplayFor(m => m.a.b.c) is still able to know the template for c.

    Since you're declaring the Principals as type List<Principal> , even if you make the Model.Principals hold UserPrincipal or GroupPrincipal at runtime, the "parser" still treat the principal as a base Principal type: They don't inspect the real type of instance. They just parse the type statically.

    How to fix

    Pass a template name when invoking Html.DisplayFor() so that the Html Helper knows the real template you want to use:

    @page
    @model IndexModel
    
    @foreach (var principal in Model.Principals)
    {
        @Html.DisplayFor(p => principal, principal.GetType().Name)
    }
    

    Demo

    enter image description here