Search code examples
c#asp.net-coreasp.net5display-templates

Asp.Net 5 Core DisplayFor() Publish Bug


I am porting some code from Asp.net 4.7 to Asp.Net 5 Core. The code is using a foreach statement to loop through records. It is using the legacy "DisplayTemplates" functionality that matches types in the DisplayTemplates folder to polymorphically display different HTML for each item depending on the model. All classes are derived from the same base class.

In order to get it to work correctly in Asp.Net 5 Core, I had to change the code from:

 @foreach (var token in Model.TokenGraph)
 {
    @Html.DisplayFor(t => token)
 }

to:

 @foreach (var token in Model.TokenGraph)
 {
    @Html.DisplayFor(t => token, token.GetType().Name)
 }

... passing in the name of the type.

No problems problems and working correctly on my local machine.

The problem is when I publish the project to a remote server, the template used always defaults to the base class template.

Strangely enough, if I output the string returned by token.GetType().Name to the page, it matches the derived type name not the base type name.

Is this is a bug in .Net Core 5? If there is not a workaround, is there a better way to handle this in .Net 5?


Solution

  • The issue ended up being in the .csproj file. Somehow, either from the initial conversion from the old legacy project, or from moving the files around afterwards.

    Simply removing these entries below fixed the issue as .Net Core generally uses the entries to excludes files (or modify how they are compiled), unlike legacy .Net.

      <ItemGroup>
        <Compile Remove="Pages\Shared\DisplayTemplates\**" />
        <Content Remove="Pages\Shared\DisplayTemplates\**" />
        <EmbeddedResource Remove="Pages\Shared\DisplayTemplates\**" />
        <None Remove="Pages\Shared\DisplayTemplates\**" />
      </ItemGroup>
    
      <ItemGroup>
        <Content Include="Pages\Shared\DisplayTemplates\Token.cshtml">
          <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
          <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        </Content>
      </ItemGroup>
    
      <ItemGroup>
        <None Include="Pages\Shared\DisplayTemplates\TextToken.cshtml" />
        ....
      </ItemGroup>
    

    As an aside, I realized that it is possible to switch out the @Html.Display() call and use a dynamically named partial as below.

     @foreach (var token in Model.TokenGraph)
     {
        <partial name="@token.GetType().Name" model="token" />
     }
    

    The only change was that files had to be moved up to the top level of the Shared directory for them to be found. This can be managed better by setting a custom location in the Startup file, like so.

    services.AddMvc().AddRazorOptions(options =>
    {
        options.PageViewLocationFormats.Add("/Pages/DisplayTemplates/{0}.cshtml");
    });
    

    This would seem to be more in line with standard CORE and .Net 5, now that Html Helpers are on the way out.