<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss version="2.0">
  <channel>
    <title>Rick Strahl's Weblog</title>
    <link>https://weblog.west-wind.com/</link>
    <image>
      <url>ImageUrl</url>
      <title>Rick Strahl's Weblog</title>
      <link>https://weblog.west-wind.com/</link>
    </image>
    <description>Wind, waves, code and everything in between</description>
    <copyright>(c) West Wind Technologies 2006-2026</copyright>
    <pubDate>2026-05-15T06:12:40.4442482Z</pubDate>
    <lastBuildDate>2026-05-13T20:35:26.6375917Z</lastBuildDate>
    <generator>Rick Strahl's West Wind Weblog</generator>
    <xhtml:meta content="noindex" name="robots" xmlns:xhtml="http://www.w3.org/1999/xhtml"/><item>
      <title>Getting the Client IP Address in ASP.NET Core</title>
      <description>&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Getting-the-Client-IP-Address-in-ASP-NET-Core/ClientIpBanner.jpg" alt="Client Ip Banner"&gt;&lt;/p&gt;
&lt;p&gt;When I need to pick up the client IP Address in ASP.NET Core I always forget where to find the connection information.&lt;/p&gt;
&lt;p&gt;It's simple enough:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;HttpContext?.Connection?.RemoteIpAddress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but I never remember to look on the context object as I expect it to be on the Request &#128516;.&lt;/p&gt;
&lt;p&gt;It's also useful to remember that if requests are proxied, we need to return the &lt;strong&gt;forwarded IP address&lt;/strong&gt;, rather than the proxy's IP Address. Finally, in most cases you'd likely want the ipv4 address rather than an IPv6 address.&lt;/p&gt;
&lt;p&gt;Here's ready to use helper extension method for the &lt;code&gt;HttpRequest&lt;/code&gt; class that makes this more easily accessible:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;/// &amp;lt;summary&amp;gt;
/// Returns the client IPv4 Address for a request.
///
/// Checks proxy forwarding first, the actual ip
/// and returns null.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;request&amp;quot;&amp;gt;The HttpRequest instance.&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;checkForProxy&amp;quot;&amp;gt;
/// Indicates whether to check for proxy headers.
/// 
/// Default returns the un-translated connection's IP Address
/// returned by the Web server.
///
/// When true, checks the Proxy forwarding headers 
/// `X-Forwarded-For`, `Forwarded` and `X-Real-IP`
/// in that order and returns the 1st valid IP address found.
/// &amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;IP Address or null&amp;lt;/returns&amp;gt;
public static string GetClientIpAddress(this HttpRequest request, bool checkForProxy = false)
{
    if (request == null) return null;

    string ip = NormalizeIpAddress(request.HttpContext?.Connection?.RemoteIpAddress);
    if (!checkForProxy)
        return ip;

    string proxy = GetForwardedIpAddress(request.Headers[&amp;quot;X-Forwarded-For&amp;quot;].FirstOrDefault());
    if (!string.IsNullOrEmpty(proxy))
        return proxy;

    proxy = GetForwardedIpAddress(request.Headers[&amp;quot;Forwarded&amp;quot;].FirstOrDefault(), true);
    if (!string.IsNullOrEmpty(proxy))
        return proxy;

    proxy = GetForwardedIpAddress(request.Headers[&amp;quot;X-Real-IP&amp;quot;].FirstOrDefault());
    return proxy ?? ip;
}


/// &amp;lt;summary&amp;gt;
/// Handle various forwarding headers and their custom parsing
/// of multiple proxy chain values
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;headerValue&amp;quot;&amp;gt;The value of the forwarding header.&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;isForwardedHeader&amp;quot;&amp;gt;Indicates if the header is the `Forwarded` header.&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;The extracted IP address or null if none found.&amp;lt;/returns&amp;gt;
private static string GetForwardedIpAddress(string headerValue, bool isForwardedHeader = false)
{
    if (string.IsNullOrWhiteSpace(headerValue))
        return null;

    foreach (var value in headerValue.Split(','))
    {
        string candidate = value?.Trim();
        if (string.IsNullOrWhiteSpace(candidate))
            continue;

        if (isForwardedHeader)
        {
            candidate = candidate.Split(';')
                .Select(segment =&amp;gt; segment?.Trim())
                .FirstOrDefault(segment =&amp;gt; segment != null &amp;amp;&amp;amp; segment.StartsWith(&amp;quot;for=&amp;quot;, StringComparison.OrdinalIgnoreCase));

            if (string.IsNullOrWhiteSpace(candidate))
                continue;

            candidate = candidate.Substring(4).Trim();
        }

        candidate = candidate.Trim('&amp;quot;');

        if (candidate.StartsWith(&amp;quot;[&amp;quot;, StringComparison.Ordinal) &amp;amp;&amp;amp; candidate.Contains(&amp;quot;]&amp;quot;, StringComparison.Ordinal))
            candidate = candidate.Substring(1, candidate.IndexOf(']') - 1);
        else if (candidate.Count(ch =&amp;gt; ch == ':') == 1)
        {
            var parts = candidate.Split(':');
            if (parts.Length == 2 &amp;amp;&amp;amp; IPAddress.TryParse(parts[0], out _))
                candidate = parts[0];
        }


        if (string.Equals(candidate, &amp;quot;unknown&amp;quot;, StringComparison.OrdinalIgnoreCase))
            continue;

        if (IPAddress.TryParse(candidate, out var address))
            return NormalizeIpAddress(address);
    }

    return null;
}

/// &amp;lt;summary&amp;gt;
/// Return as IPv4 address if the address is an IPv4-mapped IPv6 address,
/// otherwise return the original address as a string.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;address&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
private static string NormalizeIpAddress(IPAddress address)
{
    if (address == null)
        return null;

    if (address.IsIPv4MappedToIPv6)
        address = address.MapToIPv4();

    return address.ToString();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bulk of this code is related to the Proxy forwarding handling which is optional. If you know you're directly connected to the Internet, you can skip the proxy forwarding stuff - in fact it's a good idea to do this to avoid any spoofing from a client. The various forwarding headers provide multiple IP Addresses in the proxy chain and you essentially need to pick out the first IP address to get the original address.&lt;/p&gt;
&lt;p&gt;You can also find this as part of the &lt;code&gt;HttpRequestExtensions&lt;/code&gt; class in the &lt;a href="https://github.com/RickStrahl/Westwind.AspNetCore/blob/f244102fcc3b9fbed5f0d736bd0bb9cd2cd57799/Westwind.AspNetCore/Extensions/HttpRequestExtensions.cs#L204"&gt;&lt;code&gt;Westwind.AspNetCore&lt;/code&gt; package here&lt;/a&gt; which provides a host of other small, but frequently used extensions.&lt;/p&gt;
&lt;h2 id="alternative-use-the-ip-forwarded-headers-middleware"&gt;Alternative: Use the IP Forwarded Headers Middleware&lt;/h2&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;thanks to @RichardD in the comments&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;If you know you are always running behind a proxy server, and you need the IP Address in all or most requests, you can run the Forwarded Headers Middleware which handles the above logic and simply populates the &lt;code&gt;HttpContext.Connection.RemoteIpAddress&lt;/code&gt; making the process complete transparent. That certainly works, but depending on how you use IP Address might be overkill.&lt;/p&gt;
&lt;p&gt;The middle is configured like this in the service configuration during startup:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;builder.Services.Configure&amp;lt;ForwardedHeadersOptions&amp;gt;(options =&amp;gt;
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse(&amp;quot;127.0.10.1&amp;quot;));
    options.ForwardedForHeaderName = &amp;quot;X-Forwarded-For-My-Custom-Header-Name&amp;quot;;
});

...

// near the very top of the middleware pipeline
// to ensure subsequent middleware pieces get the updated addresses
app.UseForwardedHeaders();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id="summary"&gt;Summary&lt;/h3&gt;
&lt;p&gt;Nothing new here, but given how often I fumble for this value, creating this wrapper and putting a reminder here for quick lookup seems worth the effort &#128516;&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/Westwind.AspNetCore"&gt;Westwind.AspNetCore on Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-10.0"&gt;Configure ASP.NET Core to work with proxy servers and load balancers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <link>https://weblog.west-wind.com/posts/2026/May/13/Getting-the-Client-IP-Address-in-ASPNET-Core</link>
      <guid isPermaLink="false">dkvnwxdbh42n</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/May/13/Getting-the-Client-IP-Address-in-ASPNET-Core#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/May/13/Getting-the-Client-IP-Address-in-ASPNET-Core</guid>
      <pubDate>Wed, 13 May 2026 10:35:26 GMT</pubDate>
    </item>
    <item>
      <title>Putting the Westwind.Scripting C# Templating Library to work, Part 2</title>
      <description>&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/Part2-Banner.jpg" alt="Part2 Banner"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a two part series that discusses the Westwind.Scripting Template library&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://weblog.west-wind.com/posts/2026/Apr/20/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1"&gt;Part 1: Revisiting C# Scripting with the Westwind.Scripting Templating Library&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Part 2&lt;/strong&gt;: Real world integration for a Local Rendering and Web Site Generation
&lt;small&gt; &lt;em&gt;(this post)&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;In part 1 of this series I introduced the &lt;code&gt;Westwind.Scripting&lt;/code&gt; library, how it works and how you can use it and integrate it into your applications. In part 2, I'm going over implementation details of hosting a scripting engine for a specific scenario that generates local static Html output for a for a project based solution that requires both local preview and local Web site generation. As part of that process I'll point out a number of issues that you likely have to consider in this somewhat common scenario.&lt;/p&gt;
&lt;p&gt;If you haven't already, I'd recommend you &lt;a href="https://weblog.west-wind.com/posts/2026/Apr/20/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1"&gt;read Part 1&lt;/a&gt; so you have a good idea what the library provides and how it works. While not required, this post will make a lot more sense with that context in place.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://weblog.west-wind.com/posts/2026/Apr/20/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1"&gt;Revisiting Westwind.Script Template Scripting Library, Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="putting-templating-to-use-in-a-real-world-scenario"&gt;Putting Templating to use in a Real World Scenario&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ScriptParser&lt;/code&gt; class in the &lt;code&gt;Westwind.Scripting&lt;/code&gt; library allows you to execute C# based, Handlebars-like templates that merge template text with model data that can be embedded into the template with Handlebars style &lt;code&gt;{{ expression }}&lt;/code&gt; and &lt;code&gt;{{% code block }}&lt;/code&gt; directives. You can use both string based templates and templates from files that can also include references to partials and layout pages. All of that was covered in Part 1.&lt;/p&gt;
&lt;p&gt;While using the &lt;code&gt;ScriptParser&lt;/code&gt; for demos and single template output is easy enough, using it to integrate into a larger application, interacting with host application features and content, and especially generating many document Web site output with related dependencies, requires a bit more work.&lt;/p&gt;
&lt;p&gt;I'm using my &lt;a href="https://documentationmonster.com"&gt;Documentation Monster&lt;/a&gt; application as an example here as it's been my dog-fooding project to put the &lt;code&gt;ScriptParser&lt;/code&gt; to real world use. It's a project based documentation solution that statically produces Web Site Html output. It generates Html output in two ways in an offline desktop application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Renders a single topic for Live Preview as you type topic content&lt;/li&gt;
&lt;li&gt;Renders many topics in a documentation project into a full self-contained Web site&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DM uses script templates to render each topic with a specific topic type - Topic, Header, ClassHeader, ClassMethod, ClassProperty, WhatsNew, ExternalLink etc. -  each representing a separate Html template in an Html file (ie. &lt;code&gt;Topic.html&lt;/code&gt;) on disk which are the templates I'm passing into the &lt;code&gt;ScriptParser&lt;/code&gt; class for execution. The model then feeds specific a specific model that contains the topic, project and other support data and some application logic.&lt;/p&gt;
&lt;p&gt;These topic templates templates are similar but all have overlapping content: All of them have a header and topic body, but they also have customized areas to them: For example, &lt;em&gt;ClassHeader&lt;/em&gt; has a class member table, inheritance list, lists assembly and namespace, &lt;strong&gt;ClassProperty&lt;/strong&gt;/&lt;strong&gt;ClassMethod&lt;/strong&gt;/&lt;strong&gt;ClassField&lt;/strong&gt; member templates have syntax and exception settings, &lt;strong&gt;ExternalLink&lt;/strong&gt; displays an Html page during development but redirects to a Url in a published output file etc. In other words, each topic type has some unique things going on with it that the template script reflects. If a topic has a type that can't map to a template, the default template which in this case is the &lt;strong&gt;Topic&lt;/strong&gt; template is used via fallback.&lt;/p&gt;
&lt;p&gt;Templates are user customizable, so they are sensitive to changes and are recompiled whenever changes are &lt;strong&gt;detected&lt;/strong&gt; in the generated code.&lt;/p&gt;
&lt;p&gt;The raw Html rendering of topics is simple enough - the template is executed as is and produces Html output. But once you introduce document dependencies  like images, scripts, css etc. and you create output that may end up in nested folders, &lt;strong&gt;pathing becomes a concern&lt;/strong&gt; in statically generated content. The reason is, the 'hosting' environment can't be determined at render time and the content may be hosted locally via file system (for individual preview in this case), a root Web site, or in a sub-folder of a Web site.&lt;/p&gt;
&lt;p&gt;So then the question is what's a relative path based on? What's a root path (&lt;code&gt;/&lt;/code&gt;) based on? The rendered output has to be self-contained, and templates are responsible for properly making paths natural to use for a specific output environment.&lt;/p&gt;
&lt;p&gt;This means some or all Urls may have to be fixed up and in some cases a &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; has to be provided in the Html content for each page. None of that can happen automatically so this is a manual post-processing step that is application specific.&lt;/p&gt;
&lt;p&gt;In addition, when rendering topics a few things that need to be considered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When and how to render using the ScriptParser&lt;/li&gt;
&lt;li&gt;Where to store the Templates consistently&lt;/li&gt;
&lt;li&gt;Ensure that rendered output can be referenced relatively&lt;/li&gt;
&lt;li&gt;Ensure there's consistent BasePath to reference
root and project wide Urls (ie. &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;~/&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's walk through what template rendering looks like inside of an application.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id="create-a-templatehost"&gt;Create a TemplateHost&lt;/h3&gt;
&lt;p&gt;In projects that use template rendering I like to create a &lt;strong&gt;TemplateHost&lt;/strong&gt; class that encapsulates all tasks related to executing the scripting engine. This simplifies configuration of the template engine in one place and provides a few easily accessible methods for rendering templates - in the case of DM rendering topics to string and to file.&lt;/p&gt;
&lt;h4 id="scriptparser-configuration---references-and-namespaces"&gt;ScriptParser Configuration - References and Namespaces&lt;/h4&gt;
&lt;p&gt;Adding of dependent references and namespaces is one of the most frustrating things of doing runtime compilation of code and so that step needs to be consolidated into a single place.&lt;/p&gt;
&lt;p&gt;Here's the base implementation of the TemplateHost with the &lt;code&gt;CreateScriptParser()&lt;/code&gt; method implementation from DM:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class TemplateHost
{
    public ScriptParser Script
    {
        get
        {
            if (field == null)
                field = CreateScriptParser();
            return field;
        }
        set;
    }

    public static ScriptParser CreateScriptParser()
    {
        var script = new ScriptParser();            
        
        // Good chunk of .NET Default libs
        script.ScriptEngine.AddDefaultReferencesAndNamespaces();
        
        // Any library in Host app that's been loaded up to this point
        // In DM everything required actually is loaded through this
        script.ScriptEngine.AddLoadedReferences();
        
        // explicit assemblies not or not yet used by host
        //script.ScriptEngine.AddAssembly(&amp;quot;privatebin/Westwind.Ai.dll&amp;quot;);
        //script.ScriptEngine.AddAssembly(typeof(Westwind.Utilities.StringUtils));

        script.ScriptEngine.AddNamespace(&amp;quot;Westwind.Utilities&amp;quot;);
        script.ScriptEngine.AddNamespace(&amp;quot;DocMonster&amp;quot;);
        script.ScriptEngine.AddNamespace(&amp;quot;DocMonster.Model&amp;quot;);            
        script.ScriptEngine.AddNamespace(&amp;quot;DocMonster.Templates&amp;quot;);
        script.ScriptEngine.AddNamespace(&amp;quot;MarkdownMonster&amp;quot;);
        script.ScriptEngine.AddNamespace(&amp;quot;MarkdownMonster.Utilities&amp;quot;);

        script.ScriptEngine.SaveGeneratedCode = true;
        script.ScriptEngine.CompileWithDebug = true;            

        // {{ expr }} is Html encoded - {{! expr }} required for raw Html output
        script.ScriptingDelimiters.HtmlEncodeExpressionsByDefault = true;            

		// custom props that expose in the template without Model. prefix
        script.AdditionalMethodHeaderCode =
            &amp;quot;&amp;quot;&amp;quot;
            DocTopic Topic = Model.Topic;
            var Project = Model.Project;
            var Configuration = Model.Configuration;
            var Helpers = Model.Helpers;                
            var DocMonsterModel = Model.DocMonsterModel;
            var AppModel = MarkdownMonster.mmApp.Model;
            
            var BasePath = new Uri(FileUtils.NormalizePath( Project.ProjectDirectory + &amp;quot;\\&amp;quot;) );

            &amp;quot;&amp;quot;&amp;quot;;            
        return script;
    }
    
    // render methods below
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In DM the TemplateHost is created on first access of a &lt;code&gt;Project.TemplateHost&lt;/code&gt; property and then persists for the lifetime of the project, unless explicitly recreated. There is some overhead in creating the script parser environment and we might be generating &lt;strong&gt;a lot&lt;/strong&gt; of documents very quickly when generating Web site output so a cached instance is preferred.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;get
{
    if (field == null)
        field = CreateScriptParser();
    return field;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the parser is set up with common default and all loaded assembly references from the host. I then add all the specific libraries that may not have been loaded yet, and any custom namespaces that are used by the various application specific components that are used in the templates. This is perhaps the main reason to use a &lt;code&gt;TemplateHost&lt;/code&gt; like wrapper: To hide away all this application specific configuration for a one time config and then can be forgotten about - you don't want to be doing this sort of thing in your application or business logic code.&lt;/p&gt;
&lt;p&gt;In DM I'm lucky enough that all application dependencies live in a couple of assemblies that are already loaded by the time the parser is activated so I can rely on &lt;code&gt;script.ScriptEngine.AddLoadedReferences()&lt;/code&gt; to bring in all of my dependencies. If your application is broken out into many small dependencies that may not work as some assemblies may not have loaded yet in which case you have to ensure you manually use &lt;code&gt;script.AddAssemblyReference()&lt;/code&gt; to pull in explicit assemblies preferrably using the &lt;code&gt;Type&lt;/code&gt; overload.&lt;/p&gt;
&lt;p&gt;Another thing of note in this particual usage scenario: The &lt;code&gt;AdditionalMethodHeaderCode&lt;/code&gt; property is used to expose various objects as top level objects to the template script. So rather than having to specify &lt;code&gt;{{ Model.Topic.Title }}&lt;/code&gt; we can just use &lt;code&gt;{{ Topic.Title }}&lt;/code&gt; and &lt;code&gt;{{ Helpers.ChildTopicsList() }}&lt;/code&gt; for example. Shortcuts are useful, and you can stuff anything you want to expose in the script beyond the model here.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Since the templates in DM are accessible to end-users for editing, making the template expressions simpler makes for a more user friendly experience. Highly recommended.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="rendering"&gt;Rendering&lt;/h4&gt;
&lt;p&gt;The actual render code is pretty straight forward by calling &lt;code&gt;RenderTemplateFile()&lt;/code&gt; which renders a template from file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;public string RenderTemplateFile(string templateFile, RenderTemplateModel model)
{
    ErrorMessage = null;

    Script.ScriptEngine.ObjectInstance = null; // make sure we don't cache
    
    // explicitly turn these off for live output
	Script.ScriptEngine.SaveGeneratedCode = false;
	Script.ScriptEngine.CompileWithDebug = false;
	Script.ScriptEngine.DisableAssemblyCaching = false;
    
    string basePath = model.Project.ProjectDirectory;
    model.PageBasePath = System.IO.Path.GetDirectoryName(model.Topic.RenderTopicFilename);

    string result = Script.ExecuteScriptFile(templateFile, model, basePath: basePath);

    if (Script.Error)
	{
	    // run again this time with debugging options on
	    Script.ScriptEngine.SaveGeneratedCode = true;
	    Script.ScriptEngine.CompileWithDebug = true;
	    Script.ScriptEngine.DisableAssemblyCaching = true;  // force a recompile
	
	    Script.ExecuteScriptFile(templateFile, model, basePath: basePath);
	
	    Script.ScriptEngine.SaveGeneratedCode = false;
	    Script.ScriptEngine.CompileWithDebug = false;
	    Script.ScriptEngine.DisableAssemblyCaching = false;
	
	    // render the error page
	    result = ErrorHtml(model);
	    ErrorMessage = Script.ErrorMessage + &amp;quot;\n\n&amp;quot; + Script.GeneratedClassCodeWithLineNumbers;
	}

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the basic raw template execution logic that produces direct generated output - in this case Html.&lt;/p&gt;
&lt;p&gt;Note that the processing checks for template errors which captures either compilation or runtime errors. If an error occurs, the current render process is re-run with all the debug options turned on so I can get additional error information to display on the error page.&lt;/p&gt;
&lt;p&gt;I'll talk more about the error display in a minute.&lt;/p&gt;
&lt;h4 id="template-layout"&gt;Template Layout&lt;/h4&gt;
&lt;p&gt;I haven't talked about what the templates look like: DM uses relatively small topic templates, with a more complex Layout page that provides for the Web site's page chrome. The actual project output renders both the content the headers and footers and there's a bunch of logic to pull in the table of contents and handle navigation to new topics efficiently. The preview renders the same content but some of the aspects like the table of content are visually hidden in that mode.&lt;/p&gt;
&lt;p&gt;All of that logic is encapsulated in the layout page and the supporting JavaScript scripts.&lt;/p&gt;
&lt;p&gt;At the core re the Html/Handlebars topic templates. As mentioned, each topic type is a template that is rendered. Topic, Header, ExternalLink, WhatsNew, ClassHeader, ClassProperty, ClassMethod etc. each with their own custom formats. Each of the templates then references the same layout page (you could have several different one however if you chose)&lt;/p&gt;
&lt;p&gt;Here's an example &lt;strong&gt;Content Page&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;topic.html Template&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{%
    Script.Layout = &amp;quot;_layout.html&amp;quot;;
}}

&amp;lt;h2 class=&amp;quot;content-title&amp;quot;&amp;gt;
    &amp;lt;img src=&amp;quot;/_docmonster/icons/{{ Model.Topic.DisplayType }}.png&amp;quot;&amp;gt;
    {{ Model.Topic.Title }}
&amp;lt;/h2&amp;gt;

&amp;lt;div class=&amp;quot;content-body&amp;quot; id=&amp;quot;body&amp;quot;&amp;gt;
    {{% if (Topic.IsLink &amp;amp;&amp;amp; Topic.Body.Trim().StartsWith(&amp;quot;http&amp;quot;)) { }}
        &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;
            &amp;lt;a href=&amp;quot;{{! Model.Topic.Body }}&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;{{ Model.Topic.Title }}&amp;lt;/a&amp;gt;
            &amp;lt;a href=&amp;quot;{{! Model.Topic.Body }}&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;&amp;lt;i class=&amp;quot;fa-solid fa-up-right-from-square&amp;quot; style=&amp;quot;font-size: 0.7em; vertical-align: super;&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;

        &amp;lt;blockquote style=&amp;quot;font-size: 0.8em;&amp;quot;&amp;gt;&amp;lt;i&amp;gt;In rendered output this link opens in a new browser window.
            For preview purposes, the link is displayed in this generic page.
            You can click the link to open the browser with the link which is the behavior you see when rendered.&amp;lt;/i&amp;gt;
        &amp;lt;/blockquote&amp;gt;
    {{% } else { }}
        {{ Model.Helpers.Markdown(Model.Topic.Body) }}
    {{% } }}
    
&amp;lt;/div&amp;gt;

{{% if (!string.IsNullOrEmpty(Model.Topic.Remarks)) {  }}
    &amp;lt;h3 class=&amp;quot;outdent&amp;quot; id=&amp;quot;remarks&amp;quot;&amp;gt;Remarks&amp;lt;/h3&amp;gt;
    {{ Helpers.Markdown(ModelTopic.Remarks) }}
{{% } }}


{{% if (!string.IsNullOrEmpty(Topic.Example))  {  }}
    &amp;lt;h3 class=&amp;quot;outdent&amp;quot; id=&amp;quot;example&amp;quot;&amp;gt;Example&amp;lt;/h3&amp;gt;
    {{ Helpers.Markdown(Topic.Example) }}
{{% } }}

{{% if (!string.IsNullOrEmpty(Topic.SeeAlso)) { }}
    &amp;lt;h4 class=&amp;quot;outdent&amp;quot; id=&amp;quot;seealso&amp;quot;&amp;gt;See also&amp;lt;/h4&amp;gt;
    &amp;lt;div class=&amp;quot;see-also-container&amp;quot;&amp;gt;
        {{ Helpers.FixupSeeAlsoLinks(Topic.SeeAlso) }}
    &amp;lt;/div&amp;gt;
{{% } }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For demonstration purposes I'm showing both the &lt;code&gt;Model.Topic&lt;/code&gt; and the custom header based direct binding to &lt;code&gt;Topic&lt;/code&gt; via &lt;code&gt;script.AdditionalMethodHeaderCode&lt;/code&gt; I showed earlier. Both point at the same value.&lt;/p&gt;
&lt;p&gt;Note also the code block at the top that pulls in the Layout page:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{%
    Script.Layout = &amp;quot;_layout.html&amp;quot;;
}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The layout page then looks like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_Layout.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    {{%
     var theme = Project.Settings.RenderTheme;
     if(Topic.TopicState.IsPreview) { }}
    &amp;lt;base href=&amp;quot;{{ Model.PageBasePath }}&amp;quot; /&amp;gt;
    {{% } }}

    &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
    &amp;lt;title&amp;gt;{{ Topic.Title }} - {{ Project.Title }}&amp;lt;/title&amp;gt;

    {{% if (!string.IsNullOrEmpty(Topic.Keywords)) { }}
    &amp;lt;meta name=&amp;quot;keywords&amp;quot; content=&amp;quot;{{ Topic.Keywords.Replace(&amp;quot; \n&amp;quot;,&amp;quot;, &amp;quot;) }}&amp;quot; /&amp;gt;
    {{% } }}
    {{% if(!string.IsNullOrEmpty(Topic.Abstract)) { }}
    &amp;lt;meta name=&amp;quot;description&amp;quot; content=&amp;quot;{{! Topic.Abstract }}&amp;quot; /&amp;gt;
    {{% } }}
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1,maximum-scale=1&amp;quot; /&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;~/_docmonster/themes/scripts/bootstrap/bootstrap.min.css&amp;quot; /&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;~/_docmonster/themes/scripts/fontawesome/css/font-awesome.min.css&amp;quot; /&amp;gt;
    &amp;lt;link id=&amp;quot;AppCss&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;~/_docmonster/themes/{{ theme }}/docmonster.css&amp;quot; /&amp;gt;

    &amp;lt;script src=&amp;quot;~/_docmonster/themes/scripts/highlightjs/highlight.pack.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&amp;quot;~/_docmonster/themes/scripts/highlightjs-badge.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;link href=&amp;quot;~/_docmonster/themes/scripts/highlightjs/styles/vs2015.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;
    &amp;lt;script src=&amp;quot;~/_docmonster/themes/scripts/bootstrap/bootstrap.bundle.min.js&amp;quot; async&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&amp;quot;~/_docmonster/themes/scripts/lunr/lunr.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
        window.page = {};
        window.page.basePath = &amp;quot;{{ Project.Settings.RelativeBaseUrl }}&amp;quot;;
        window.renderTheme=&amp;quot;{{ Project.Settings.RenderThemeMode }}&amp;quot;;
    &amp;lt;/script&amp;gt;
    &amp;lt;script src=&amp;quot;~/_docmonster/themes/scripts/docmonster.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

    {{% if(Topic.TopicState.IsPreview) { }}
    &amp;lt;!-- Preview Navigation and Syncing --&amp;gt;
    &amp;lt;script src=&amp;quot;~/_docmonster/themes/scripts/preview.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    {{% } }}

&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;!-- Markdown Monster Content --&amp;gt;
    &amp;lt;div class=&amp;quot;flex-master&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;banner&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;float-end&amp;quot;&amp;gt;
                &amp;lt;button id=&amp;quot;themeToggleBtn&amp;quot; type=&amp;quot;button&amp;quot; onclick=&amp;quot;toggleTheme()&amp;quot;
                        class=&amp;quot;btn btn-sm btn-secondary theme-toggle&amp;quot;
                        title=&amp;quot;Toggle Light/Dark Theme&amp;quot;&amp;gt;
                    &amp;lt;i id=&amp;quot;themeToggleIcon&amp;quot;
                       class=&amp;quot;fa fa-moon text-warning&amp;quot;&amp;gt;
                    &amp;lt;/i&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div class=&amp;quot;float-start sidebar-toggle&amp;quot;&amp;gt;
                &amp;lt;i class=&amp;quot;fa fa-bars&amp;quot;
                   title=&amp;quot;Show or hide the topics list&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
            &amp;lt;/div&amp;gt;

			{{% if (Topic.Incomplete) { }}
               &amp;lt;div class=&amp;quot;float-end mt-2 &amp;quot; title=&amp;quot;This topic is under construction.&amp;quot;&amp;gt;
                   &amp;lt;i class=&amp;quot;fa-duotone fa-triangle-person-digging fa-lg fa-beat&amp;quot;
                   style=&amp;quot;--fa-primary-color: #333; --fa-secondary-color: goldenrod; --fa-secondary-opacity: 1; --fa-animation-duration: 3s;&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;
	           &amp;lt;/div&amp;gt;
		    {{% } }}

            &amp;lt;img src=&amp;quot;~/images/logo.png&amp;quot; class=&amp;quot;banner-logo&amp;quot; /&amp;gt;
            &amp;lt;div class=&amp;quot;projectname&amp;quot;&amp;gt; {{ Project.Title }}&amp;lt;/div&amp;gt;

            &amp;lt;div class=&amp;quot;byline&amp;quot;&amp;gt;
                &amp;lt;img src=&amp;quot;~/_docmonster/icons/{{ Topic.DisplayType }}.png&amp;quot;&amp;gt;
                {{ Topic.Title }}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&amp;quot;page-content&amp;quot;&amp;gt;
            &amp;lt;div id=&amp;quot;toc-container&amp;quot; class=&amp;quot;sidebar-left toc-content&amp;quot;&amp;gt;
                &amp;lt;nav class=&amp;quot;visually-hidden&amp;quot;&amp;gt;
                    &amp;lt;a href=&amp;quot;~/tableofcontents.html&amp;quot;&amp;gt;Table of Contents&amp;lt;/a&amp;gt;
                &amp;lt;/nav&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div class=&amp;quot;splitter&amp;quot;&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;nav class=&amp;quot;topic-outline&amp;quot;&amp;gt;
                &amp;lt;div class=&amp;quot;topic-outline-header&amp;quot;&amp;gt;On this page:&amp;lt;/div&amp;gt;
                &amp;lt;div class=&amp;quot;topic-outline-content&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/nav&amp;gt;

            &amp;lt;div id=&amp;quot;MainContent&amp;quot; class=&amp;quot;main-content&amp;quot;&amp;gt;
                &amp;lt;!-- Rendered Content --&amp;gt;

                &amp;lt;article class=&amp;quot;content-pane&amp;quot;&amp;gt;
                    {{ Script.RenderContent() }}
                &amp;lt;/article&amp;gt;

                &amp;lt;div class=&amp;quot;footer&amp;quot;&amp;gt;

                    &amp;lt;div class=&amp;quot;float-start&amp;quot;&amp;gt;
                        &amp;amp;copy; {{ Project.Owner }}, {{ DateTime.Now.Year }} &amp;amp;bull;
                        updated: {{ Topic.Updated.ToString(&amp;quot;MMM dd, yyyy&amp;quot;) }}
                        &amp;lt;br /&amp;gt;
                        {{%
                            string mailBody = $&amp;quot;Project: {Project.Title}\nTopic: {Topic.Title}\n\nUrl:\n{ Project.Settings.WebSiteBaseUrl?.TrimEnd('/') + Project.Settings.RelativeBaseUrl }{ Topic.Id }.html&amp;quot;;
                            mailBody = WebUtility.UrlEncode(mailBody).Replace(&amp;quot;+&amp;quot;, &amp;quot;%20&amp;quot;);
                        }}
                        &amp;lt;a href=&amp;quot;mailto:{{ Project.Settings.SupportEmail }}?subject=Support: {{ Project.Title }} - {{ Topic.Title }}&amp;amp;body={{ mailBody }}&amp;quot;&amp;gt;Comment or report problem with topic&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;

                    &amp;lt;div class=&amp;quot;float-end&amp;quot;&amp;gt;
                        &amp;lt;a href=&amp;quot;https://documentationmonster.com&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;~/_docmonster/images/docmonster.png&amp;quot; style=&amp;quot;height: 3.8em&amp;quot;/&amp;gt;&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;!-- End Rendered Content --&amp;gt;
            &amp;lt;/div&amp;gt; &amp;lt;!-- End MainContent --&amp;gt;
        &amp;lt;/div&amp;gt; &amp;lt;!-- End page-content --&amp;gt;
    &amp;lt;/div&amp;gt;   &amp;lt;!-- End flex-master --&amp;gt;
    
    &amp;lt;!-- End Markdown Monster Content --&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full rendered site output looks something like this for a topic:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/RenderedHtmlSite.png" alt="Documentation Monster Rendered Html Site"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 1&lt;/strong&gt; - Documentation Monster Rendered site output&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;The rendered topic content is in the middle panel of the display - all the rest is both static and dynamically rendered Html that is controlled through the Layout page. Both the table of contents and the document outline on the right are rendered using dynamic loading via JavaScript, while the header and footer are static with some minor embedded expressions.&lt;/p&gt;
&lt;p&gt;With a Layout page this is easy to set up and maintain as there's a single page that handles that logic and it's easily referenced from each of the topic templates with a single layout page reference.&lt;/p&gt;
&lt;p&gt;Behind the scenes, the parser looks for a Layout page directive in the content page requested by the &lt;code&gt;ScriptParser&lt;/code&gt;, and if it finds one combines the layout page and content page into a single page that is executed. Essentially the &lt;code&gt;Script.Layout&lt;/code&gt; command in the content page causes the referenced Layout page to be loaded, and the &lt;code&gt;{{ Script.RenderContent() }}&lt;/code&gt; tag then is replaced with the content page content resulting in a single Html/Handlebars template. This combined content is then compiled and executed to produce the final merged output.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;So far things are pretty straight forward relying on the core features of the scripting engine: We're pointing at template and a layout page and it produces Html in various forms depending on the type of template that we are dealing with.&lt;/p&gt;
&lt;p&gt;That still leaves the task fixing up paths to make sure they work in the final 'hosting' environment.&lt;/p&gt;
&lt;h3 id="base-path-handling---pagebasefolder-and-basefolder"&gt;Base Path Handling - PageBaseFolder and BaseFolder&lt;/h3&gt;
&lt;p&gt;When rendering Html output that depends on other resources like images CSS and scripts that referenced either as relative or site rooted paths, it's important that the page context can find these resources based on natural relative and absolute page path resolution.&lt;/p&gt;
&lt;p&gt;There are two concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Page Base Path for page relative links&lt;/li&gt;
&lt;li&gt;Project Base Path for site root paths&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="page-base-path-for-relative-linking"&gt;Page Base Path for Relative Linking&lt;/h4&gt;
&lt;p&gt;Page base path refers to resolving relative paths in the document. For example from the current page referencing an image as &lt;code&gt;SomeImage.png&lt;/code&gt; (same folder) or &lt;code&gt;../images/SomeFolder&lt;/code&gt; (relative path). In order for these relative paths to work the page has to be either running directly from the folder or the page has to be mapped into that page context.&lt;/p&gt;
&lt;p&gt;In the context of a Web site that's simple as you have a natural page path that always applies. However, for previewing pages that are often rendered to a temporary file in an external location, which is then displayed in a WebView for preview. In that scenario relative path needs to be fixed up so it can find resolve links.&lt;/p&gt;
&lt;p&gt;This scenario can be handled by explicitly forcing the page's &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; path to reflect the current page's path:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{%
    if(Topic.TopicState.IsPreview)  { }}
       &amp;lt;base href=&amp;quot;{{ Model.PageBasePath }}&amp;quot; /&amp;gt;
{{%  }  }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that I'm only applying the &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; tag in Preview mode. In Preview the Html is rendered into a temp location, so I map back to the actual location where relative content is expected and that makes relative links work.&lt;/p&gt;
&lt;p&gt;Here's what that looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/PageBasePathAndRootPath.png" alt="Page Base Path And Root Path"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 2&lt;/strong&gt; - Providing a Page Base path when rendering to a temp location is crucial to ensure relative resources like images can be found!&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Without the &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; path in the document, the page would look for the image in the rendered output location - in the &lt;code&gt;TMP&lt;/code&gt; folder - and of course would not find the image there.&lt;/p&gt;
&lt;p&gt;For final rendered output running in a Web Browser, this is not necessary as the page naturally runs out of the appropriate folder and no &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; path is applied.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Why not just render local output into the 'correct relative location' where relative content can be found?&lt;/em&gt;&lt;br&gt;
For local preview that's often impractical due to permissions or simply for cluttering up folders with temporary render files. Generated files can wreak havoc with source control or Dropbox and permissions unless explicit exceptions are set up. I recommend that local WebView content that has dependencies should always be rendered to a temporary location and then be back-linked via &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; paths to ensure that relative paths can resolve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="root-paths-in-temporary-location"&gt;Root Paths In Temporary Location&lt;/h4&gt;
&lt;p&gt;The other more important issue has to do with resolving the root path. This is especially important when rendering to a temporary file, but it can even be an issue if you create a 'site' that sits off a Web root.&lt;/p&gt;
&lt;p&gt;For example, most of my documentation 'sites' live in a &lt;code&gt;/docs&lt;/code&gt; folder off the main Web site rather than at the &lt;code&gt;/&lt;/code&gt; root.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://markdownmonster.west-wind.com/docs/"&gt;Markdown Monster Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this site, any links that reference &lt;code&gt;/&lt;/code&gt; and intend to go back to the documentation root, are going to jump back to the main Web site root instead. From a project perspective I want &lt;code&gt;/&lt;/code&gt; to mean the project root, but I don't want to have to figure out while I'm working on the content whether I have to use &lt;code&gt;/docs/&lt;/code&gt; or &lt;code&gt;/&lt;/code&gt;. In fact, I never want to hard code a reference to &lt;code&gt;/docs/&lt;/code&gt; in my templates or in user content, but expect to reference the project root as &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In DM this is very relevant when the site is generated. We can then specify a root folder &lt;code&gt;/&lt;/code&gt; by default or &lt;code&gt;/docs/&lt;/code&gt; explicitly as shown here entered for my specific site:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/ProvidingPageBasePathWhenRendering.png" alt="Providing Page Base Path When Rendering"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 3&lt;/strong&gt; - When rendering to a non-root location it has to be generated at Html generation time.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Here's what this looks like in the template &lt;em&gt;(you can use either &lt;code&gt;~/&lt;/code&gt; or &lt;code&gt;/&lt;/code&gt;)&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;link id=&amp;quot;AppCss&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; 
      href=&amp;quot;~/_docmonster/themes/{{ theme }}/docmonster.css&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and here is the rendered output with the &lt;code&gt;/docs/&lt;/code&gt; path for any &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;~/&lt;/code&gt; starting Urls:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;link id=&amp;quot;AppCss&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; 
      href=&amp;quot;/docs/_docmonster/themes/Dharkan/docmonster.css&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fixup applies to any Url generated both from the templates and from user generated topic content so the fixup has to occur post rendering - it's not something that can be fixed via the template.&lt;/p&gt;
&lt;p&gt;This fixup also happens in Preview mode where the full path is prefixed which produces these nasty looking paths:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;link id=&amp;quot;AppCss&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; 
      href=&amp;quot;C:/Users/rstrahl/Documents/Documentation Monster/MarkdownMonster/_docmonster/themes/Dharkan/docmonster.css&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means that the root path need to be fixed up depending on the root path environment. There are several scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Root is root &lt;code&gt;/&lt;/code&gt; rendered into root of site for final Html Site output - no replacements required.&lt;/li&gt;
&lt;li&gt;Root is a subfolder (ie. &lt;code&gt;/docs/&lt;/code&gt;) - &lt;code&gt;/&lt;/code&gt; is replaced with &lt;code&gt;/docs/&lt;/code&gt; in all paths&lt;/li&gt;
&lt;li&gt;Root is the project folder from Temp location - &lt;code&gt;/&lt;/code&gt; is replaced with a folder file location or WebView virtual host name root&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is something that is not part of the &lt;code&gt;ScriptParser&lt;/code&gt; class, but rather has to be handled at the application layer post fix up, and it's fairly specific to the Html based generation that takes place. In my template based applications I &lt;strong&gt;always&lt;/strong&gt; have do one form or another of this sort of fix up.&lt;/p&gt;
&lt;p&gt;Here's what this looks like in DM:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;string html = Project.TemplateHost.RenderTemplateFile(templateFile, model);

...

// Fix up any locally linked .md extensions to .html
string basePath = null;
if (renderMode == TopicRenderModes.Html)
{
 	 // fix up DM specific `dm-XXX` links like topic refs, .md file links etc.
     html = FixupHtmlLinks(html, renderMode);  

     // Specified value in dialog ('/docs/` or `/`)
     basePath = Project.Settings.RelativeBaseUrl;  // 
}            
else if (renderMode == TopicRenderModes.Preview || renderMode == TopicRenderModes.Chm)
{
	// special `dm-XXX` links are handled via click handlers
	
	// Project directory is our base folder
    basePath = Path.TrimEndingDirectorySeparator(Project.ProjectDirectory).Replace(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;) + &amp;quot;/&amp;quot;;
}

html = html
           .Replace(&amp;quot;=\&amp;quot;/&amp;quot;, &amp;quot;=\&amp;quot;&amp;quot; + basePath)
           .Replace(&amp;quot;=\&amp;quot;~/&amp;quot;, &amp;quot;=\&amp;quot;&amp;quot; + basePath)
           // UrlEncoded
           .Replace(&amp;quot;=\&amp;quot;%7E/&amp;quot;, &amp;quot;=\&amp;quot;&amp;quot; + basePath)
           // Escaped
          .Replace(&amp;quot;=\&amp;quot;\\/&amp;quot;, &amp;quot;=\&amp;quot;/&amp;quot;)
          .Replace(&amp;quot;=\&amp;quot;~\\/&amp;quot;, &amp;quot;=\&amp;quot;/&amp;quot;);
          
if(renderMode == TopicRenderModes.Preview ||
   renderMode == TopicRenderModes.Print || 
   renderMode == TopicRenderModes.Chm)
{
   // explicitly specify local page path
   var path = Path.GetDirectoryName(topic.GetExternalFilename()).Replace(&amp;quot;\\&amp;quot;,&amp;quot;/&amp;quot;) + &amp;quot;/&amp;quot;;
   html = html.Replace(&amp;quot;=\&amp;quot;./&amp;quot;, &amp;quot;=\&amp;quot;&amp;quot; + path)
              .Replace(&amp;quot;=\&amp;quot;../&amp;quot;, &amp;quot;=\&amp;quot;&amp;quot; + path + &amp;quot;../&amp;quot;);
}

OnAfterRender(html, renderMode);

return html;          
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key piece is the Html fix up block that takes any &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;~/&lt;/code&gt; links - including some variations - and explicitly replaces the actual base path that's specified.&lt;/p&gt;
&lt;p&gt;Another issue is addressed in the following block that deals with print and local pages: Relative links don't resolve in print and PDF output, so they have to be explicitly specfied as part of the link. Apparently the WebView print engine ignores &lt;code&gt;&amp;lt;basePath&amp;gt;&lt;/code&gt; for many things and so even relative links have to be fixed up with an explicit prefix even if the page is in the correct location.&lt;/p&gt;
&lt;p&gt;So yeah - path handling is a pain in the ass! &#129315; But hopefully this section gives you the information you need to fix up your paths as needed.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id="error-handling"&gt;Error Handling&lt;/h3&gt;
&lt;p&gt;When a template is run there is a possibility that it can fail. Typically it fails because there's an error in the template itself, which tends to generate compilation errors, or you can also run into runtime errors when code execution fails.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ScriptParser&lt;/code&gt; automatically captures error information for both compilation and runtime errors are provides convenient members to access them. There's &lt;code&gt;ErrorMessage&lt;/code&gt; and ``
If an error occurs, DM checks for it and then generates an error page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (Script.Error)
{
    result = ErrorHtml();
    ErrorMessage = Script.ErrorMessage + &amp;quot;\n\n&amp;quot; + Script.GeneratedClassCodeWithLineNumbers;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;code&gt;ErrorHtml()&lt;/code&gt; is the method that creates the error.&lt;/p&gt;
&lt;p&gt;You can keep this real simple and just render the error message and optionally the code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;public string ErrorHtml(string errorMessage = null, string code = null)
{            
    if (string.IsNullOrEmpty(errorMessage))
        errorMessage = Script.ErrorMessage;
    if (string.IsNullOrEmpty(code))
        code = Script.GeneratedClassCodeWithLineNumbers;

    string result =
            &amp;quot;&amp;lt;style&amp;gt;&amp;quot; +
            &amp;quot;body { background: white; color; black; font-family: sans;}&amp;quot; +
            &amp;quot;&amp;lt;/style&amp;gt;&amp;quot; +
            &amp;quot;&amp;lt;h1&amp;gt;Template Rendering Error&amp;lt;/h3&amp;gt;\r\n&amp;lt;hr/&amp;gt;\r\n&amp;quot; +
            &amp;quot;&amp;lt;pre style='font-weight: 600;margin-bottom: 2em;'&amp;gt;&amp;quot; + WebUtility.HtmlEncode(errorMessage) + &amp;quot;&amp;lt;/pre&amp;gt;\n\n&amp;quot; +
            &amp;quot;&amp;lt;pre&amp;gt;&amp;quot; + WebUtility.HtmlEncode(code) + &amp;quot;&amp;lt;/pre&amp;gt;&amp;quot;;                   

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can present a nicer error page that itself is rendered through a template. This is the error page that is used in DM.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/DisplayErrorMessages.png" alt="Display Error Messages"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 4&lt;/strong&gt; - Displaying render error information for debugging purposes&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;The code for this implementation is more complex.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public string ErrorHtml(RenderTemplateModel model, string errorMessage = null, string code = null)
  {
      if (string.IsNullOrEmpty(errorMessage))
          errorMessage = Script.ErrorMessage;
      if (string.IsNullOrEmpty(code))
          code = Script.GeneratedClassCodeWithLineNumbers;

      var origTemplateFile = model.Topic.GetRenderTemplatePath(model.Project.Settings.RenderTheme);            
      var templateFile = Path.Combine(Path.GetDirectoryName(origTemplateFile), &amp;quot;ErrorPage.html&amp;quot;);
      string errorOutput = null;

      if (File.Exists(templateFile))
      {
      	  // render the error template
          var lastException = Script.ScriptEngine.LastException;
          model?.TemplateError = new TemplateError
          {
              Message = errorMessage,
              GeneratedCode = code,
              TemplateFile = origTemplateFile,
              CodeErrorMessage = errorMessage,
              CodeLineError = string.Empty,
              Exception = lastException
          };
          model.TemplateError.Message = model.TemplateError.WrapCompilerErrors(model.TemplateError.Message);
          model.TemplateError.ParseCompilerError();

          // Try to execute ErrorPage.html template

          bool generateScript = Script.SaveGeneratedClassCode;
          Script.SaveGeneratedClassCode = false;

          errorOutput = Script.ExecuteScriptFile(templateFile, model, basePath: model.Project.ProjectDirectory);

          Script.SaveGeneratedClassCode = generateScript;
      }

	  // if template doesn't exist or FAILs render a basic error page
      if (string.IsNullOrEmpty(errorOutput))
      {
          if (Script.ErrorMessage.Contains(&amp;quot; CS&amp;quot;) &amp;amp;&amp;amp; Script.ErrorMessage.Contains(&amp;quot;):&amp;quot;))
          {
              errorOutput =
                      &amp;quot;&amp;lt;style&amp;gt;&amp;quot; +
                      &amp;quot;body { background: white; color: black; font-family: sans-serif; }&amp;quot; +
                      &amp;quot;&amp;lt;/style&amp;gt;&amp;quot; +
                      &amp;quot;&amp;lt;h1&amp;gt;Template Compilation Error&amp;lt;/h3&amp;gt;\r\n&amp;lt;hr/&amp;gt;\r\n&amp;quot; +
                      &amp;quot;&amp;lt;p style='margin-bottom: 2em; '&amp;gt;&amp;quot; + HtmlUtils.DisplayMemo(model.TemplateError.WrapCompilerErrors(errorMessage)) + &amp;quot;&amp;lt;/pre&amp;gt;\n\n&amp;quot; +
                      (model.Topic.TopicState.TopicRenderMode == TopicRenderModes.Preview
                          ? &amp;quot;&amp;lt;pre&amp;gt;&amp;quot; + WebUtility.HtmlEncode(code) + &amp;quot;&amp;lt;/pre&amp;gt;&amp;quot;
                          : &amp;quot;&amp;quot;);
          }
          else
          {
              errorOutput =
                  &amp;quot;&amp;lt;style&amp;gt;&amp;quot; +
                  &amp;quot;body { background: white; color: black; font-family: sans-serif;}&amp;quot; +
                  &amp;quot;&amp;lt;/style&amp;gt;&amp;quot; +
                  &amp;quot;&amp;lt;h1&amp;gt;Template Rendering Error&amp;lt;/h3&amp;gt;\r\n&amp;lt;hr/&amp;gt;\r\n&amp;quot; +
                   &amp;quot;&amp;lt;p style='font-weight: 600;margin-bottom: 2em; '&amp;gt;&amp;quot; + WebUtility.HtmlEncode(errorMessage) + &amp;quot;&amp;lt;/p&amp;gt;\n\n&amp;quot; +
                   (model.Topic.TopicState.TopicRenderMode == TopicRenderModes.Preview
                       ? &amp;quot;&amp;lt;hr/&amp;gt;&amp;lt;pre&amp;gt;&amp;quot; + WebUtility.HtmlEncode(code) + &amp;quot;&amp;lt;/pre&amp;gt;&amp;quot;
                       : &amp;quot;&amp;quot;);
          }
      }

      return errorOutput;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;And that brings us to the end of this two-part post series. In this second part I've given a look behind the scenes of what's involved in running a scripting engine for site generation and local preview. As you can tell this is a little more involved than the basics of running a script template using the &lt;code&gt;ScriptParser&lt;/code&gt; class as described in part one of this series.&lt;/p&gt;
&lt;p&gt;Pathing is the main thing that can give you headaches - especially if there are multi-use cases of where your output is rendered. In the case of Documentation Monster, output can both be rendered into a final output Web site, or be used locally for previewing content from the file system and also be used for generating PDF and Print output which have additional specific requirements for pathing. The issues involved depend to some degrees on how you set up your application, but for me at least, I spent a lot of time getting the pathing to work correctly across all the output avenues. I hope that what I covered here will be of help in figuring out what needs to be considered, if not solving the issues directly.&lt;/p&gt;
&lt;p&gt;I've been very happy with how using ScriptParser with its new features of Layout/Sections support works for this project. Given that I've been sitting in analysis paralysis for so long prior fighting with Razor, the results I've gotten are a huge relief, both in terms of features and functionality and performance. Hopefully the Westwind.Scripting library and the Script Templating will be useful to some of you as well.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://weblog.west-wind.com/posts/2026/Apr/20/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1"&gt;Part 1: Revisiting C# Scripting with the Westwind.Scripting Templating Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/Westwind.Scripting"&gt;Westwind.Scripting Library on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/Westwind.Scripting/blob/master/ScriptAndTemplates.md"&gt;Westwind.Scripting Templating Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-C-Code-Compilation-Revisited-for-Roslyn"&gt;Previous Post: Runtime Compilation with Roslyn and Building Westwind.Scripting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://documentationmonster.com"&gt;Documentation Monster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://markdownmonster.west-wind.com"&gt;Markdown Monster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <link>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2</link>
      <guid isPermaLink="false">5314605</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2</guid>
      <pubDate>Thu, 23 Apr 2026 16:04:43 GMT</pubDate>
    </item>
    <item>
      <title>Revisiting C# Scripting with the Westwind.Scripting Templating Library, Part 1</title>
      <description>&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/Scripting-Banner.jpg" alt="Scripting Banner"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a two part series that discusses the Westwind.Scripting Template library&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Part 1: An introduction to Template Scripting and how it works    &lt;small&gt;&lt;em&gt;(this post)&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2"&gt;Part 2: Real world integration for Local Rendering and Web Site Generation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Recently I updated my &lt;a href="https://github.com/RickStrahl/Westwind.Scripting/blob/master/ScriptAndTemplates.md"&gt;C# template scripting engine&lt;/a&gt; that uses a &lt;code&gt;ScriptParser&lt;/code&gt; class in &lt;a href="https://github.com/RickStrahl/Westwind.Scripting"&gt;Westwind.Scripting&lt;/a&gt; by adding support for &lt;strong&gt;Layout Pages&lt;/strong&gt; and &lt;strong&gt;Sections&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ScriptParser&lt;/strong&gt; is a small, self-contained C# based template rendering engine that merges a text document with embedded expressions and code. It uses Handlebars-like syntax mixed with a simple mechanism for raw C# code expressions and code blocks. The scripting engine turns templates into executable .NET code that runs at native .NET speeds for fast template rendering.&lt;/p&gt;
&lt;p&gt;Similar in concept to Razor, which also compiles templates down to runnable code, &lt;strong&gt;ScriptParser&lt;/strong&gt; lets you use raw C# code as the 'markup language' in your templates using familiar Handlebars syntax. Raw C# is used for all language and structure constructs, so unlike some other Handlebars scripting solutions, there are no weird 'scripting language constructs' involved; the scripting language is just raw C#.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;There are two main features to the &lt;code&gt;Westwind.Scripting&lt;/code&gt; library:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;C# Script Execution Engine&lt;/strong&gt;&lt;br&gt;
This is the core execution engine that allows you to compile and run C# code from source at runtime. You can execute code by providing a standalone snippet, a complete method, or an entire class definition to be loaded and accessed dynamically. The engine manages the full lifecycle of the script: generating the necessary wrapper code, compiling it via Roslyn, loading dependencies, caching the resulting assemblies for performance, and executing the compiled code. All of this including how it works is covered in &lt;a href="https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-C-Code-Compilation-Revisited-for-Roslyn"&gt;a previous article&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Template Scripting Engine&lt;/strong&gt;&lt;br&gt;
The template scripting engine and the focus of this post series, leverages the execution engine by first parsing a Handlebars-style template into C# code, then compiling and executing the generated code. Templates use Handlebars-style syntax with &lt;code&gt;{{ expression }}&lt;/code&gt; and &lt;code&gt;{{% codeBlock }}&lt;/code&gt; tags to embed C# expressions and blocks within text using a model object passed in as the execution context. Expressions and code blocks can then access the model's data and methods to expand into the output. Templates can be executed from strings or files, with the model merged into the template as its data context. Because templates can contain code blocks, they can (but probably shouldn't) express complex logic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post I'm only focusing on the latter Template Scripting Engine via &lt;strong&gt;ScriptParser&lt;/strong&gt;, although that component uses the C# Script Execution after parsing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ScriptParser&lt;/strong&gt; isn't new and it has undergone a number of changes over its lifetime, but I realize now that I never mentioned it outside of the documentation. Since I spent a bit of time recently adding new features that add &lt;strong&gt;Layout Pages&lt;/strong&gt; and &lt;strong&gt;Sections&lt;/strong&gt;, this is as good a time as any to write about what it is and how it's been very useful to me.&lt;/p&gt;
&lt;p&gt;Here's an overview of the template features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handlebars-like syntax using raw C# code&lt;/li&gt;
&lt;li&gt;String based template execution&lt;/li&gt;
&lt;li&gt;File based script execution&lt;/li&gt;
&lt;li&gt;Support for Partials loaded from disk &lt;small&gt;&lt;em&gt;(both string and file scripts)&lt;/em&gt;&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;Support for Layout and Section directives &lt;small&gt;&lt;em&gt;(file scripts only)&lt;/em&gt;&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;Expressions and code blocks use raw C# syntax&lt;/li&gt;
&lt;li&gt;Support for latest C# syntax&lt;/li&gt;
&lt;li&gt;Uses native C# language constructs - no pseudo scripting language&lt;/li&gt;
&lt;li&gt;Familiar Handlebars syntax with C# code:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{{ C# expression }}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{% C# code block }}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{: html encoded Expression }}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{! raw expression }}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{@ commented block @}}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Code blocks can be used for:
&lt;ul&gt;
&lt;li&gt;self contained code blocks&lt;/li&gt;
&lt;li&gt;Any C# structured statements (for, while, if, using  etc.)&lt;br&gt;
can split across multiple code blocks
with literal text, expressions and code blocks&lt;br&gt;
in between.  &lt;strong&gt;It's all raw C# code!&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Script parses to plain C# code at runtime&lt;/li&gt;
&lt;li&gt;Compiled template generation code is very fast
&lt;ul&gt;
&lt;li&gt;Expressions and Codeblocks execute directly &lt;small&gt;&lt;em&gt;(no Reflection or Expressions)&lt;/em&gt;&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;Compiled code is very efficient
&lt;ul&gt;
&lt;li&gt;Code is compiled on first execution&lt;/li&gt;
&lt;li&gt;First run is slightly slower&lt;/li&gt;
&lt;li&gt;Subsequent invocation is faster&lt;/li&gt;
&lt;li&gt;Code is cached to avoid re-compilation and startup cost
on subsequent execution&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Error Handling Support
&lt;ul&gt;
&lt;li&gt;Captures Compilation and Runtime errors&lt;/li&gt;
&lt;li&gt;Compilation errors capture line numbers and source reference&lt;/li&gt;
&lt;li&gt;Runtime Errors can provide stack info&lt;/li&gt;
&lt;li&gt;Compiled source can be captured&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-build"&gt;Why Build?&lt;/h2&gt;
&lt;p&gt;So why not use Razor and build something completely from scratch? I built &lt;strong&gt;ScriptParser&lt;/strong&gt; originally after I started several desktop and non-Web projects with Razor for templating using first an internal and then a third party Razor library, and eventually ran into walls with both of them.&lt;/p&gt;
&lt;p&gt;Razor is great in full ASP.NET Web applications when you use it with integrated tooling in Visual Studio or Rider. For pure Web applications Razor is awesome! But when used elsewhere, between the complexity of running it outside of ASP.NET, the differences in behavior, the Html based formatting oddities for plain text use cases, the required ASP.NET runtime requirements and potential for runtime compilation going away, the crappy Razor language and terrible 'syntax highlighting' support in non .NET editors, plus the constant churn of the engine internals made me eventually give up trying to run Razor outside of ASP.NET Web applications.&lt;/p&gt;
&lt;p&gt;I went back and forth between continuing on with Razor (and several other libraries like DotLiquid and Fluid) and building a custom engine, resulting in long stretch of &lt;strong&gt;analysis paralysis&lt;/strong&gt; which made me put off going forward with several projects for some time!&lt;/p&gt;
&lt;p&gt;At some point I decided I had enough and just build exactly what I needed! Since I had built script engines before (as far back as FoxPro in the early 90's no less) and decided it would be more efficient and flexible to do something similar in .NET. Turns out that was the right call: Even the original very limited version of &lt;strong&gt;ScriptParser&lt;/strong&gt; I built ended up being a very small easily re-usable scripting engine that uses raw C# code and runs very fast at compiled speeds. The syntax isn't as fancy as Razor's neat C# syntax integration in HTML, but it also doesn't require fancy tooling in editors to be still readable in any Html or plain text editor which is actually quite important for non-ASP.NET applications. And almost any Html editor does a decent job with Handlebars syntax these days. And as a bonus the Handlebars syntax is generally more familiar and easier to understand than Razor syntax outside of .NET circles.&lt;/p&gt;
&lt;p&gt;I also like the idea of having a fully self-contained, .NET-based scripting solution that is easily embeddable, and not dependent on the whims of some big and constantly shifting scripting environment. The ScriptParser implementation is simple and unlikely to change, as it's merely a parser plus raw C# code that generates output. If C# changes, the existing parser can accommodate that simply by adding the latest Roslyn libraries. There's nothing magic there. And for me personally that's a huge bonus!&lt;/p&gt;
&lt;h2 id="show-me-the-money"&gt;Show me the Money!&lt;/h2&gt;
&lt;p&gt;If you want to jump to code and examples for the runtime compilation or C# Handlebars style scripting, head on over to the GitHub repo. The repo has all the info you need to get started quickly, and if you want you can look at the source code or check out the tests that demonstrate much of the functionality.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/Westwind.Scripting"&gt;Westwind.Scripting on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/Westwind.Scripting/blob/master/ScriptAndTemplates.md"&gt;Template Script Expansion in Westwind.Scripting specific topic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to read more about the dynamic compilation features which are also used by this library to actually compile and run the templates, look at the previous blog post for the base &lt;code&gt;Westwind.Scripting&lt;/code&gt; features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-C-Code-Compilation-Revisited-for-Roslyn"&gt;Deep Dive on the Westwind.Scripting inner C# Compilation Workings&lt;/a&gt;  &lt;small&gt;&lt;em&gt;(original release blog post)&lt;/em&gt;&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep reading if you want to learn more about the template scripting and the new Layout/Section features.&lt;/p&gt;
&lt;h2 id="a-bit-of-background-on-westwindscripting-and-scriptparser"&gt;A bit of Background on Westwind.Scripting and ScriptParser&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/RickStrahl/Westwind.Scripting"&gt;Westwind.Scripting library&lt;/a&gt; was originally created as a C# runtime source-code based compilation library, but it also includes the &lt;a href="https://github.com/RickStrahl/Westwind.Scripting/blob/master/ScriptAndTemplates.md"&gt;ScriptParser Template Engine&lt;/a&gt;. The template engine provides &lt;strong&gt;C# Handlebars-style templating&lt;/strong&gt; that uses embedded raw C# code expressions and code blocks to compile templates into executable .NET code.&lt;/p&gt;
&lt;p&gt;I originally created &lt;strong&gt;Westwind.Scripting&lt;/strong&gt; almost 20 years ago, and it has gone through many behind the scenes iterations as compiler technology in .NET has changed. The latest incarnation a few years back &lt;a href="https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-C-Code-Compilation-Revisited-for-Roslyn"&gt;updated the library to support the latest Roslyn compilation APIs&lt;/a&gt; At that time I also added the &lt;code&gt;ScriptParser&lt;/code&gt; class for Handlebars-like script template parsing and execution using .NET expressions and code snippets.&lt;/p&gt;
&lt;p&gt;My primary use cases have been for template snippet expansions with embedded expressions and logic in Markdown Monster and Html generation templates in several text-based documentation generation tools. The templating has been a great addition for those scenarios.&lt;/p&gt;
&lt;h3 id="installing-the-nuget-package"&gt;Installing the NuGet Package&lt;/h3&gt;
&lt;p&gt;To get started with the runtime compilation and/or the Template Scripting you can install a single NuGet package:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ps"&gt;dotnet add package Westwind.Scripting 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This installs the actual &lt;code&gt;Westwind.Scripting&lt;/code&gt; library and also the .NET Roslyn compiler libraries required to handle runtime code compilation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4 id="--rosyln-runtime-dependency"&gt;&lt;i class="fas fa-info-circle" style="font-size: 1.1em"&gt;&lt;/i&gt;  Rosyln Runtime Dependency&lt;/h4&gt;
&lt;p&gt;Unfortunately the Roslyn runtime compilation library &lt;code&gt;Microsoft.CodeAnalysis.CSharp&lt;/code&gt; is not part of the distributed .NET Runtime, so this library has to be added explicitly, and it adds several MBs of Roslyn dependency DLLs to your application's distribution.&lt;/p&gt;
&lt;p&gt;It's a small price to pay for the ability to dynamically compile code at runtime, but something to keep in mind.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="running-template-script-expansion"&gt;Running Template Script Expansion&lt;/h3&gt;
&lt;p&gt;To give you a quick idea how &lt;a href="https://docs.west-wind.com/Westwind.Scripting/Westwind-Scripting-Class-Reference/Westwind-Scripting/ScriptParser-Class.html"&gt;ScriptParser class&lt;/a&gt; works with Handlebars &lt;code&gt;{{ expression }}&lt;/code&gt; and &lt;code&gt;{{% code block }}&lt;/code&gt; syntax and uses raw C# code here are is a short overview.&lt;/p&gt;
&lt;h4 id="expanding-expressions-and-code-blocks"&gt;Expanding Expressions and Code Blocks&lt;/h4&gt;
&lt;p&gt;There are really just two constructs that make up template logic:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Expressions&lt;/strong&gt; &lt;small&gt;&lt;em&gt;&lt;code&gt;{{ expr }}&lt;/code&gt;&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;i&amp;gt;{{ DateTime.Now.ToString(&amp;quot;d&amp;quot;) }}&amp;lt;/i&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Code Blocks&lt;/strong&gt; &lt;small&gt;&lt;em&gt;&lt;code&gt;{{% C# code }}&lt;/code&gt;&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{% for(int x; x&amp;lt;10; x++  { }}     
    &amp;lt;div&amp;gt;{{ x }}. Hello World&amp;lt;/div&amp;gt;
{{% } }}

{{%
     // declare variables
     var album = new Album() {
       Title = &amp;quot;Album Title&amp;quot;,
       Band = &amp;quot;Rocka Rolla&amp;quot;
     }
     
     var message = &amp;quot;Rock on Garth.&amp;quot;;
}}

&amp;lt;!--  use declared variables --&amp;gt;
&amp;lt;div&amp;gt;
{{ album.title }} by {{ album.band }}
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
	{{ message }}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also pass in a &lt;code&gt;model&lt;/code&gt; parameter to the various script execution methods, which then become available as &lt;code&gt;Model.&lt;/code&gt; in the script code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h5 id="--scripts-compile-to-class-methods"&gt;&lt;i class="fas fa-lightbulb" style="font-size: 1.1em"&gt;&lt;/i&gt;  Scripts compile to Class Methods&lt;/h5&gt;
&lt;p&gt;You can think of a template script as &lt;strong&gt;a method in a class&lt;/strong&gt; where the majority of the template's text is treated as &lt;strong&gt;literal text&lt;/strong&gt; that's written out with &lt;code&gt;.Write()&lt;/code&gt; method, with the &lt;strong&gt;embedded expressions and codeblocks&lt;/strong&gt; directly injected as raw C# method body code as is. This means variables declared are visible to expressions and code blocks below, along with the &lt;code&gt;Model.&lt;/code&gt; property if a model was passed. The whole thing is combined into a C# class wrapper, that is then compiled on the fly, and optionally cached and executed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One way to execute script templates in C# code with a passed in model looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;var model = new TestModel {Name = &amp;quot;rick&amp;quot;, DateTime = DateTime.Now.AddDays(-10)};

string script = @&amp;quot;
Hello World. Date is: {{ Model.DateTime.ToString(&amp;quot;&amp;quot;d&amp;quot;&amp;quot;) }}!
{{% for(int x=1; x&amp;lt;3; x++) 
{ }}
    {{ x }}. Hello World {{Model.Name}}
{{% } }}

And we're done with this!
&amp;quot;;

var scriptParser = new ScriptParser();

// add dependencies - sets on .ScriptEngine instance
scriptParser.AddAssembly(typeof(ScriptParserTests));
scriptParser.AddNamespace(&amp;quot;Westwind.Scripting.Test&amp;quot;);

// Execute the script
string result = scriptParser.ExecuteScript(script, model);

Console.WriteLine(result);

Console.WriteLine(scriptParser.ScriptEngine.GeneratedClassCodeWithLineNumbers);
Console.WriteLine(scriptParser.ErrorType);  // if there's an error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see in the script template string, the C# code expressions are just &lt;strong&gt;raw C# code wrapped into  Handlebars blocks&lt;/strong&gt;. Scripts are parsed into C# code, compiled at runtime into executable IL code, and then executed using standard .NET runtime execution. Output can be saved to assembly or executed immediately. Compiled code is automatically cached by default, so execution is very fast after the initial, one-time compilation step. If you re-run the same script or file template on the same script parser instance the cached assembly code is used.&lt;/p&gt;
&lt;p&gt;All of the latest C# features are automatically supported in &lt;strong&gt;embedded expressions&lt;/strong&gt; and &lt;strong&gt;code blocks&lt;/strong&gt; matching the latest version support of the dependent Roslyn compiler assembly.&lt;/p&gt;
&lt;h3 id="adding-layout-pages-and-sections"&gt;Adding Layout Pages and Sections&lt;/h3&gt;
&lt;p&gt;Basic script operation is useful, but often you need to componentize templates into re-usable sub-pages or master pages that simplify more complex site based setups like a documentation project.&lt;/p&gt;
&lt;p&gt;To that effect, the initial implementation of &lt;code&gt;ScriptParser&lt;/code&gt; until recently only supported &lt;code&gt;{{ Script.RenderPartial(&amp;quot;./partial.html&amp;quot;) }}&lt;/code&gt; to pull in external templates. This allows pulling in of external code or files and executing them separately. While that works, use of &lt;code&gt;Script.RenderPartial()&lt;/code&gt; can get very repetitive in larger projects if you need to simulate many pages that have similar high level layout and common document sections.&lt;/p&gt;
&lt;p&gt;To that effect, the latest release of &lt;code&gt;ScriptParser&lt;/code&gt; adds support for Layout Pages and Sections. Layout pages allow you to create a 'Master' page that is referenced from a 'Content' page. You can then have many Content pages that reference the same Master page to provide common UI Chrome that is common to all of those pages. The most common use case when creating a Web site/project, the common UI Chrome holds the main site layout (main panels, headers, footers, sidebars, logins etc.), but it could also be document headers for text generation for reports for example, or class wrappers for method code generations for example. Layouts can be super useful for a variety of use cases.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/ContentAndLayoutPages.png" alt="Content And Layout Pages"&gt;
&lt;small&gt;&lt;strong&gt;Figure 1&lt;/strong&gt; - Many Content pages request a shared Layout page into which content is loaded&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Layout pages work only with files on disk so you point at a base &lt;code&gt;basePath&lt;/code&gt; location for the template files. Based on that related files like Layout files and partials can be located based on relative or site root paths.&lt;/p&gt;
&lt;p&gt;The process works like this.&lt;/p&gt;
&lt;p&gt;Start by setting up and calling the script parser and calling &lt;code&gt;ExecuteScriptFile()&lt;/code&gt; (or &lt;code&gt;ExecuteScriptFileAsync()&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;[TestMethod]
public void LayoutFileScriptTest()
{
    var scriptParser = new ScriptParser();
    
    // auto-encode {{ expr }}
    scriptParser.ScriptingDelimiters.HtmlEncodeExpressionsByDefault = true; 

    var result = scriptParser.ExecuteScriptFile(&amp;quot;website/Views/Detail.html&amp;quot;,
                        new TestModel { Name = &amp;quot;Rick&amp;quot; },
                        basePath: &amp;quot;website/Views/&amp;quot;);

    Console.WriteLine(result);
    Console.WriteLine(scriptParser.ScriptEngine.GeneratedClassCodeWithLineNumbers);

    Assert.IsNotNull(result, scriptParser.ErrorMessage);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next you specify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Template Script file to 'execute'&lt;/li&gt;
&lt;li&gt;Provide a model to pass in (exposed a &lt;code&gt;Model&lt;/code&gt; in the script)&lt;/li&gt;
&lt;li&gt;Provide a root path for the templates&lt;br&gt;
&lt;small&gt;This allows paths that start with &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;~/&lt;/code&gt; to find related scripts. Defaults the template's path so if everything is in the same path you don't need to pass this.&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;It's important that &lt;code&gt;basePath&lt;/code&gt; resolves only for &lt;code&gt;Layout&lt;/code&gt; and &lt;code&gt;RenderPartial()&lt;/code&gt;. It doesn't automagically resolve other document Urls. We'll discuss how that can be handled towards the end of this post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next you'll have the &lt;code&gt;Detail.html&lt;/code&gt; content page that is loaded from &lt;code&gt;ExecuteScriptFile()&lt;/code&gt; call:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{ Script.Section(&amp;quot;StartDocument&amp;quot;) }}
{{%
    // Script.Layout page is PARSED out of the document not executed
    // it supports  bracketed expressions from the script Model optionally.
    // Paths resolve as: Absolute, Relative to Script, ~ Base Path Relative or Base Path Relative without ~
    Script.Layout = &amp;quot;Layout.html&amp;quot;;

    // this is explicitly projected into the Layout page
    // A better approach is to make anything you need in the Layout page 
    // part of the model, but for hard overrides this works.
    string title = &amp;quot;My Great Detail with Layout Page&amp;quot;;
    Script.Title = title;
}}
{{ Script.EndSection(&amp;quot;StartDocument&amp;quot;) }}
&amp;lt;div&amp;gt;
    &amp;lt;!-- dynamic type cast from an Anonymous type: THIS FAILS! --&amp;gt;
    &amp;lt;h1&amp;gt;Welcome back, {{ Model.Name }}&amp;lt;/h1&amp;gt;

    {{ Script.RenderPartial(&amp;quot;Partial.html&amp;quot;, Model) }}

    &amp;lt;p&amp;gt;This is the detail page content {{ DateTime.Now.ToString(&amp;quot;t&amp;quot;) }}&amp;lt;/p&amp;gt;

    {{% 
        for(var i = 1; i &amp;lt;= 5; i++) {  
        // any C# code
        Model.Name = &amp;quot;Name &amp;quot; + i;
    }}       
    {{% } }}

    &amp;lt;h3&amp;gt;Inline Methods&amp;lt;/h3&amp;gt;
    {{ Add(8,10)}}        
    {{%
        // Example of an inline function
        int Add(int a, int b)
        {
           return a + b;
        }
        writer.WriteLine(Add(5, 10));        
    }} 
  
    {{%
        var text = &amp;quot;This is &amp;amp; text requires \&amp;quot;escaping\&amp;quot;.&amp;quot;;
    }}

    Encoded:  
    {{: text }}

    Unencoded: 
    {{! text }}

    default (depends on ScriptDelimiters.HtmlEncodeExpressionsByDefault):
    {{ text }}

    {{%
        // write from within code blocks
        writer.WriteLine(&amp;quot;Hello world, &amp;quot; + Model.Name);  // unencoded
        
        // write with HtmlEncoding
        writer.WriteHtmlEncoded( $&amp;quot;this text is basic {Model.Name}, but \&amp;quot;encoded\&amp;quot;.\n&amp;quot; );
    }}
&amp;lt;/div&amp;gt;

{{ Script.Section(&amp;quot;Headers&amp;quot;) }}
&amp;lt;style&amp;gt;
    body {
        background-color: lightblue;
    }
    &amp;lt;script&amp;gt; 
        var viewMode = { Name:&amp;quot;{{ Model.Name }}&amp;quot; };
    &amp;lt;/script&amp;gt;
&amp;lt;/style&amp;gt;
{{ Script.EndSection(&amp;quot;Headers&amp;quot;) }}

{{ Script.Section(&amp;quot;Scripts&amp;quot;) }}
&amp;lt;script src=&amp;quot;https://code.jquery.com/jquery-3.5.1.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
{{ Script.EndSection(&amp;quot;Scripts&amp;quot;) }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This Html page is the entry point, but it's not what starts rendering the document since it references a &lt;code&gt;Script.Layout = &amp;quot;Layout.html&amp;quot;;&lt;/code&gt;  I could also have used &lt;code&gt;&amp;quot;/Laytout.html&amp;quot;&lt;/code&gt;  since we declared a base path in the C# code which points back to current folder.&lt;/p&gt;
&lt;p&gt;The rest of the page then uses the passed in &lt;code&gt;Model&lt;/code&gt; and shows a few examples of using expressions and script blocks.&lt;/p&gt;
&lt;p&gt;Also notice that there are several Sections like this one:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{ Script.Section(&amp;quot;Headers&amp;quot;) }}
&amp;lt;style&amp;gt;
    body {
        background-color: lightblue;
    }
    &amp;lt;script&amp;gt; 
        var viewMode = { Name:&amp;quot;{{ Model.Name }}&amp;quot; };
    &amp;lt;/script&amp;gt;
&amp;lt;/style&amp;gt;
{{ Script.EndSection(&amp;quot;Headers&amp;quot;) }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This syntax refers to a Section defined in the Layout page into which the section's content is embedded.&lt;/p&gt;
&lt;p&gt;To keep things simple, the Layout Page in this test scenario here is not very practical and simply shows a header, but it shows the different areas that are being filled in. In real application, this page likely would contain a top level Html layout with headers and footers, sidebars etc.&lt;/p&gt;
&lt;p&gt;The key item below is the &lt;code&gt;{{ Script.RenderContent() }}&lt;/code&gt; block that pulls in the content page:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{ Script.RenderSection(&amp;quot;StartDocument&amp;quot;) }}
&amp;lt;!DOCTYPE html&amp;gt;

&amp;lt;html lang=&amp;quot;en&amp;quot; xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;{{ Model.Name }}&amp;lt;/title&amp;gt;
   
        {{ Script.RenderSection(&amp;quot;Headers&amp;quot;) }}
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;header&amp;gt;This is a Site header&amp;lt;/header&amp;gt;
        
        &amp;lt;!-- Merges in the Content Page's script before parsing and compiling --&amp;gt;
        {{ Script.RenderContent() }}

        &amp;lt;footer&amp;gt;
            &amp;lt;hr/&amp;gt;
            Site Footer here &amp;amp;copy; {{ DateTime.Now.Year}}
        &amp;lt;/footer&amp;gt;


        {{ Script.RenderSection(&amp;quot;Scripts&amp;quot;) }}
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;h5 id="--content-and-layout-merge-into-a-single-class-method"&gt;&lt;i class="fas fa-lightbulb" style="font-size: 1.1em"&gt;&lt;/i&gt;  Content and Layout Merge into a Single Class Method&lt;/h5&gt;
&lt;p&gt;Layout and Content page using &lt;code&gt;ScriptParser&lt;/code&gt; are combined into a single block of generated code at parse time. The Layout page renders first and merges the content from the Content page at the  &lt;code&gt;{{ Script RenderContent() }}&lt;/code&gt; block. The combined content is then parsed compiled and executed.&lt;/p&gt;
&lt;p&gt;This means that any change to the Layout page requires rebuilding all the content pages that use that Layout page.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Notice also the sections that are &lt;strong&gt;declared in the Layout page&lt;/strong&gt; and &lt;strong&gt;implemented in the Content page&lt;/strong&gt;. Sections are pulled from the Content page and injected into the generated code at the defined section boundaries so they execute in order based on the location of the section definition in the layout page.&lt;/p&gt;
&lt;p&gt;Here's another look at Content, Layout and Partials in single view in relation to each other.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/ContentLayoutSectionsPartials.png" alt="Content Layout Sections and Partials"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 2&lt;/strong&gt; - A test example that demonstrates Content, Partials, Layout and Sections in templates using Handlebars syntax. All files are .html files to facilitate editor syntax support.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;I've needed this functionality for some time on several projects and finally after building it had the chance to put it into a production system that replaced the previous incomplete Razor engine that was holding up the project. The results in this documentation solution worked out great with fast performance and lots of flexibility for custom rendering and providing for customized rendering by combining both the Template Scripting and the underlying C# code generation to customize the exposed script API surface in scripts.&lt;/p&gt;
&lt;p&gt;More on this later in part 2.&lt;/p&gt;
&lt;h3 id="eating-my-own-dogfood"&gt;Eating my own Dogfood&lt;/h3&gt;
&lt;p&gt;So let me give a few examples how I've been using the Template Scripting features in various ways both with and without the new Layout Page features. Templating is not a common application scenario, but when you need it, you really need it and there are not a ton of options.&lt;/p&gt;
&lt;p&gt;Here are a few scenarios where I'm using this templating engine now in production:&lt;/p&gt;
&lt;h4 id="markdown-monster-snippet-engine"&gt;Markdown Monster Snippet Engine&lt;/h4&gt;
&lt;p&gt;Markdown Monster has a dynamic execution feature in the &lt;a href="https://markdownmonster.west-wind.com/docs/Embedding-Links-Images-Tables-and-More/Embedding-Code-Snippets.html"&gt;Markdown Monster for the Snippet Template Expansions&lt;/a&gt; and &lt;a href="https://github.com/RickStrahl/Commander-MarkdownMonster-Addin/blob/master/readme.md"&gt;Commander Scripting Addin&lt;/a&gt; both of which allow Markdown Monster's functionality with dynamic code. &lt;strong&gt;Snippets&lt;/strong&gt; uses dynamic text expansions in the markdown text editor, and &lt;strong&gt;Commander&lt;/strong&gt; allows for application automation. The Snippets expansion uses the &lt;code&gt;ScriptParser&lt;/code&gt; template expansion to mix text with C# expressions and code to produce embeddable text. The latter use pure code execution to perform automation tasks and has built-in support for text generation through the scripting engine.&lt;/p&gt;
&lt;p&gt;Here's an example of the Snippets Addin in Markdown Monster, running user provided snippets:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2022/Moving-old-Dynamic-Compilation-Code-to-use-Roslyn/SnippetExpansion.gif" alt=""&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 3&lt;/strong&gt; - Snippet Expansion in Markdown Monster uses template expansions with C# code&lt;/small&gt;&lt;/p&gt;
&lt;h4 id="documentation-monster-topic-generation"&gt;Documentation Monster Topic Generation&lt;/h4&gt;
&lt;p&gt;I also use the ScriptParser to generate output for my documentation solution &lt;a href="https://documentationmonster.com"&gt;Documentation Monster&lt;/a&gt; which creates a self-contained Web site for documentation (Examples: &lt;a href="https://markdownmonster.west-wind.com/docs"&gt;here (product)&lt;/a&gt; and &lt;a href="https://websurge.west-wind.com/docs/"&gt;here (product)&lt;/a&gt; and &lt;a href="https://docs.west-wind.com/westwind.utilities/"&gt;here (generated class reference)&lt;/a&gt;). Html is generated from Templates for live previews in real time as you type and for final bulk output generation of the entire Documentation project. The process is blazing fast:  2.5k+ Html topics generated in ~10 seconds in one project.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/DocumentationMonsterPreviewAndSiteGeneration.png" alt="Documentation Monster Preview And Site Generation"&gt;
&lt;small&gt;&lt;strong&gt;Figure 4&lt;/strong&gt; - Documentation Monster renders topics in the previewer and eventually in a fully generated Web site of Html pages using various topic templates merged with topic content&lt;/small&gt;&lt;/p&gt;
&lt;h4 id="bulk-e-mail-generation-for-notifications"&gt;Bulk E-Mail Generation for Notifications&lt;/h4&gt;
&lt;p&gt;I also use the script engine for creating a simple mail merge engine that runs against my customer and order database. I create mailing documents with embedded data that comes from various internal databases driven through a set of business objects. Code snippets at the top of templates drive the controller code, and the template expressions do the view rendering. In this case it's a background service console app that gets called every few days to check for renewals and sends out various generated and mail merged notifications on a regular schedule.&lt;/p&gt;
&lt;h4 id="code-generation"&gt;Code Generation&lt;/h4&gt;
&lt;p&gt;Recently I also upgraded an ancient legacy application that uses a SQL to entity generator tool for creating custom entities based on various structure code templates, that merge together multiple templates into code files. The old T3 templating was getting to be a pain in the ass to use and maintain so I switched out to a small console app using ScriptParser with mostly string based processing. The end result was much cleaner code that was more flexible as we could mix application logic with the template generation bits.&lt;/p&gt;
&lt;p&gt;In short, this templating engine has proven very useful to me for a whole host of uses cases.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="a-look-under-the-hood-of-westwindscripting-and-scriptparser"&gt;A look under the Hood of Westwind.Scripting and ScriptParser&lt;/h2&gt;
&lt;p&gt;We already looked at template rendering but let's look a little deeper at what gets generated and run, and at configuration and things you have to provide in order for code to compile.&lt;/p&gt;
&lt;p&gt;So again, the simplest Script Rendering you can do is the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;string script = @&amp;quot;
Hello World. Date is: {{ DateTime.Now.ToString(&amp;quot;&amp;quot;d&amp;quot;&amp;quot;) }}!

{{% for(int x=1; x&amp;lt;3; x++) { }}
{{ x }}. Hello World
{{% } }}

DONE!
&amp;quot;;
var scriptParser = new ScriptParser();
var result = scriptParser.ExecuteScript(script, null);

// merged content as a string
Console.WriteLine(result);  

// Generated Class Code with Line Numbers
Console.WriteLine(scriptParser.ScriptEngine.GeneratedClassCodeWithLineNumbers);

// Error Information is available on the .ScriptEngine property
// which captures compilation and runtime errors
// ErrorType, ErrorMessage, ErrorException are available
Assert.IsNotNull(result, scriptParser.ScriptEngine.ErrorMessage);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output from this is:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;Hello World. Date is: 3/31/2025!

1. Hello World
2. Hello World

DONE!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty straight forward and simple. In this case I'm not passing in a model, so the code just uses base C# and library code -  it's using core .NET runtime features so there are no extra assemblies that need to be loaded.&lt;/p&gt;
&lt;p&gt;By default the compiler and parser load a good chunk of common runtime libraries which works for core language features, but if you need additional types - like a model or runtime features not included - &lt;strong&gt;you have to explicitly reference them&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here I &lt;strong&gt;have to&lt;/strong&gt; add the model's type because the compiler doesn't know about it as it's contained in an external library (&lt;code&gt;Westwind.Utilities&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var model = new TestModel { Name = &amp;quot;Rick&amp;quot;, Date = DateTime.Now.AddDays(-10) };

string script = &amp;quot;&amp;quot;&amp;quot;
{{%
using Westwind.Utilities;
}}
Hello {{ Model.Name }}.
Date is: {{ Model.Date.ToString(&amp;quot;d&amp;quot;) }}!

{{% for(int x=1; x&amp;lt;4; x++) { }}
{{ x }}. {{ StringUtils.Replicate(&amp;quot;Hello World &amp;quot;,x) }}
{{% } }}

And we're done with this!
&amp;quot;&amp;quot;&amp;quot;;

var scriptParser = new ScriptParser();

// Referenced types and required assemblies
scriptParser.AddAssembly(typeof(Westwind.Utilities.StringUtils));  // include a library
scriptParser.AddAssembly(typeof(TestModel));                       // include model/executing assembly

// Execute and pass the model
string result = scriptParser.ExecuteScript(script, model);

Console.WriteLine(result);

Console.WriteLine(scriptParser.ScriptEngine.GeneratedClassCodeWithLineNumbers);
Assert.IsNotNull(result, scriptParser.ScriptEngine.ErrorMessage);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;.AddAssembly()&lt;/code&gt; calls. The relevant assemblies have to be present in the current execution path of the current (host) environment.&lt;/p&gt;
&lt;p&gt;This template produces the following text:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;Hello Rick.
Date: 2/5/2026!

1. Hello World 
2. Hello World Hello World 
3. Hello World Hello World Hello World 

And we're done with this!
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;There is also &lt;code&gt;ExecuteScriptAsync()&lt;/code&gt; which allows for executing &lt;code&gt;async&lt;/code&gt; code to execute in the script. Both &lt;code&gt;ExecuteScript()&lt;/code&gt; and &lt;code&gt;ExecuteScriptAsync()&lt;/code&gt; have several overloads that deal with how the model or parameters are handled.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this example the model is passed in generically and parsed as a &lt;code&gt;dynamic&lt;/code&gt; value that is passed to the template code. If you'd rather pass the model using a fixed type you can use the &lt;code&gt;ExecuteScript&amp;lt;TModelType&amp;gt;()&lt;/code&gt; overload. If you know the type at execution time, the generic version is preferred as it gets around weird edge cases where dynamic might misinterpret types or null values, plus it's slightly faster.&lt;/p&gt;
&lt;p&gt;If you look at the template code you see the &lt;code&gt;{{ expression }}&lt;/code&gt; and &lt;code&gt;{{% codeBlock }}&lt;/code&gt; tags.&lt;/p&gt;
&lt;p&gt;To make it a little clearer what happens here's the for loop with some indentation and comments:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{% 
	for(int x=1; x&amp;lt;3; x++) 
	{
	    var output = x + &amp;quot;. Hello World&amp;quot;;
}}  
	&amp;lt;!-- End of top code block --&amp;gt;
	
	&amp;lt;!-- literal text with expression expansions --&amp;gt;
	&amp;lt;div&amp;gt;{{ output }}&amp;lt;/div&amp;gt;
	
{{% 
    // You can add additional code here before closing tag
    Console.WriteLine(output);  
} }}
&amp;lt;!-- End of for loop --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expressions expand inline and simply replace the expression value for the tag. They are expanded as string values - by default rendering using &lt;code&gt;ToString()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Code blocks are &lt;strong&gt;raw snippets C# code&lt;/strong&gt; and they expand &lt;strong&gt;as-is&lt;/strong&gt; into the generated code. They also strip off the linefeed at the end of the code block so generated text doesn't end up with extra line breaks.&lt;/p&gt;
&lt;p&gt;Code Blocks can be single line blocks as shown in the first example, or multi-line code blocks that combine multiple operations as shown in the second example. When creating structured blocks (ie. &lt;code&gt;if&lt;/code&gt; or &lt;code&gt;for&lt;/code&gt; statements) with markup text between the start and end statements you need two code blocks for each part separated by additional markup in between. The markup in between can contain additional literal text, or nested expressions and code blocks.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bottom line: Expressions and Code blocks are simply embedded as-is into the generated code with any text between embedded the markup writing out string literals and generate into raw C# code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To give you an idea of a more real world example of a template here's one of my Documentation Monster topic templates in VS Code:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/TemplateExample.png" alt="Template Example"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 5&lt;/strong&gt; - A real world example of a template that renders a documentation topic for a Class Header. &lt;/small&gt;&lt;/p&gt;
&lt;p&gt;You can see there's a mixture of expressions and code blocks and nested code blocks. Generally the template inline code is kept very simple. While it's possible to write complex C# logic in the template, personally I prefer code helpers for complex operations. For example, generating a Child Topics List as a list, or creating a list of members for a Class Header is much better done in code than an enormouse code block inline of the template. You can do that if you want to, but it's preferred to externalize complex logic to helpers on the model. The advantage is that you get to write your code in your development IDE with all the editor tooling and completions, and you can independently test the logic. Just as importantly: You can re-use the functionality in multiple templates.&lt;/p&gt;
&lt;p&gt;Here's what this rendered template &lt;strong&gt;ClassHeader&lt;/strong&gt; looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/TemplateExampleRendered.png" alt="Template Example Rendered"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 6&lt;/strong&gt; - The rendered template has complex page elements that are rendered in external library code rather than embedding complex code into the template&lt;/small&gt;&lt;/p&gt;
&lt;h3 id="references-and-namespaces"&gt;References and Namespaces&lt;/h3&gt;
&lt;p&gt;In the first examples,  I have to explicitly add references to the model type and utility library.&lt;/p&gt;
&lt;p&gt;Because code gets compiled, the compiler needs to know &lt;strong&gt;all&lt;/strong&gt;  references that are used in the code you are executing. The current application's environment and loaded assemblies are not used by the compiler so &lt;strong&gt;everything&lt;/strong&gt; has to be explicitly declared.&lt;/p&gt;
&lt;p&gt;The script engine by default adds commonly used .NET Core base assemblies so it works with common base .NET base libraries. But if you use less common libraries or reference your own support assemblies and types including model types, you'll have to explicitly add those assemblies and also the namespaces - either in code or via the template. Regardless of whether they might already be loaded in your application as the compiler doesn't know about any of that.&lt;/p&gt;
&lt;p&gt;.NET Core made this more complex due to its highly granular assembly usage. To help make it easier to add references there are a few high level helpers methods in the C# Execution engine and Script Parser:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Common .NET Runtime base libraries (default)&lt;br&gt;
&lt;code&gt;AddCommonDefaultReferences()&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Common plus forward all loaded assemblies from the host application&lt;br&gt;
&lt;code&gt;AddLoadedReferences()&lt;/code&gt;.&lt;br&gt;
&lt;small&gt;&lt;em&gt;Note that assemblies have to be actually loaded at the time of compilation to be picked up, not just referenced by the application!&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicitly add assemblies from types or from disk&lt;br&gt;
&lt;code&gt;AddAssembly()&lt;/code&gt; &lt;code&gt;AddAssemblies()&lt;/code&gt;&lt;br&gt;
&lt;small&gt;&lt;em&gt;Explicitly adding assemblies is usually easiest with typeof() unless the type is not used by the host:&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;  scriptParser.AddAssembly(typeof(ScriptParserTests));  // from type
&lt;/code&gt;&lt;/pre&gt;
  
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add all assemblies from a &lt;strong&gt;.NET Runtime Meta Reference Library&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;AddAssemblies()&lt;/code&gt;&lt;br&gt;
&lt;small&gt;&lt;em&gt;This guarantees that all runtime libraries are loaded, but these meta libraries are large and add several megabytes of disk footprint to your application.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's how you add references and namespaces so the compiler can resolve your script code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var scriptParser = new ScriptParser();

// Explicit references
scriptParser.AddAssembly(typeof(ScriptParserTests));  // from type
scriptParser.AddAssembly(&amp;quot;./Westwind.Utilities.dll&amp;quot;); // from file

// Loaded References from Host (current assembly context)  
// be careful here: Not all referenced assemblies may be loaded when you call this
script.ScriptEngine.AddLoadedReferences();               

// If you add a Meta Data Reference Assembly to your project
scriptParser.AddAssemblies(metaAssemblies: Basic.Reference.Assemblies.Net100.References.All.ToArray());

// Add namespaces your code needs explicitly            
scriptParser.AddNamespace(&amp;quot;Westwind.Scripting.Test&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Namespaces can also be added in code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;string script = &amp;quot;&amp;quot;&amp;quot;
{{%
using Westwind.Utilities;
}}
Hello {{ Model.Name }}.
Date: {{ Model.Date.ToString(&amp;quot;d&amp;quot;) }}!

{{% for(int x=1; x&amp;lt;4; x++) { }}
{{ x }}. {{ StringUtils.Replicate(&amp;quot;Hello World &amp;quot;,x) }}
{{% } }}

And we're done with this!
&amp;quot;&amp;quot;&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Assembly loading and explicit namespace requirements can be one of the more tricky things to get right when using the compilation and templating tools especially if you are integrating with a complex application and are exposing a broad application model to templates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="take-a-look-at-generated-code---no-really"&gt;Take a look at Generated Code - no really!&lt;/h3&gt;
&lt;p&gt;The easiest way to understand how Literals, Expressions and Code Blocks work in templates is to look at the generated code from the script template above. The generated class source code can be captured via &lt;code&gt;script.ScriptEngine.GeneratedClassCode&lt;/code&gt; or &lt;code&gt;.GeneratedClassCodeWithLineNumbers&lt;/code&gt; which shows the entire generated class:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt; 1. using System;
 2. using System.Text;
 3. using System.Reflection;
 4. using System.IO;
 5. using System.Net;
 6. using System.Net.Http;
 7. using System.Collections;
 8. using System.Collections.Generic;
 9. using System.Collections.Concurrent;
1. using System.Text.RegularExpressions;
2. using System.Threading.Tasks;
3. using System.Linq;
4. using Westwind.Scripting;
5. using Westwind.Utilities;
6. 
7. namespace __ScriptExecution {
8. 
9. public class _yzf2irai
10. { 
11. 
12. 
13. public System.String ExecuteCode(Westwind.Scripting.Test.TestModel Model)
14. {
15. ScriptHelper Script = new ScriptHelper() { BasePath = null };
16. 
17. 
18. using( var writer = new ScriptWriter())
19. {
20. 
21. // using Westwind.Utilities;
22. 
23. writer.Write(&amp;quot;Hello &amp;quot;);
24. writer.Write(  Model.Name  );
25. writer.Write(&amp;quot;.\r\nDate: &amp;quot;);
26. writer.Write(  Model.Date.ToString(&amp;quot;d&amp;quot;)  );
27. writer.Write(&amp;quot;!\r\n\r\n&amp;quot;);
28. for(int x=1; x&amp;lt;4; x++) { 
29. writer.Write(  x  );
30. writer.Write(&amp;quot;. &amp;quot;);
31. writer.Write(  StringUtils.Replicate(&amp;quot;Hello World &amp;quot;,x)  );
32. writer.Write(&amp;quot;\r\n&amp;quot;);
33. } 
34. writer.Write(&amp;quot;\r\nAnd we're done with this!&amp;quot;);
35. return writer.ToString();
36. 
37. } // using ScriptWriter
38. 
39. }
40. 
41. 
42. } 
43. }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see that actual template script, is broken down into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Class Header&lt;/strong&gt;&lt;br&gt;
This is basically a shell that wraps the code and includes all the added namespaces and class wrapper. This code is static and entirely generated by the &lt;code&gt;CSharpScriptExecution&lt;/code&gt; class. The only thing that changes there is typically the class name and namespace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Literal text&lt;/strong&gt;&lt;br&gt;
All non code text outside of the &lt;code&gt;{{ }}&lt;/code&gt; tags is treated as a literal. In the generated code literals are parsed and formatted and then written out as properly encoded C# string literals which is why you see line breaks and other encoded text.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expressions&lt;/strong&gt;&lt;br&gt;
Expressions are parsed and embedded - as-is - and written straight out as string values into &lt;code&gt;writer.Write()&lt;/code&gt;. Non-string values are auto-converted using &lt;code&gt;.ToString()&lt;/code&gt; on any non string value.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Blocks&lt;/strong&gt;&lt;br&gt;
Similar to expressions any code block code is written out as is but does not generate a &lt;code&gt;writer.Write()&lt;/code&gt; since it's literally raw code. There's some additional logic that cleans up line breaks after code blocks to avoid extra line breaks introduced by the code tags (as Razor maddeningly does).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From a coding perspective the hardest part about all of this is the literal text parsing - finding all the places and special cases where literals get too long and have to be broken up and require special encoding or formatting.&lt;/p&gt;
&lt;p&gt;There are also different tag prefixes that need to be parsed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;{{: expression }}&lt;/strong&gt; - Explicitly Html Encoded&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;{{! expression }}&lt;/strong&gt; - Always parsed as Raw Text - no encoding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that there's an option to automatically Html Encode all expression tags:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;scriptParser.ScriptingDelimiters.HtmlEncodeExpressionsByDefault = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which makes the &lt;code&gt;{{: expr }}&lt;/code&gt; syntax redundant but you may be required to use &lt;code&gt;{{! expr }}&lt;/code&gt; occasionally to get raw Html or text rendered. If you're generating Html you'll want this set to &lt;code&gt;true&lt;/code&gt;, but if you're generating text for code, scripts or other plain text output, you'll want to leave it at its &lt;code&gt;false&lt;/code&gt; default.&lt;/p&gt;
&lt;h3 id="caching"&gt;Caching&lt;/h3&gt;
&lt;p&gt;Each template 'executed' compiled into an assembly that is eventually executed. Many templates means also many assemblies loaded into the current process.&lt;/p&gt;
&lt;p&gt;The script engine by default caches generated template assemblies based on the generated code used as input. If you execute the same template multiple times, the previously cached assembly will be used instead, for much faster execution.&lt;/p&gt;
&lt;p&gt;This means the &lt;strong&gt;very first&lt;/strong&gt; time you run your template it might take a second or two as the Roslyn compiler is loaded for the first time. If you re-run the same template again it will be much, much faster. Compiling another template after the initial cold start will be significantly faster than the very first compile, but still slower than cached operation. Compilation even of moderately sized templates tends to be very quick, but ideally you'll want to compile once, and run many times to achieve best performance.&lt;/p&gt;
&lt;p&gt;In most application situations templates get reused frequently. For example, in Documentation Monster I have 15 different topic templates of which 8 are actively used. Some of my help files contain a couple of thousand topics. So essentially I compile 8 templates once as they are first accessed, but I may generate two thousand topics from these 8 cached assemblies. When a script or layout page changes, the cache busts and the templates that require it automatically recompile - once - and then are reused many times more to render the other topics that use the same template. Using cached compilation I can compile my nearly 3500 topic documentation site to disk in less than 15 seconds.&lt;/p&gt;
&lt;p&gt;Assembly caching uses a &lt;code&gt;static&lt;/code&gt; object collection. Assemblies are loaded into the host process and by default can't be unloaded, hence the static collection. However, if you're adventurous you can create and provide your own &lt;code&gt;AassemblyLoadContext&lt;/code&gt; to implement unload behavior on your own.&lt;/p&gt;
&lt;h3 id="error-handling"&gt;Error Handling&lt;/h3&gt;
&lt;p&gt;Error handling for templates is critical as you may run into compilation errors as you're creating your templates:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/TemplateErrorWithCode.png" alt="Template Compilation Error With Code"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 7&lt;/strong&gt; - A template compilation error shows code, so that you can cross reference back into your original template&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Errors are captured on the ScriptEngine's &lt;code&gt;ErrorMessage&lt;/code&gt; property. There are two &lt;code&gt;scriptParser.ScriptEngine.ErrorType&lt;/code&gt; values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compilation&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;strong&gt;compilation errors&lt;/strong&gt; occur in the generated code the &lt;code&gt;scriptParser.ScriptEngine.ErrorMessage&lt;/code&gt; has error information pointing at specific line numbers into the generated code as shown in &lt;strong&gt;Figure 99&lt;/strong&gt; above. This can be super useful for scripting solutions: In Markdown Monster's Command Scripting Addin the errors and generated code are displayed so you can glance at the generated code and glean where the error occurred - in there I adjust the line numbers so they match up with the user provided code. It ain't exactly Visual Studios Error Pane but it's pretty good for a generic template solution to pinpoint the line of &lt;strong&gt;generated&lt;/strong&gt; code where the error occurred that you can translate into whatever line that matches in your script.&lt;/p&gt;
&lt;p&gt;Runtime errors provide less specific error information - you generally just get the exception and message returned. For runtime errors you likely want to catch the exception around the template execution block and either display the error message or handle it in some generic way via a custom method that generates formatted error output. The exception is preserved and passed forward so you can decide what to do with it - it's essentially up to your application how to display the error. More on that in part 2.&lt;/p&gt;
&lt;h2 id="file-template-rendering-and-layout-and-sections"&gt;File Template Rendering and Layout and Sections&lt;/h2&gt;
&lt;p&gt;String rendering works for simple things, but if you're building more complex projects that involve multiple pages you're likely to use files to hold your templates. Unlike string based templates, file templates support Layout Pages and Sections.&lt;/p&gt;
&lt;p&gt;If you want to render templates from disk, the base features work the same as with templates except you use &lt;code&gt;ExecuteScriptFile()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you're calling a single self contained template it works exactly the same as &lt;code&gt;ExecuteScript()&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var scriptParser = new ScriptParser();
var result = scriptParser.ExecuteScriptFile(&amp;quot;website/Views/SelfContained.html&amp;quot;, 
             new TestModel { Name = &amp;quot;Rick&amp;quot; });

Console.WriteLine(result);
Console.WriteLine(scriptParser.ScriptEngine.GeneratedClassCodeWithLineNumbers);
Assert.IsNotNull(result, scriptParser.ErrorMessage);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The template in this case is a self-contained Html page:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{%
  Script.Title = &amp;quot;Self Contained Page&amp;quot;;
}}
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
    &amp;lt;title&amp;gt;{{ Script.Title }}&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;This is a self contained page!&amp;lt;/h1&amp;gt;

  &amp;lt;p&amp;gt;Hello, {{ Model.Name }}&amp;lt;/p&amp;gt;

  &amp;lt;p&amp;gt;This page is self contained and doesn't require any external resources to render.&amp;lt;/p&amp;gt;

  {{ DateTime.Now.ToString(&amp;quot;d&amp;quot;) }}
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a folder hierarchy for your templates you might need to explicitly specify a base path that acts as a root folder for the project. This is so that files can resolve the root folder as in &lt;code&gt;/path/subpath/file&lt;/code&gt; in code for things like a referenced Layout or Partial pages.&lt;/p&gt;
&lt;h3 id="layout-and-content-page-in-multipage-layouts"&gt;Layout and Content Page in MultiPage Layouts&lt;/h3&gt;
&lt;p&gt;But it gets more interesting if you are working with multi-page layouts where you have multiple pages that use a common theme layout. You can separate out Content pages and Layout pages the latter of which provide the site chrome that can potentially be customized through the model that is passed to it.&lt;/p&gt;
&lt;p&gt;In this scenario you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Content Page that holds the current Page Content&lt;/li&gt;
&lt;li&gt;A Layout Page that is referenced from the Content Page&lt;/li&gt;
&lt;li&gt;Partial Pages loaded from either Content or Layout Pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's start with the Content page. This page contains the relevant content that holds the current information to display. So in my documentation example, each topic renders as a Content page. The content page is the top level page that is referenced by the code that invokes the Template. The template then can optionally link a Layout page that provides the base layout which is then merged into the Content template as it's rendered and compiled.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var scriptParser = new ScriptParser();
scriptParser.ScriptingDelimiters.HtmlEncodeExpressionsByDefault = true;

var result = scriptParser.ExecuteScriptFile(&amp;quot;website/Views/Detail.html&amp;quot;,
                    new TestModel { Name = &amp;quot;Rick&amp;quot; },
                    basePath: &amp;quot;website/Views/&amp;quot;);

Console.WriteLine(result);
Console.WriteLine(scriptParser.ScriptEngine.GeneratedClassCodeWithLineNumbers);

Assert.IsNotNull(result, scriptParser.ErrorMessage);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's the &lt;code&gt;Detail.html&lt;/code&gt; content page - notice the &lt;code&gt;Script.Layout  = &amp;quot;detail.html&amp;quot;&lt;/code&gt; at the top that links in the Layout page. The page demonstrates a lot of the different features of the templates all of which should look familiar if you've used Razor/Blazor or ASP.NET Pages.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{%
    // This code will execute at the top of the Layout page
    Script.Layout = &amp;quot;Layout.html&amp;quot;;
}}
&amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Welcome back, {{ Model.Name }}&amp;lt;/h1&amp;gt;

    
    {{ Script.RenderPartial(&amp;quot;Partial.html&amp;quot;, Model) }}

    &amp;lt;p&amp;gt;This is the detail page content {{ DateTime.Now.ToString(&amp;quot;t&amp;quot;) }}&amp;lt;/p&amp;gt;

    {{% 
        string display = string.Empty;
        for(var i = 1; i &amp;lt;= 5; i++) {  
            // any C# code
            display = &amp;quot;Name &amp;quot; + i;
    }}
    {{ i }}. {{ display }}
    {{% } }}

    &amp;lt;h3&amp;gt;Inline Methods&amp;lt;/h3&amp;gt;
    {{ Add(8,10)}}        
    {{%
        // Example of an inline function
        int Add(int a, int b)
        {
           return a + b;
        }
        writer.WriteLine(Add(5, 10));        
    }} 
  
    {{%
        var text = &amp;quot;This is &amp;amp; text requires \&amp;quot;escaping\&amp;quot;.&amp;quot;;
    }}

    Encoded: {{: text }}
    Unencoded: {{! text }}
    Default: {{ text }}

    {{%
        // write from within code blocks
        writer.WriteLine(&amp;quot;Hello world, &amp;quot; + Model.Name);  // unencoded
        
        // write with HtmlEncoding
        writer.WriteHtmlEncoded( $&amp;quot;this text is basic {Model.Name}, but \&amp;quot;encoded\&amp;quot;.\n&amp;quot; );
    }}
&amp;lt;/div&amp;gt;

{{ Script.Section(&amp;quot;Headers&amp;quot;) }}
&amp;lt;style&amp;gt;
    body {
        background-color: lightblue;
    }
    &amp;lt;script&amp;gt; 
        var viewMode = { Name:&amp;quot;{{ Model.Name }}&amp;quot; };
    &amp;lt;/script&amp;gt;
&amp;lt;/style&amp;gt;
{{ Script.EndSection(&amp;quot;Headers&amp;quot;) }}

{{ Script.Section(&amp;quot;Scripts&amp;quot;) }}
&amp;lt;script src=&amp;quot;https://code.jquery.com/jquery-3.5.1.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
{{ Script.EndSection(&amp;quot;Scripts&amp;quot;) }} 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="layout-page"&gt;Layout Page&lt;/h3&gt;
&lt;p&gt;The Layout page is loaded via a code reference to &lt;code&gt;Script.Layout = &amp;quot;Layout.html&amp;quot;;&lt;/code&gt;. This code can appear &lt;strong&gt;anywhere&lt;/strong&gt; in code in the template. The page is referenced relative to the page it's called from - in this case in the same folder.&lt;/p&gt;
&lt;p&gt;The Layout Page and the Content page are merged into a single text document before the code is parsed into code. This means the Layout page is passed the same model that the main content page has access to. This usually means that content pages need to use a common base model that has the data for the Content page and the Layout page. That means things like Title, Project Name etc. company info for the footer etc. Essentially the model contains both data for the content page &lt;strong&gt;and&lt;/strong&gt; the layout page.&lt;/p&gt;
&lt;p&gt;The content page is the entry point that references the layout page, but the layout page renders first and the &lt;code&gt;{{ Script.RenderContent() }}&lt;/code&gt; section &lt;strong&gt;pulls the Content Page into the layout&lt;/strong&gt;. Both are combined into a single C# program that is then compiled and executed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Keep in mind that any code that runs in the Content Page &lt;strong&gt;runs after the top of the Layout page code has run&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;IOW, by the time the first code inside of the Content page is executed, the Html header has already been rendered and any code blocks and expressions in the top of the Layout page have already run. This means for example that you can't set the title after the fact from the content page. You can use the model to hold the values you want to set, or you can use Sections to force content from the Content Page into the layout page at designated Section locations.&lt;/p&gt;
&lt;p&gt;In the layout page you declare sections using &lt;code&gt;{{ Script.RenderSection(&amp;quot;StartDocument&amp;quot;) }}&lt;/code&gt;  which 'pulls' the content from the content page:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;!-- inject code here so it's available/set above Script.RenderContent() --&amp;gt;
{{ Script.RenderSection(&amp;quot;StartDocument&amp;quot;) }}
&amp;lt;!DOCTYPE html&amp;gt;

&amp;lt;html lang=&amp;quot;en&amp;quot; xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;{{ Model.Name }}&amp;lt;/title&amp;gt;
   
        {{ Script.RenderSection(&amp;quot;Headers&amp;quot;) }}
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;header&amp;gt;This is a Site header&amp;lt;/header&amp;gt;
        
        &amp;lt;!-- main content goes here --&amp;gt;
        {{ Script.RenderContent() }}

        &amp;lt;footer&amp;gt;
            &amp;lt;hr/&amp;gt;
            Site Footer here &amp;amp;copy; {{ DateTime.Now.Year}}
        &amp;lt;/footer&amp;gt;

        {{ Script.RenderSection(&amp;quot;Scripts&amp;quot;) }}
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="sections"&gt;Sections&lt;/h3&gt;
&lt;p&gt;You've seen sections referenced in the previous snippet in the Layout page.&lt;/p&gt;
&lt;p&gt;Sections are defined in a Layout page as a placeholder via &lt;code&gt;{{ Script.RenderSection(&amp;quot;SectionName&amp;quot;) }}&lt;/code&gt; and implemented in Content pages via &lt;code&gt;{{ Script.Section(&amp;quot;SectionName&amp;quot;) }}&lt;/code&gt; and &lt;code&gt;{{ Script.EndSection(&amp;quot;SectionName&amp;quot;) }}&lt;/code&gt;. Any content between the two - literal text, expressions and code blocks are then embedded in the Layout page at the section placeholder location.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layout Page&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;{{ Script.Title }}&amp;lt;/title&amp;gt;
    {{ Script.RenderSection(&amp;quot;Headers&amp;quot;) }}
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Content Page&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;{{ Script.Section(&amp;quot;Headers&amp;quot;) }}
&amp;lt;style&amp;gt;
    body {
        background-color: lightblue;
    }
    &amp;lt;script&amp;gt; 
        var viewMode = { Name:&amp;quot;{{ Model.Name }}&amp;quot; };
    &amp;lt;/script&amp;gt;
&amp;lt;/style&amp;gt;
{{ Script.EndSection(&amp;quot;Headers&amp;quot;) }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Layout page's &lt;code&gt;RenderSection()&lt;/code&gt; pulls the markup from between the Content Page's section and embeds it into the template during the parsing phase as part of the process of creating a single executable file from the content and layout page.&lt;/p&gt;
&lt;h3 id="partials"&gt;Partials&lt;/h3&gt;
&lt;p&gt;Unlike Layout and Content pages (and Sections) which merge into a single template before parsing, partials are rendered like a standalone page based on a file on disk. For Html rendering Partials are typically Html fragments for a page sub-component.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;h3&amp;gt;
    This is some Partial Content, {{ Model.Name }} 
    at {{ DateTime.Now.ToString(&amp;quot;HH:mm:ss&amp;quot;) }}
&amp;lt;/h3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Partials can be called from Content Pages, Layout Pages and other Partial pages using the following syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;hr /&amp;gt;
    {{ Script.RenderPartial(&amp;quot;Partial.html&amp;quot;, Model) }}
&amp;lt;hr /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that you pass a model to the partial explicitly - typically you'll just pass forward the existing content page model but you can also pass something completely different here if it makes sense.&lt;/p&gt;
&lt;p&gt;Using Layout pages and Sections is a key feature and I'm glad that functionality finally part of the &lt;strong&gt;ScriptParser&lt;/strong&gt; class. It's made a big difference for several projects that previously suffered with lots of &lt;code&gt;.RenderPartial()&lt;/code&gt; embeddings.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;This brings us to the end of part 1 of this series.&lt;/p&gt;
&lt;p&gt;In this post I've reviewed the core features of the Westwind.Scripting templating features and how you can use both string based and file template based scripting to quickly merge text with data using its simple Handlebars C# syntax and provided some insight how the template engine works behind the scenes.&lt;/p&gt;
&lt;p&gt;In Part 2, I'll expand on usage in a real-world scenario for Html output generation from a desktop application, which involves a few extra steps to ensure that content generated locally works in Web environments. It'll demonstrate how to pass in application level data, format paths, handle site base paths in Web scenarios and a few other issues like dealing with dependency references.&lt;/p&gt;
&lt;p&gt;Until then  give this library a try and see where it might fit for your use cases.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2"&gt;Part 2: Putting the Westwind.Scripting Templating Library to work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/Westwind.Scripting"&gt;Westwind.Scripting Library on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/Westwind.Scripting/blob/master/ScriptAndTemplates.md"&gt;Westwind.Scripting Templating Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-C-Code-Compilation-Revisited-for-Roslyn"&gt;Previous Post: Runtime Compilation with Roslyn and Building Westwind.Scripting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://documentationmonster.com"&gt;Documentation Monster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://markdownmonster.west-wind.com"&gt;Markdown Monster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <link>https://weblog.west-wind.com/posts/2026/Apr/01/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1</link>
      <guid isPermaLink="false">5311031</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Apr/01/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Apr/01/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1</guid>
      <pubDate>Wed, 01 Apr 2026 10:35:37 GMT</pubDate>
    </item>
    <item>
      <title>Using .NET Native AOT to build Windows WinAPI Dlls</title>
      <description>Did you know that you can use .NET AOT compilation to create native Windows DLLs to potentially replace traditional C/C++ compiled DLLs? It's now possible to build completely native DLLs that can be called from external applications and legacy applications in particular using .NET AOT  compilation. In this post I show how this works and discuss the hits and misses of this tech.</description>
      <link>https://weblog.west-wind.com/posts/2026/Mar/21/Using-NET-Native-AOT-to-build-Windows-WinAPI-Dlls</link>
      <guid isPermaLink="false">5321191</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Mar/21/Using-NET-Native-AOT-to-build-Windows-WinAPI-Dlls#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Mar/21/Using-NET-Native-AOT-to-build-Windows-WinAPI-Dlls</guid>
      <pubDate>Sat, 21 Mar 2026 11:47:13 GMT</pubDate>
    </item>
    <item>
      <title>Azure Trusted Signing Revisited with Dotnet Sign</title>
      <description>In this follow-up post to my previous guide on Azure Trusted Signing, I  explore how the new `dotnet sign` tool significantly simplifies the code signing process compared to the traditional `SignTool` workflow. The post identifies `dotnet sign` using `artifact-signing` as a faster, more efficient alternative.</description>
      <link>https://weblog.west-wind.com/posts/2026/Mar/02/Azure-Trusted-Signing-Revisited-with-Dotnet-Sign</link>
      <guid isPermaLink="false">5249291</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Mar/02/Azure-Trusted-Signing-Revisited-with-Dotnet-Sign#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Mar/02/Azure-Trusted-Signing-Revisited-with-Dotnet-Sign</guid>
      <pubDate>Mon, 02 Mar 2026 10:10:23 GMT</pubDate>
    </item>
    <item>
      <title>Don't use the Microsoft Timestamp Server for Signing</title>
      <description>The default Microsoft timestamp server frequently causes intermittent failures during the code-signing process, particularly when processing many files or large binaries as part of a application distribution. These reliability issues can be resolved by replacing the Microsoft timestamp server with a more stable, compatible third-party alternative.</description>
      <link>https://weblog.west-wind.com/posts/2026/Feb/26/Dont-use-the-Microsoft-Timestamp-Server-for-Signing</link>
      <guid isPermaLink="false">5239628</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Feb/26/Dont-use-the-Microsoft-Timestamp-Server-for-Signing#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Feb/26/Dont-use-the-Microsoft-Timestamp-Server-for-Signing</guid>
      <pubDate>Thu, 26 Feb 2026 12:41:03 GMT</pubDate>
    </item>
    <item>
      <title>Reliably Refreshing the WebView2 Control</title>
      <description>The WebView2 control lacks a direct `Reload(noCache)` overload that forces a browser hard reload of the current page. Instead content is loaded with a soft refresh that - hopefully - reloads the current page and its dependencies dependent on WebView environment and server cache policies. In this post we'll look at how to work around this limitation and force a hard refresh in several different ways.</description>
      <link>https://weblog.west-wind.com/posts/2026/Feb/04/Reliably-Refreshing-the-WebView2-Control</link>
      <guid isPermaLink="false">5204283</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Feb/04/Reliably-Refreshing-the-WebView2-Control#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Feb/04/Reliably-Refreshing-the-WebView2-Control</guid>
      <pubDate>Wed, 04 Feb 2026 21:53:22 GMT</pubDate>
    </item>
    <item>
      <title>What the heck is a `\\.\nul` path and why is it breaking my Directory Files Lookup?</title>
      <description>I've been trying to track down an odd bug in my logs related to Null device failures in directory lookups which have been causing application level exceptions. In some odd scenarios these null mappings are showing up for files. In this post I take a look at what they are and how to work around them for this very edge case scenario.</description>
      <link>https://weblog.west-wind.com/posts/2025/Dec/08/What-the-heck-is-a-nul-path-and-why-is-it-breaking-my-Directory-Files-Lookup</link>
      <guid isPermaLink="false">5133488</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2025/Dec/08/What-the-heck-is-a-nul-path-and-why-is-it-breaking-my-Directory-Files-Lookup#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2025/Dec/08/What-the-heck-is-a-nul-path-and-why-is-it-breaking-my-Directory-Files-Lookup</guid>
      <pubDate>Mon, 08 Dec 2025 13:15:18 GMT</pubDate>
    </item>
    <item>
      <title>Using the new WebView2 AllowHostInputProcessing Keyboard Mapping Feature</title>
      <description>If you've used the WebVIew2 control for a UI interactive Hybrid active you've probably run into some odd keyboard behavior, where some keys cannot be captured properly in the host application or are not forwarded quite the way they normally behave. In the newer releases of the WebView SDK there's a new option called `AllowHostInputProcessing` that allows for forwarding keyboard events more aggressively to the host which fixes some Windows behaviors and introduces some new issues. </description>
      <link>https://weblog.west-wind.com/posts/2025/Aug/20/Using-the-new-WebView2-AllowHostInputProcessing-Keyboard-Mapping-Feature</link>
      <guid isPermaLink="false">4979824</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2025/Aug/20/Using-the-new-WebView2-AllowHostInputProcessing-Keyboard-Mapping-Feature#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2025/Aug/20/Using-the-new-WebView2-AllowHostInputProcessing-Keyboard-Mapping-Feature</guid>
      <pubDate>Wed, 20 Aug 2025 17:17:34 GMT</pubDate>
    </item>
    <item>
      <title>Fighting through Setting up Microsoft Trusted Signing</title>
      <description>It's that time of year again to update my CodeSigning certificate, only to find out that the rules have changed since I last did this. Certs now require either a physical hardware key or a online service provides the non-exportable keys to sign binaries along with massive price increases for the privilege. So I decided to give Microsoft's new Trusted CodeSigning service a try, and while that entails all the joy of setting up Azure services, at the and of the day it works and is a considerably more economical way for CodeSigning to work. In this post I describe how to set this up hopefully to help you avoid some of the pain I went through.</description>
      <link>https://weblog.west-wind.com/posts/2025/Jul/20/Fighting-through-Setting-up-Microsoft-Trusted-Signing</link>
      <guid isPermaLink="false">4940804</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2025/Jul/20/Fighting-through-Setting-up-Microsoft-Trusted-Signing#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2025/Jul/20/Fighting-through-Setting-up-Microsoft-Trusted-Signing</guid>
      <pubDate>Sun, 20 Jul 2025 14:56:23 GMT</pubDate>
    </item>
  </channel>
</rss>