6

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>

mheptinstall
  • 2,109
  • 3
  • 24
  • 44
  • In your razor page have you tried @Html.DisplayFor(principal => principal) ? – daremachine Oct 03 '19 at 19:19
  • That generates the error: "A local or parameter named 'principal' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter" – mheptinstall Oct 03 '19 at 19:33
  • What i used in past it could be in `~/Views/Shared/DisplayTemplates/_Principle.cshtml` or `~/Views/Principals/_Principle.cshtml` if you are using it in Page folder I'm not sure how view engine or route path was configured first try it on default path else are you using it inside area? – sairfan Oct 03 '19 at 19:41
  • @sairfan I get the same outcome whether they are located in the Pages folder or View folder. Using an ASP.NET Core Web Application project with either .NET Core/Framework this doesn't work. Using a ASP.NET Web Application (.NET Framework) project works. – mheptinstall Oct 03 '19 at 19:50
  • found this answer [here](https://stackoverflow.com/questions/50582964/how-to-use-displaytemplates-in-mvc-core-2-0) see if that is something helpful, and [this](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.0) may also help – sairfan Oct 03 '19 at 20:09

1 Answers1

9

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

itminus
  • 23,772
  • 2
  • 53
  • 88