RazorLight icon indicating copy to clipboard operation
RazorLight copied to clipboard

An Include stops Layout being rendered

Open marekvse opened this issue 6 years ago • 19 comments

A template renders fine if it has a Layout and two sections defined. However, as soon as I add an "include" it completely ignores the Layout and sections and only renders the actual content of the template plus the "include"

Steps to reproduce the behavior: So this renders fine:

@{
    Layout = "14"; // Simple Razor Layout
}
@section HEADER {
    @{ await IncludeAsync("16");  }
}
@section FOOTER {
    @{ await IncludeAsync("7");  }
}
<p>
  This is THE BODY of the template.
</p>

Whilst adding this at the end:

@{ await IncludeAsync("10"); }

causes it to ignore the Layout and HEADER and FOOTER sections and just renders the content of the

plust the contents of the include with Id 10. (Those Ids represent IDs in a database, loaded using the override of GetItemAsync of RazorLightProject class.

Expected behaviour: It should render all the tags as in the first case plus add the content of the Include ID 10 to the rendered body of the template.

Information (please complete the following information):

  • Win 10
  • NET Standard 2.0
  • RazorLight version - the latest code as on 1/2/2019 which should correspond to 2.0-beta1 but does not really seem to since, as already reported, the Cache.Remove does not remove the cached compiled template so I needed to fix that. By switching my reference to the project instead of the DLL from the Nuget package, I found that there are actually changes in the organization of the caching classes so the Nuget does not represent the current latest code.

I'm still looking into how to fix this behaviour, seeing that the project seems to be dormant for the past 8 months so I can't expect a fix soon probably, but since I do not know exactly how the in-place compilation works I've already spent a few hours on that, found that the generated code looks fine, but then the generated assembly only calls RazorLight for the one Include for some reason. I do not see how that may be caused by the RaorLight code, but I obviously am just not seeing that as the generated code seems to be fine as already mentioned.

marekvse avatar Feb 04 '19 00:02 marekvse

OK, spent quite a bit of time on this as I really needed it to work and here is the fix (probably not the best way to fix it, but it works and I don't have more time to study the project even more):

In template renderer I made the following changes:

// After line 46 (var context = RazorPage.PageContext;) I added:
var page = RazorPage;
// and replaced the await RenderLayoutAsync(…) with:
await RenderLayoutAsync(context, bodyWriter, page).ConfigureAwait(false);
// so I'm passing the actual current RazorPage into the function.

// Then I obviously needed to update its signature to accept that additional argument to:
private async Task RenderLayoutAsync(
    PageContext context, 
    ViewBufferTextWriter bodyWriter, 
    ITemplatePage page)
// and change var previousPage = RazorPage to:
var previousPage = page;
// This is because the compiled template, when it contains an await IncludeAsync, 
// calls a IncludeFunc lambda, which seems to change the object the RazorPage is pointing to. 
// This may mean a more fundamental flaw that may need to be fixed, but this fix works. 
// I did not have time to explore and understand all the workings of RazorLight more.

After that the template is rendered after the Include in the body is rendered. (At least I hope that's all that applies to this case.)

I have also had to do a few other fixes where my test templates were not producing the expected results:

If an outer Layout was assigned to an inner layout and the outer layout had its own named sections, these would not be rendered. After that was fixed, if content for all sections (present in both inner as well as outer layouts) were provided by the main body template, all was fine. However, if one or more of the outer layout's sections contents were to be provided by the inner layout, the "section ignored" exception was being raised. To fix those problems we need to ensure that:

PreviousSectionWriters are present where we actually need them and that We keep the list of already rendered section names available globally available to all templates that the top called template depends on. This would be ideally kept in an object that can be accessed by all of them per render, but I could not, in the time I had, identify a safe candidate for that so I instead kind of followed the previous way how this was done incorrectly, with the private readonly _renderedSections field of the TemplatePage class and just made this list shared among all of them by passing a reference to it from the previous to the current template. Here are the fixes for both 1) and 2):

// Add the following property to TemplatePageBase:
public HashSet<string> AllRenderedSectionNames { get; set; }

// Then, in TemplatePage class:
// - comment out the declaration of _renderedSections on line 12
// - on line 185 in private async Task<htmlString> RenderSectionAsyncCodre(…)
// replace if (_renderedSections.Contains….)) with:
if (AllRenderedSectionNames != null && AllRenderedSectionNames.Contains(sectionName))
// and then the next use of that field on line 192 with:
if (AllRenderedSectionNames == null) {
    AllRenderedSectionNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
AllRenderedSectionNames.Add(sectionName);

// And then in public override void EnsureRenderBodyOrSections() on line 243 swap _renderedSections for AllRenderedSectionNames.

// In TemplateRenderer.RenderLayoutAsync:

// replace layoutPage.PreviousSectionWriters = previousPage.SectionWriters; for
if (previousPage.PreviousSectionWriters != null && previousPage.PreviousSectionWriters.Count > 0) {
     layoutPage.PreviousSectionWriters = previousPage.PreviousSectionWriters;
     foreach(var item in previousPage.SectionWriters) {
          if (!layoutPage.PreviousSectionWriters.ContainsKey(item.Key))
               layoutPage.PreviousSectionWriters.Add(item.Key, item.Value);
     }
} else
     layoutPage.PreviousSectionWriters = previousPage.SectionWriters;

if (((TemplatePageBase)layoutPage).AllRenderedSectionNames == null && ((TemplatePageBase)previousPage).AllRenderedSectionNames != null)
     ((TemplatePageBase)layoutPage).AllRenderedSectionNames = ((TemplatePageBase)previousPage).AllRenderedSectionNames;

Also note that I have additionally implemented this fix: wik8wz/RazorLight@35f06da which may or may not influence the things I've done. All I know is that at this point RazorLight is rendering correctly all those most common layout scenarios I tested.

I hope this will help someone until the project is fixed properly.

marekvse avatar Feb 07 '19 04:02 marekvse

Thank you for your reply, that may help a lot

toddams avatar Feb 13 '19 20:02 toddams

@toddams I ran into this same issue. Is there any expectation for a new version of RazorLight soon? A few other issues are cropping up i.e. #134

ElleryW avatar Apr 10 '19 21:04 ElleryW

@toddams Also seeing an issue here and in another one that has been reviewed as @ElleryW states. Is there anything we can do to help?

keithnicholson avatar Apr 22 '19 13:04 keithnicholson

@marekvse This is a pretty sophisticated test case, given you are overriding GetItemAsync of RazorLightProject class. I am curious now that RazorLight-2.0.0-beta2 has been released if you've tried upgrading to it, and if it solves your example.

jzabroski avatar Dec 17 '19 23:12 jzabroski

@jzabroski I've got loads of work right now to mess with something that is working right now, but I do have an issue in the pipeline which may be connected to RazorLight (haven't looked into it yet), which is to be looked into soon. It may happen that trying a new version could be simpler than trying to work out that issue if it would seem caused by my meddling with the code which I did not understand 100%. Something to do with calling a dispose on an incorrect thread. I'm not sure that it has to do with RazorLight, but will keep you posted when I get to it.

marekvse avatar Dec 18 '19 07:12 marekvse

Well... Is your database access code based on the samples EntityFramworkRazorLightProject? https://github.com/toddams/RazorLight/tree/master/samples/RazorLight.Samples

If it is, that might be a good place to start unifying things/reporting bugs. Happy to help work through your problems to get you upgraded, even if we have to kick the can back and forth over a couple of weeks doing 30 minutes here and there. Let me know.

jzabroski avatar Dec 18 '19 18:12 jzabroski

@toddams and @jzabroski : First of all, sorry for such a late reply to your last post. I must have missed it in my mailbox. Anyway, today I've come back to email templating and found out, that after an upgrade to .NET Core 3.1 from v2 the RazorLight project did not work. So, seeing that beta5 should work in Core3 I have tried the latest version of the project in mine. Did not work for my templates and seems to have a problem with @section { .. } blocks now. I've played with it the whole day and ended up implementing basically all of my previous changes to the latest version. After testing, there seems to be just one, but major thing wrong and that is the @section problem. I can't figure that out. After the precompilation where it says @section SIDEBAR_TOP { ... content of the section... } in the template the precompiled code says Write(section);, where the "section" is not a variable defined in that code previously so it fails on that.

I can send you the whole update of the project so that you can do a diff on it to see all my (old) changes now applied to the latest beta and I can also send you the content of my templates to test with. If that's something you'd want me to do let me know which messaging platform I can contact you on. I'm on FB Messenger, Skype and Viber.

For now, here is my top-level template content:

@{
    Layout = "26"; 
}

@section SIDEBAR_TOP {
<div>
  <div style="font-size:110%">
    <h2 style="margin-top:0;margin-bottom:5px">
      Instructions
    </h2>
    <ol style="padding:0;margin-left:12px;list-style">
      <li><b>Log in</b> to the <a href="http://thesite.com/Dashboard">Dashboard</a></li>
      <li><b>Select the @Model["ORG_NAME"] organisation</b> at the top of the left sidebar below your user name.</li>
      <li>Start helping to solve bugs.</li>
    </ol>
  </div>
  <hr />
</div>
}

<p>
  Hello there,
</p>
<p>
  @Model["MANAGER"] has added you to the team working on the <b>@Model["APP_NAME"]</b> application within the <b>@Model["ORG_NAME"]</b> organisation.
</p>
<p>
  <i>Please follow the instructions on the right.</i>
</p>
@if(@Model["MESSAGE"] != null && @Model["MESSAGE"].Length > 0) {
  <div style="background:#f3f3f3;margin-bottom:9px">
    <table  cellpadding="9" bgcolor="#f3f3f3" style="padding:9px;background:#f3f3f3">
      <tr><td>
       <h4 style="margin-top:0;margin-bottom:5px">
          A message from @Model["MANAGER"]:
       </h4>
       <p>
         @Raw(Model["MESSAGE"])
       </p>
      </td></tr>
    </table>
</div>
}
<p>
  Best regards,<br>
  the TheSite.com team.
</p>

and here is what it produces:

#pragma checksum "24" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "e3b11b47bc75692fe9bb36358a01a51fcecd86cd"
// <auto-generated/>
#pragma warning disable 1591
[assembly:global::RazorLight.Razor.RazorLightTemplateAttribute(@"24", typeof(RazorLight.CompiledTemplates.GeneratedTemplate))]
namespace RazorLight.CompiledTemplates
{
    #line hidden
    public class GeneratedTemplate : global::RazorLight.TemplatePage<dynamic>
    {
        #pragma warning disable 1998
        public async override global::System.Threading.Tasks.Task ExecuteAsync()
        {
#nullable restore
#line 1 "24"
  
    Layout = "26"; 

#line default
#line hidden
#nullable disable
            WriteLiteral("\n");
#nullable restore
#line 5 "24"
Write(section);

#line default
#line hidden
#nullable disable
            WriteLiteral(@" SIDEBAR_TOP {
<div>
  <div style=""font-size:110%"">
    <h2 style=""margin-top:0;margin-bottom:5px"">
      Instructions
    </h2>
    <ol style=""padding:0;margin-left:12px;list-style"">
      <li><b>Log in</b> to the <a href=""http://thesite.com/Dashboard"">Dashboard</a></li>
      <li><b>Select the ");
#nullable restore
#line 13 "24"
                   Write(Model["ORG_NAME"]);

#line default
#line hidden
#nullable disable
            WriteLiteral(" organisation</b> at the top of the left sidebar below your user name.</li>\n      <li>Start helping to solve bugs. :-)</li>\n    </ol>\n  </div>\n  <hr />\n</div>\n}\n\n<p>\n  Hello there,\n</p>\n<p>\n  ");
#nullable restore
#line 25 "24"
Write(Model["MANAGER"]);

#line default
#line hidden
#nullable disable
            WriteLiteral(" has added you to the team working on the <b>");
#nullable restore
#line 25 "24"
                                                           Write(Model["APP_NAME"]);

#line default
#line hidden
#nullable disable
            WriteLiteral("</b> application within the <b>");
#nullable restore
#line 25 "24"
                                                                                                            Write(Model["ORG_NAME"]);

#line default
#line hidden
#nullable disable
            WriteLiteral("</b> organisation.\n</p>\n<p>\n  <i>Please follow the instructions on the right.</i>\n</p>\n");
#nullable restore
#line 30 "24"
 if(@Model["MESSAGE"] != null && @Model["MESSAGE"].Length > 0) {

#line default
#line hidden
#nullable disable
            WriteLiteral("  <div style=\"background:#f3f3f3;margin-bottom:9px\">\n    <table  cellpadding=\"9\" bgcolor=\"#f3f3f3\" style=\"padding:9px;background:#f3f3f3\">\n      <tr><td>\n       <h4 style=\"margin-top:0;margin-bottom:5px\">\n          A message from ");
#nullable restore
#line 35 "24"
                    Write(Model["MANAGER"]);

#line default
#line hidden
#nullable disable
            WriteLiteral(":\n       </h4>\n       <p>\n         ");
#nullable restore
#line 38 "24"
    Write(Raw(Model["MESSAGE"]));

#line default
#line hidden
#nullable disable
            WriteLiteral("\n       </p>\n      </td></tr>\n    </table>\n</div>\n");
#nullable restore
#line 43 "24"
}

#line default
#line hidden
#nullable disable
            WriteLiteral("<p>\n  Best regards,<br>\n  the TheSite.com team.\n</p>\n");
        }
        #pragma warning restore 1998
    }
}
#pragma warning restore 1591

The referenced layout's content is

@{
    Layout = "19"; /* Main layout */
}

@section HEADER {
    @{ await IncludeAsync("20", Model); /* HEADER: Standard header (R) */ }
}

@section FOOTER {
    @{ await IncludeAsync("21", Model); /* FOOTER: Standard footer (R) */ }
} 

@section SIDEBAR_BOTTOM {
	@{ await IncludeAsync("28", Model); /* INSERT: Side bar advertisement */ }
}

@RenderBody()

which in turn references the base layout containing all the HTML and and the 5 @RenderSection("NAME", required: false) supplied by the two layers above it.

All the templates are stored in the database and are retrieved by their IDs by a class that implements RazorLightProject. The comments in the code blocks in my templates are removed by a function I added to the project also, before they are processed further.

All this rendered fine before. However, it seems to me that the current problem with the @section may be somehow caused by the new version of the framework maybe? I hope not and you guys can figure that out. I tested that on a much simpler 2-level template setup (that is a master template and a template that uses it) and the master template can contain @RenderSection and it does not fail if the template that uses the master does not call @section . However, once it does I get the problem described here with the "Write(section)" failing on compilation.

marekvse avatar Mar 22 '20 09:03 marekvse

Hey guys, any news on this front?

marekvse avatar Apr 30 '20 01:04 marekvse

OK, so I've spent more time on this yesterday and today trying to make it work. I did find where the problem is, but I am not able to solve it because:

  1. It seems to be caused by a (very) different result in the GeneratedCode property from the call to Microsoft.AspNetCore.Razor.Language.DefaultRazorCodeDocument.GetCSharpDocument.
  2. I have no idea if and how to influence the result from that call.

For testing, I have a simplified template with only one layout containing one optional section called HEADER:

@{
    Layout = "36"; /* Test main layout */
}


@section HEADER {
<div>
  TEST <i>HEADER</i>
</div>
}

<p>
  Hello there,
</p>
<p>
  BLA BLA BLAH
</p>
<p>
  Best regards,<br><br>  
  from us.
</p>

The test layout content:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  </head>
  <body>
    @RenderSection("HEADER", required: false)
    <hr />
    @RenderBody()
  </body>
</html>

This compiles only when the "@section HEADER { ... }" part is left out. If it is included it results in "error CS0103: The name 'section' does not exist in the current context." after the call to compilation.Emit in the CompileAndEmit function body where the "result.Success" is FALSE.

The obvious reason for this is the aforementioned result for the GeneratedCode from the GetCSharpDocument call, which when run under .NET Core 3.1 returns the following from the source HTML template:

#pragma checksum "22" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "a463ecc46c7005fb78c6115266c4dad8e348fa6d"
// <auto-generated/>
#pragma warning disable 1591
[assembly:global::RazorLight.Razor.RazorLightTemplateAttribute(@"22", typeof(RazorLight.CompiledTemplates.GeneratedTemplate))]
namespace RazorLight.CompiledTemplates
{
    #line hidden
    public class GeneratedTemplate : global::RazorLight.TemplatePage<dynamic>
    {
        #pragma warning disable 1998
        public async override global::System.Threading.Tasks.Task ExecuteAsync()
        {
#nullable restore
#line 1 "22"
  
    Layout = "36"; 

#line default
#line hidden
#nullable disable
            WriteLiteral("\n\n");
#nullable restore
#line 6 "22"
Write(section);

#line default
#line hidden
#nullable disable
            WriteLiteral(" HEADER {\n<div>\n  HEADER\n</div>\n}\n\n<p>\n  Hello there,\n</p>\n<p>\n  BLA BLA BLAH\n</p>\n<p>\n  Best regards,<br><br>  \n  the TipOff.Stream team.\n</p>");
        }
        #pragma warning restore 1998
    }
}
#pragma warning restore 1591

(Notice the Write(section).)

For comparison, the same call to that function in .NET Core 2.1 returns this:

#pragma checksum "22" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "c1485ef954197a844195e602fb01c1a49c5f8597"
// <auto-generated/>
#pragma warning disable 1591
[assembly:global::RazorLight.Razor.RazorLightTemplateAttribute(@"22", typeof(RazorLight.CompiledTemplates.GeneratedTemplate))]
namespace RazorLight.CompiledTemplates
{
    #line hidden
    public class GeneratedTemplate : global::RazorLight.TemplatePage<dynamic>
    {
        #pragma warning disable 1998
        public async override global::System.Threading.Tasks.Task ExecuteAsync()
        {
#line 1 "22"
  
    Layout = "36"; /* Test main layout */

#line default
#line hidden
            BeginContext(47, 2, true);
            WriteLiteral("\n\n");
            EndContext();
            DefineSection("HEADER", async() => {
                BeginContext(66, 23, true);
                WriteLiteral("\n<div>\n  HEADER\n</div>\n");
                EndContext();
            }
            );
            BeginContext(91, 109, true);
            WriteLiteral("\n<p>\n  Hello there,\n</p>\n<p>\n  BLA BLA BLAH\n</p>\n<p>\n  Best regards,<br><br>  \n  the TipOff.Stream team.\n</p>");
            EndContext();
        }
        #pragma warning restore 1998
    }
}
#pragma warning restore 1591

where you can clearly see the DefineSection("HEADER", async() => { ... } that is missing from the 3.1 version and which is replaced there with the Write(section) and the WriteLiteral (" ... " ) contains the string "HEADER" twice there, which also seems to be quite clearly wrong.

Can anybody fix this behavior somehow? Any ideas @jzabroski or @toddams ?

marekvse avatar May 01 '20 03:05 marekvse

Any ideas anyone?

marekvse avatar Jun 23 '20 08:06 marekvse

@marekvse I missed your last three reply somehow. I just saw this bump - thank you for bumping it. I will try to take a look this weekend. I have such a jammed workweek this week that there is little chance I can get to digging into this, but I think you provided me with sufficient info to dig in, so thank you very much.

jzabroski avatar Jun 23 '20 13:06 jzabroski

Hi @jzabroski . That is great to hear. I'm especially eager to help in any way I can since this is really something that is part of my app and that I spent weeks on and that now, after conversion to .NET Core 3.1 does not work due to that error which to solve myself I don't seem to be equipped with the right knowledge at this time. It would be great if you could figure that out, I've only now had time to check my mail after more than a week so your answer was very nice to see. I can imagine how busy you probably are. There is no real rush for me yet even though I have to avoid this area of the app I'm developing right now, but it will take me some more time to finish it. It would be absolutely great if I could stop worrying about this sooner rather than later of course, but I'll just be happy if you were able to solve this eventually. Thanks for responding!

marekvse avatar Jun 30 '20 00:06 marekvse

Hi @marekvse . please try the latest beta. I think it was fixed there, but I did not 100% play by play reproduce your issue using a test. If you'd like to submit a failing test I will investigate further, but I was expecting this issue to be resolved via beta8 release (we have some problems right now due to a bug in nuget.exe, but that should get resolved shortly.)

jzabroski avatar Jun 30 '20 00:06 jzabroski

@marekvse I took your example from https://github.com/toddams/RazorLight/issues/240#issuecomment-622234122 and it appears to work now. I put the example on a branch to avoid polluting the mainline test cases. https://github.com/toddams/RazorLight/tree/example/issue240

jzabroski avatar Jun 30 '20 01:06 jzabroski

That's absolutely great to here. OK, I'm hopeful :). I'm going to apply the changes between my last version (which was beta 5) and the branch above now and see if it works and will report on that. Great thanks for now in any case!

marekvse avatar Jun 30 '20 01:06 marekvse

@marekvse When you say apply our changes, are you saying you have a forked copy and aren't using nuget packagereference? If so, please use the official release from Nuget and stop using a fork.

jzabroski avatar Jun 30 '20 02:06 jzabroski

Well, so I report that you've made at least one person very happy today. It does work!! I'm really really happy just now :).

As for the forking. Well, I did report about a year and a half ago some other problems and how I fixed them, but also said that I was not completely sure those were the ideal fixes as I could not say that I understand what I was fixing 100% so I did not submit those. I remember that a couple of those fixes came from suggestions within threads reporting the same problems I was having and then there were roughly two more, for me major, problems related to multi-level templates and keeping a reference to the right parent templates as well as a problem with caching that was returning a previously compiled template which was in the meantime changed and removed using the Remove method on the cache. The Remove was not removing the compiled template too, which was a major problem for me as my app is using RazorLight as an editor of templates, not just simply compiling already finished designs. This problem seems to still be there from what I remember I was overwriting still with my changes just earlier today. In another couple of places I found that the code was fixed exactly the way I did it for myself.

I did not fork it, I just downloaded the zip of the project and changed those things locally then. And not all seem to have been fixed since I still need to use my version. But if you want I can of course send my current version to you to look at the changes and decide if they are useful and then apply them or take them as an inspiration for a better fix. I did offer that several times before. I'd only be happy to be able to update the code directly from Github in the future.

marekvse avatar Jun 30 '20 04:06 marekvse

YEah, please send your current version (ideally, with individual git commits if you have them so it's not one big blob of changes.) If you dont want to upload to github you can send to my personal email address in my GitHub profile.

I can see why due to the project not being actively maintained for a year people started forking it. But I am getting on top of things and cleaning stuff up. Cache is one area that needs improving/testing. DI is another.

jzabroski avatar Jun 30 '20 13:06 jzabroski