<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">
  <channel>
    <title>Rick Strahl's Web Log</title>
    <link>https://weblog.west-wind.com/</link>
    <description>Life, Surf, Code and everything in between</description>
    <generator>West Wind Web Log</generator>
    <language>en-us</language>
    <image>
      <url>http://www.west-wind.com/images/WebLogBannerLogo.jpg</url>
      <title>Rick Strahl's Web Log</title>
      <link>https://weblog.west-wind.com/</link>
    </image>
    <pubDate>Sat, 09 May 2026 02:30:42 GMT</pubDate>
    <lastBuildDate>Fri, 24 Apr 2026 02:04:43 GMT</lastBuildDate>
    <xhtml:meta content="noindex" name="robots" xmlns:xhtml="http://www.w3.org/1999/xhtml"/><item>
      <title>Putting the Westwind.Scripting C# Templating Library to work, Part 2</title>
      <link>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2</link>
      <guid isPermaLink="false">5314605_202604240204</guid>
      <pubDate>Fri, 24 Apr 2026 02:04:43 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=5314605</wfw:commentRss>
      <category>.NET</category>
      <category>C#</category>
      <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;a href="https://markdownmonster.west-wind.com?ut=weblog"  target="_blank"
     title="Markdown Monster - Easy to use, yet powerfully productive Markdown Editing for Windows"&gt;
&lt;img src="/images/sponsors/banner-example.png?v=1.2" class="da-content-image" /&gt;
&lt;/a&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;
&lt;div style='margin: 10px 0px'&gt;&lt;small&gt;© Rick Strahl, West Wind Technologies, 2005-2026&lt;/small&gt;&lt;/div&gt;&lt;div&gt;Posted in &lt;b&gt;&lt;a href='/ShowPosts.aspx?Category=.NET'&gt;.NET&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;a href='/ShowPosts.aspx?Category=C#'&gt;C#&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/div&gt;
&lt;/small&gt;</description>
    </item>
    <item>
      <title>Revisiting C# Scripting with the Westwind.Scripting Templating Library, Part 1</title>
      <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_202604012035</guid>
      <pubDate>Wed, 01 Apr 2026 20:35:37 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2026/Apr/01/Revisiting-C-Scripting-with-the-WestwindScripting-Templating-Library-Part-1#Comments</comments>
      <slash:comments>4</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=5311031</wfw:commentRss>
      <category>.NET</category>
      <category>C#</category>
      <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;a href="https://markdownmonster.west-wind.com?ut=weblog"  target="_blank"
     title="Markdown Monster - Easy to use, yet powerfully productive Markdown Editing for Windows"&gt;
&lt;img src="/images/sponsors/banner-example.png?v=1.2" class="da-content-image" /&gt;
&lt;/a&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;
&lt;div style='margin: 10px 0px'&gt;&lt;small&gt;© Rick Strahl, West Wind Technologies, 2005-2026&lt;/small&gt;&lt;/div&gt;&lt;div&gt;Posted in &lt;b&gt;&lt;a href='/ShowPosts.aspx?Category=.NET'&gt;.NET&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;a href='/ShowPosts.aspx?Category=C#'&gt;C#&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/div&gt;
&lt;/small&gt;</description>
    </item>
    <item>
      <title>Using .NET Native AOT to build Windows WinAPI Dlls</title>
      <link>https://weblog.west-wind.com/posts/2026/Mar/21/Using-NET-Native-AOT-to-build-Windows-WinAPI-Dlls</link>
      <guid isPermaLink="false">5321191_202603212147</guid>
      <pubDate>Sat, 21 Mar 2026 21:47:13 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2026/Mar/21/Using-NET-Native-AOT-to-build-Windows-WinAPI-Dlls#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=5321191</wfw:commentRss>
      <category>.NET</category>
      <category>C#</category>
      <category>Windows</category>
      <description>&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Using-NET-Native-AOT-to-build-Windows-WinAPI-Dlls/PostBanner.jpg" alt="Banner"&gt;&lt;/p&gt;
&lt;p&gt;Here's something that I didn't know you could do: You can create AOT compiled DLLs that compile down to platform specific native DLLs. For example, on Windows you can compile an AOT library to a Standard Calling Convention type DLL that you can be called from any tool that can access WinApi-style dlls. C-Style DLLs are also supported but in this post I look at StandardCall (WinAPI style) DLLs for Windows.&lt;/p&gt;
&lt;p&gt;I'm specifically looking at this for some Windows DLLs that I created eons ago using the Visual Studio C++ compiler to build the DLLs. Moving those into .NET and C# certainly would make a number of things easier including not having to install the Visual Studio C++ compiler and all its crazy Windows dependencies (C++ SDK, ATL etc.) and dealing with the constant version update hassles in Visual Studio when the compiler version revs and screws up dependencies.&lt;/p&gt;
&lt;p&gt;My specific use case involves some very old FoxPro x86 code on the calling end, so I'll use FoxPro for my calling examples here. But the same applies for any application that can call into DLLs using either WinAPI standard call or optionally standard C calling syntax.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://markdownmonster.west-wind.com?ut=weblog"  target="_blank"
     title="Markdown Monster - Easy to use, yet powerfully productive Markdown Editing for Windows"&gt;
&lt;img src="/images/sponsors/banner-example.png?v=1.2" class="da-content-image" /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="setting-up-an-aot-project-for-native-windows-dll-compilation"&gt;Setting up an AOT Project for native Windows DLL Compilation&lt;/h2&gt;
&lt;p&gt;So here's what you need to create an .NET AOT DLL for use as a Windows API (Standard Call) style DLL:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new &lt;strong&gt;Class Library&lt;/strong&gt; .NET 10+ project&lt;/li&gt;
&lt;li&gt;Set it up for AOT compilation&lt;/li&gt;
&lt;li&gt;Create a class with static methods with specific attributes to specify native Exports&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;dotnet publish&lt;/code&gt; to build the native DLL for each target platform&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's start with the project file. To build a DLL you'll use a standard Class Library project and then add a couple of explicit tags:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;Project Sdk=&amp;quot;Microsoft.NET.Sdk&amp;quot;&amp;gt;
	&amp;lt;PropertyGroup&amp;gt;
		&amp;lt;TargetFramework&amp;gt;net10.0&amp;lt;/TargetFramework&amp;gt;
		&amp;lt;ImplicitUsings&amp;gt;enable&amp;lt;/ImplicitUsings&amp;gt;
		
		&amp;lt;!-- These are the critical settings --&amp;gt;
		&amp;lt;PublishAot&amp;gt;true&amp;lt;/PublishAot&amp;gt;
		&amp;lt;NativeLib&amp;gt;Shared&amp;lt;/NativeLib&amp;gt;
		&amp;lt;AllowUnsafeBlocks&amp;gt;true&amp;lt;/AllowUnsafeBlocks&amp;gt;
	&amp;lt;/PropertyGroup&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PublishAot&lt;/code&gt; and &lt;code&gt;NativeLib&lt;/code&gt; are the keys for creating a DLL library. &lt;code&gt;AllowUnsafeBlocks&lt;/code&gt; is optional but likely needed if you do any sort of P/Invoke code which is likely if you end up pushing reference values in and out of the DLL.&lt;/p&gt;
&lt;p&gt;Next create a class and add the appropriate attributes to &lt;code&gt;static&lt;/code&gt; methods that make up the exports:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static class Exports
{    
    [UnmanagedCallersOnly(
        EntryPoint = &amp;quot;Add&amp;quot;,
        CallConvs = new[] { typeof(CallConvStdcall) })]
    public static int Add(int a, int b)
    {
        return a + b;
    }
}    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key bit here are the &lt;code&gt;[UnmanagedCallersOnly]&lt;/code&gt; attribute which marks the method for external access. For Windows Standard Call or 'WinApi' style DLLs you can specify the &lt;code&gt;CallConvStdcall&lt;/code&gt; type which is required in order to generate the DLL export, so the exported function becomes visible.&lt;/p&gt;
&lt;p&gt;Next you need to build the project. A plain &lt;code&gt;dotnet build&lt;/code&gt; just builds a .NET DLL not an AOT DLL. In order to create the native DLL you have to use &lt;code&gt;dotnet publish&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Open a terminal in the project folder and then use:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;dotnet publish -c Release -r win-x64 /p:PublishAot=true
dotnet publish -c Release -r win-x86 /p:PublishAot=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should publish for each of the platforms you want to target.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the &lt;code&gt;/p:PublishAot&lt;/code&gt; is technically not required since we specify it in the project file, but if you don't, this is how you can override the project value for AOT compilation explicitly at build time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To call the generated DLL externally - using FoxPro in my case - I can do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;lcDll = FullPath(&amp;quot;wwDotnetBridge_Loader.dll&amp;quot;)

DECLARE integer Add;
     in (lcDll) ;
     as DotnetAdd ;
     integer a, integer b 
     
? DotNetAdd(1,5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et voila: You now have a Windows compatible DLL that you can call from another application.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://weblog.west-wind.com/images/2026/Using-NET-Native-AOT-to-build-Windows-WinAPI-Dlls/ExplorerViewOfCompiledDll.png" alt="Explorer View Of Compiled Dll"&gt;&lt;/p&gt;
&lt;p&gt;The file is 850k - not exactly tiny but considering it's .NET code that's not bad. As you add .NET features though, the size of the DLL gets bigger and fast. Obviously the code above does very little and relies only on core language features and no library calls, so that's about as small as the DLL you can get. From here any dependencies used increase the size of the DLL. Any library you reference or call, AOT will pull out the code you are using (if it's compatible even) and include it in the AOT DLL. So the more diverse code you call - the bigger the library gets.&lt;/p&gt;
&lt;p&gt;For reference, for the entire samples I show in this post (which are all very minimal!) the size will end up at about 1.5mb.&lt;/p&gt;
&lt;h3 id="its-net-but-its-different"&gt;It's .NET but it's different&lt;/h3&gt;
&lt;p&gt;Although you're building your code with .NET, a Windows native DLL that is called using external non-.NET code has to behave more like a C/C++ method than what you're used to in a .NET interface. While you can easily pass primitive parameters like the integers I passed above for just about anything else you have to fall back to C style calling conventions. This means using pointers for strings and objects, ints for boolean values and so on. The only straight through values pretty much are number primitive types (int, long, double, single).&lt;/p&gt;
&lt;p&gt;This makes passing parameters in and out a lot more tedious especially in a client that doesn't have explicit support for structured fixed-sized types.&lt;/p&gt;
&lt;p&gt;Here's an example of passing strings in and out:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;[UnmanagedCallersOnly(
    EntryPoint = &amp;quot;StringInStringOut&amp;quot;,
    CallConvs = new[] { typeof(CallConvStdcall) })]
public static int StringInStringOut(IntPtr input, IntPtr output)
{
    // retrieve the input buffer        
    string inputStr = Marshal.PtrToStringAnsi(input) ?? string.Empty;


    // get the empty buffer for size
    string outputStr = Marshal.PtrToStringAnsi(output) ?? string.Empty;
    if (outputStr.Length &amp;lt; 1)
        return 0; // output buffer too small

    var result = inputStr + &amp;quot; !!!&amp;quot;; // &amp;quot;Echoing back your message:\r\n\r\n&amp;quot; + inputStr;

    WriteAnsiString(output, outputStr?.Length  ?? 0 , result);
    return outputStr.Length;  // success
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In typical Windows API style string output is passed in as a buffer address that is filled by the method. Input too comes in as a pointer that has to be dereferenced back into a string first.&lt;/p&gt;
&lt;p&gt;To call this from FoxPro looks like any other Windows API call and requires passing in a  pre-allocated buffer by reference for the return value:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;DECLARE integer StringInStringOut ;
     in (lcDll) ;
     string input, string@ output

lcOutput = SPACE(255)
? StringInStringOut(&amp;quot;Hello World. Time is: &amp;quot; + TIME(), @lcOutput)
? lcOutput
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's also possible to pass back a generated string, but then your calling code will be responsible for releasing the memory allocated for the string which is even more of a pain than passing in a pre-allocated buffer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;None of this is new or different than calling Windows APIs or most StdCall DLLs. While you can use .NET code to write your method logic, the call and return interface is still tightly bound to the very low level DLL calling standard and accordingly more verbose than simple parameter and result value passing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="objects-yes-but-no-thanks"&gt;Objects? Yes but no Thanks!&lt;/h2&gt;
&lt;p&gt;Object passing is even more painful especially when you work with a non-structured client like FoxPro that can't easily construct a fixed structure. For objects, the best way is to use Structures with fixed layouts that can be filled and read by the client.&lt;/p&gt;
&lt;p&gt;Here's an example of passing data in via a structure, updating it and passing it back:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PersonInfo
{
    public int Id;
    public double Amount;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string Name;
}

[UnmanagedCallersOnly(
    EntryPoint = &amp;quot;ProcessPerson&amp;quot;,
    CallConvs = new[] { typeof(CallConvStdcall) })]
public static int ProcessPerson(IntPtr personPtr)
{
    if (personPtr == IntPtr.Zero)
        return -1;

    // read the structure from the VFP buffer
    var person = Marshal.PtrToStructure&amp;lt;PersonInfo&amp;gt;(personPtr);

    var id = person.Id; // return val        
    
    // Update fields
    person.Id += 100;
    person.Amount += 10.00;
    person.Name = &amp;quot;Updated from .NET&amp;quot;;

    // Write updated struct back into same VFP buffer
    Marshal.StructureToPtr(person, personPtr, false);

    return id;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From FoxPro this code is ugly as you have to create the binary layout via string construction. Here's what that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;lcStruct = ;
    BINTOC(11, &amp;quot;4RS&amp;quot;) + ;        &amp;amp;&amp;amp; int Id
    BINTOC(99.95, &amp;quot;8B&amp;quot;) + ;       &amp;amp;&amp;amp; double Amount - verify format
    PADR(&amp;quot;Rick&amp;quot;, 64, CHR(0))      &amp;amp;&amp;amp; fixed char[64]
? lcStruct

DECLARE INTEGER ProcessPerson IN (lcDll) STRING@ person

lnResult = ProcessPerson(@lcStruct)
? lnResult  &amp;amp;&amp;amp; 11

***  retrive the data back out
lnId = CTOBIN(SUBSTR(lcStruct, 1, 4), &amp;quot;4RS&amp;quot;)  
lnAmount = CTOBIN(SUBSTR(lcStruct, 5, 8), &amp;quot;8B&amp;quot;)
lcName = SUBSTR(lcStruct, 13, 64)
lcName = LEFT(lcName, AT(CHR(0), lcName + CHR(0)) - 1)

? lnId    &amp;amp;&amp;amp; 111
? lnAmount
? lcName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yeah that's not pretty, but it gives you the idea of how to pass complex data in and out using StdCall DLL interface. This isn't any better than standard C/C++ because we're building a native DLL.&lt;/p&gt;
&lt;p&gt;There are FoxPro libraries that can help with structure conversions (&lt;a href="https://github.com/ChristianEhlscheid/vfp2c32"&gt;Vfp2C32&lt;/a&gt;) but even then this process is tedious and error prone especially if structures change.&lt;/p&gt;
&lt;h2 id="when-does-this-make-sense"&gt;When does this make Sense?&lt;/h2&gt;
&lt;p&gt;I was pretty excited about using C# to create DLL interfaces and replace some of my ancient, nearly unmaintainable native DLLs. Using C/C++ for critical components is a PITA, and it requires installing the C compiler plus a pile of dependencies that seem to break every time the compiler or Windows SDK revs. So yes, replacing C with C# and AOT compilation sounds like a great idea.&lt;/p&gt;
&lt;p&gt;But... there are issues. I use .NET interop a lot from different applications, and one thing that has always bugged me is the loader DLL that spins up the .NET Runtime Host. For one, that code is incredibly dense, and difficult to understand and horribly under documented by Microsoft. So much so that even with the help of LLMs, I've gotten this wrong many times before ending up on a shakey foundation that worked but that I don't 100% understand. I was hoping by bootstrapping through .NET that would be easier, but unfortunately, COM wrapper interfaces aren't part of AOT so there's no 'built-in' mechanism to pass back a .NET reference from without re-constructing the COM wrapper infrastructure. And even if it would be available, startup would likely bloat into multi-megabyte territory, which is unacceptable.&lt;/p&gt;
&lt;p&gt;On top of that the reality is that to do anything productive with this tech, you still have to use the same awkward calling syntax that makes C-style DLL code painful outside of C code in the first place. AOT doesn't change that. And if you need serious C# functionality with runtime dependencies, you'll likely end up with a fairly large DLL anyway.&lt;/p&gt;
&lt;p&gt;For any environment that has access to COM, I'd recommend sticking with traditional ways to call into .NET via COM interop or something like &lt;a href="https://github.com/RickStrahl/wwDotnetBridge"&gt;wwDotnetBridge&lt;/a&gt; to bootstrap and then communicate over COM with the .NET object. Yes, that's not native code, but you get a much cleaner calling interface, and performance is only slightly slower for call overhead, and first time JIT compilation. After that, I wouldn't expect any meaningful performance difference of JIT compiled code versus native AOT compilation.&lt;/p&gt;
&lt;p&gt;I can still see this being useful in a few places. I may still end up using it for my &lt;code&gt;wwDotnetBridge&lt;/code&gt; bootstrapping DLL although it'll end up being a bit more work than I'd hoped. Even though I can't use runtime libraries directly to get the .NET Runtime hosted, I might be able to make it work with P/Invoke calls. The main benefit for me would be dropping the C compiler from my build pipeline, which would be a win.&lt;/p&gt;
&lt;p&gt;That leaves environments that have good native C interfaces like Python or no easy COM interfaces. Basically any scenario where you'd opt to use C/C++, the .NET AOT route might be an option.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;In the end using AOT to create native DLLs is a very niche feature that has a very narrow optimal usage range that probably benefits only certain bootstrapping scenarios. Almost everything else is probably better handled with full .NET interop with the same performance profile other than initial JIT compile overhead.&lt;/p&gt;
&lt;p&gt;The only benefit I see here really is for people like me who dig out C++ every 5 years for some system level interface, only to take 2 weeks to create something that would take 10 seconds in .NET &#128516; And with LLMs today that probably can compress down to 1 hour and some incomprehensible C/C++ code &#128514;.&lt;/p&gt;
&lt;p&gt;However, if you find yourself with one of those very narrow use cases, I hope this post has given you the background for what you need to know to create your AOT DLLs...&lt;/p&gt;
&lt;div style='margin: 10px 0px'&gt;&lt;small&gt;© Rick Strahl, West Wind Technologies, 2005-2026&lt;/small&gt;&lt;/div&gt;&lt;div&gt;Posted in &lt;b&gt;&lt;a href='/ShowPosts.aspx?Category=.NET'&gt;.NET&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;a href='/ShowPosts.aspx?Category=C#'&gt;C#&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;a href='/ShowPosts.aspx?Category=Windows'&gt;Windows&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/div&gt;
&lt;/small&gt;</description>
    </item>
    <item>
      <title>Azure Trusted Signing Revisited with Dotnet Sign</title>
      <link>https://weblog.west-wind.com/posts/2026/Mar/02/Azure-Trusted-Signing-Revisited-with-Dotnet-Sign</link>
      <guid isPermaLink="false">5249291_202603022010</guid>
      <pubDate>Mon, 02 Mar 2026 20:10:23 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2026/Mar/02/Azure-Trusted-Signing-Revisited-with-Dotnet-Sign#Comments</comments>
      <slash:comments>1</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=5249291</wfw:commentRss>
      <category>Windows</category>
      <category>Security</category>
      <category>WPF</category>
      <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>
    </item>
    <item>
      <title>Don't use the Microsoft Timestamp Server for Signing</title>
      <link>https://weblog.west-wind.com/posts/2026/Feb/26/Dont-use-the-Microsoft-Timestamp-Server-for-Signing</link>
      <guid isPermaLink="false">5239628_202602262241</guid>
      <pubDate>Thu, 26 Feb 2026 22:41:03 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2026/Feb/26/Dont-use-the-Microsoft-Timestamp-Server-for-Signing#Comments</comments>
      <slash:comments>3</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=5239628</wfw:commentRss>
      <category>Security</category>
      <category>Windows</category>
      <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>
    </item>
    <item>
      <title>Reliably Refreshing the WebView2 Control</title>
      <link>https://weblog.west-wind.com/posts/2026/Feb/04/Reliably-Refreshing-the-WebView2-Control</link>
      <guid isPermaLink="false">5204283_202602050753</guid>
      <pubDate>Thu, 05 Feb 2026 07:53:22 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2026/Feb/04/Reliably-Refreshing-the-WebView2-Control#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=5204283</wfw:commentRss>
      <category>WebView</category>
      <category>Windows</category>
      <category>WPF</category>
      <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>
    </item>
    <item>
      <title>What the heck is a `\\.\nul` path and why is it breaking my Directory Files Lookup?</title>
      <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_202512082315</guid>
      <pubDate>Mon, 08 Dec 2025 23:15:18 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <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>
      <slash:comments>8</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=5133488</wfw:commentRss>
      <category>dotnet</category>
      <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>
    </item>
    <item>
      <title>Using the new WebView2 AllowHostInputProcessing Keyboard Mapping Feature</title>
      <link>https://weblog.west-wind.com/posts/2025/Aug/20/Using-the-new-WebView2-AllowHostInputProcessing-Keyboard-Mapping-Feature</link>
      <guid isPermaLink="false">4979824_202508210317</guid>
      <pubDate>Thu, 21 Aug 2025 03:17:34 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Aug/20/Using-the-new-WebView2-AllowHostInputProcessing-Keyboard-Mapping-Feature#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4979824</wfw:commentRss>
      <category>WPF</category>
      <category>WebView</category>
      <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>
    </item>
    <item>
      <title>Fighting through Setting up Microsoft Trusted Signing</title>
      <link>https://weblog.west-wind.com/posts/2025/Jul/20/Fighting-through-Setting-up-Microsoft-Trusted-Signing</link>
      <guid isPermaLink="false">4940804_202507210056</guid>
      <pubDate>Mon, 21 Jul 2025 00:56:23 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Jul/20/Fighting-through-Setting-up-Microsoft-Trusted-Signing#Comments</comments>
      <slash:comments>38</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4940804</wfw:commentRss>
      <category>Windows</category>
      <category>Security</category>
      <category>Azure</category>
      <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>
    </item>
    <item>
      <title>Centering a WPF TreeViewItem in the TreeView ScrollViewer</title>
      <link>https://weblog.west-wind.com/posts/2025/Jul/15/Centering-a-WPF-TreeViewItem-in-the-TreeView-ScrollViewer</link>
      <guid isPermaLink="false">4934685_202507160423</guid>
      <pubDate>Wed, 16 Jul 2025 04:23:03 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Jul/15/Centering-a-WPF-TreeViewItem-in-the-TreeView-ScrollViewer#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4934685</wfw:commentRss>
      <category>WPF</category>
      <description>One of the annoying features of the TreeView in WPF is the inability to easily select and make an item prominently visible. While there is `TreeView.BringIntoView()` it doesn't do a good job dropping the item that the very bottom (or top) of the viewport with no way to center. In this post I show a few helper methods that facilitate this process and a few related tasks with some useful TreeView helpers.</description>
    </item>
    <item>
      <title>Unpacking Zip Folders into Windows Long File Paths</title>
      <link>https://weblog.west-wind.com/posts/2025/Jun/22/Unpacking-Zip-Folders-into-Windows-Long-File-Paths</link>
      <guid isPermaLink="false">4906257_202506221100</guid>
      <pubDate>Sun, 22 Jun 2025 11:00:48 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Jun/22/Unpacking-Zip-Folders-into-Windows-Long-File-Paths#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4906257</wfw:commentRss>
      <category>.NET</category>
      <category>Windows</category>
      <description>Ah, long file paths bit me again today - this time with `ZipFile.ExtractToDirectory()` not unzipping long path files, even when specifying long path syntax for the output folder. In this post I briefly review Windows long path issues again, and describe what doesn't work and how to work around this particular problem. While specific to `ExtractToDirectory()` a similar approach might apply to other batch file based operations.</description>
    </item>
    <item>
      <title>Adding Runtime NuGet Package Loading to an Application</title>
      <link>https://weblog.west-wind.com/posts/2025/Jun/09/Adding-Runtime-NuGet-Package-Loading-to-an-Application</link>
      <guid isPermaLink="false">4892294_202506100440</guid>
      <pubDate>Tue, 10 Jun 2025 04:40:37 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Jun/09/Adding-Runtime-NuGet-Package-Loading-to-an-Application#Comments</comments>
      <slash:comments>1</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4892294</wfw:commentRss>
      <category>.NET ASP.NET</category>
      <description>It's not a common use case, but if you need need to dynamically add external code at runtime, NuGet packages are the most familiar way for developers to add that functionality besides old-school direct assembly loading. In this post I look at my LiveReloadServer local Web Server tool and how I integrated both external assembly loading and NuGet package support to allow extensibility for the Razor (and Markdown) scripting functionality of LiveReloadServer.</description>
    </item>
    <item>
      <title>Distinguished Name on FileZilla Server Self-Generated Certs</title>
      <link>https://weblog.west-wind.com/posts/2025/Jun/06/Distinguished-Name-on-FileZilla-Server-SelfGenerated-Certs</link>
      <guid isPermaLink="false">4888546_202506062044</guid>
      <pubDate>Fri, 06 Jun 2025 20:44:04 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Jun/06/Distinguished-Name-on-FileZilla-Server-SelfGenerated-Certs#Comments</comments>
      <slash:comments>1</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4888546</wfw:commentRss>
      <category>Windows</category>
      <category>FTP</category>
      <description>Renewing Filezilla certificates I've run into a 'huh?' moment a few times trying to renew the self-signed certificates for the Administration interface. Trying the obvious entries of putting in the DNS names results in an error that's not perplexingly is fixed by providing a full Common Name instead.</description>
    </item>
    <item>
      <title>Configuring Microsoft.AI.Extensions with multiple providers</title>
      <link>https://weblog.west-wind.com/posts/2025/May/30/Configuring-MicrosoftAIExtension-with-multiple-providers</link>
      <guid isPermaLink="false">4880955_202505310856</guid>
      <pubDate>Sat, 31 May 2025 08:56:33 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/May/30/Configuring-MicrosoftAIExtension-with-multiple-providers#Comments</comments>
      <slash:comments>1</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4880955</wfw:commentRss>
      <category>.NET</category>
      <description>Microsoft.Extensions.AI is the new base library for creating AI clients in an abstracted way. While the library does a great job with the abstraction of the interfaces it works with, the provider interfaces that create the intial abstractions like IChatClient are a lot less user friendly with hard to discover syntax based on extension methods and different syntax for each provider. In this post I review three providers and how to get them instantiated along with a small streaming example to use them.</description>
    </item>
    <item>
      <title>Lazy Loading the Mermaid Diagram Library</title>
      <link>https://weblog.west-wind.com/posts/2025/May/10/Lazy-Loading-the-Mermaid-Diagram-Library</link>
      <guid isPermaLink="false">4856296_202505110926</guid>
      <pubDate>Sun, 11 May 2025 09:26:29 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/May/10/Lazy-Loading-the-Mermaid-Diagram-Library#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4856296</wfw:commentRss>
      <category>Javascript</category>
      <category>Web</category>
      <description>The Mermaid library is a large beast, and if you're using it selectively in your Web content you probably want to make sure you don't load it unless you actually need it, due to it's download footprint and load time. If you're loading Mermaid with server only code it's pretty straight forward, but if you use it on the client there you may want to delay loading the library until you actually need to display a diagram. </description>
    </item>
    <item>
      <title>WebView2: Waiting for Document Loaded</title>
      <link>https://weblog.west-wind.com/posts/2025/May/06/WebView2-Waiting-for-Document-Loaded</link>
      <guid isPermaLink="false">4847859_202505062150</guid>
      <pubDate>Tue, 06 May 2025 21:50:16 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/May/06/WebView2-Waiting-for-Document-Loaded#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4847859</wfw:commentRss>
      <category>WebView</category>
      <category>Windows</category>
      <category>WPF</category>
      <description>WebBrowser loading is always async and the WebView2 control is no different - in fact it's more complex as there a number of events that need to be handled in order to properly handle loading the control. In this post I describe how you can create a simplified load checking routine, or use the Westwind.WebView library and WebViewHandler to wait on content and process document access using linear `await` syntax.</description>
    </item>
    <item>
      <title>Avoiding WPF Image Control Local File Locking</title>
      <link>https://weblog.west-wind.com/posts/2025/Apr/28/WPF-Image-Control-Local-File-Locking</link>
      <guid isPermaLink="false">4838355_202504282201</guid>
      <pubDate>Mon, 28 Apr 2025 22:01:46 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Apr/28/WPF-Image-Control-Local-File-Locking#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4838355</wfw:commentRss>
      <category>WPF .NET Windows</category>
      <description>WPF locks local images when referenced via a Source attribute. In this post I discuss why this might be a problem and how you can work around it using native XAML and custom binding converter that provides a consolidated approach that includes image caching for better performance of reused images.</description>
    </item>
    <item>
      <title>The Strong ARM of .NET: Wrestling with x64 and Arm64 Desktop App Deployment</title>
      <link>https://weblog.west-wind.com/posts/2025/Apr/18/The-Strong-ARM-of-NET-Wrestling-with-x64-and-Arm64-Desktop-App-Deployment</link>
      <guid isPermaLink="false">4827822_202504190835</guid>
      <pubDate>Sat, 19 Apr 2025 08:35:43 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Apr/18/The-Strong-ARM-of-NET-Wrestling-with-x64-and-Arm64-Desktop-App-Deployment#Comments</comments>
      <slash:comments>3</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4827822</wfw:commentRss>
      <category>.NET</category>
      <category>Windows</category>
      <description>.NET  works great for cross-platform development making it easy to build apps that are cross-platform and cross-architecture specifically for x64 and Arm64. However, building a distributable application that can install and run out of box on both of these platforms, that's a bit more effort. In this post I discuss some of the gotchas and how to work around them to distribute apps that run out of the gate on both platforms.</description>
    </item>
    <item>
      <title>Using Windows.Media SpeechRecognition in WPF</title>
      <link>https://weblog.west-wind.com/posts/2025/Mar/24/Using-WindowsMedia-SpeechRecognition-in-WPF</link>
      <guid isPermaLink="false">4800107_202503250433</guid>
      <pubDate>Tue, 25 Mar 2025 04:33:00 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Mar/24/Using-WindowsMedia-SpeechRecognition-in-WPF#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4800107</wfw:commentRss>
      <category>WPF</category>
      <category>Windows</category>
      <category>.NET</category>
      <description>Windows has a pretty capable SpeechRecognition engine built-in via Windows Media services. In .NET these features are accessible via the Windows SDK (WinSdk) that expose these Windows features to .NET applications. In this post I discuss how you can integrate these features into a WPF application, and discuss some of the pitfalls along the way that you have to watch out for related to the SDK integration 'features'.</description>
    </item>
    <item>
      <title>Making Html Input Controls Truly ReadOnly</title>
      <link>https://weblog.west-wind.com/posts/2025/Mar/14/Making-Html-Input-Controls-Truly-ReadOnly</link>
      <guid isPermaLink="false">4789670_202503142026</guid>
      <pubDate>Fri, 14 Mar 2025 20:26:04 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Mar/14/Making-Html-Input-Controls-Truly-ReadOnly#Comments</comments>
      <slash:comments>6</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4789670</wfw:commentRss>
      <category>Html</category>
      <category>CSS</category>
      <description>A discussion of how to show readonly controls in user interface and ensuring that they are not UI activated. This post discusses readonly and disabled behavior and how you can make readonly behave better if you choose to use it over disabled.</description>
    </item>
    <item>
      <title>Accessing Windows Settings Dialogs from Code via Shell Commands</title>
      <link>https://weblog.west-wind.com/posts/2025/Mar/13/Accessing-Windows-Settings-Dialogs-from-Code-via-Shell-Commands</link>
      <guid isPermaLink="false">4788723_202503132240</guid>
      <pubDate>Thu, 13 Mar 2025 22:40:22 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Mar/13/Accessing-Windows-Settings-Dialogs-from-Code-via-Shell-Commands#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4788723</wfw:commentRss>
      <category>.NET</category>
      <category>Windows</category>
      <description>Windows has support for an `ms-setting:` Protocol Handler/Uri Scheme that allows you to directly open most Windows settings dialogs directly via simple Url based shell commands.</description>
    </item>
    <item>
      <title>Resolving Paths To Server Relative Paths in .NET Code</title>
      <link>https://weblog.west-wind.com/posts/2025/Mar/08/Resolving-Paths-To-Server-Relative-Paths-in-NET-Code</link>
      <guid isPermaLink="false">4783718_202503090639</guid>
      <pubDate>Sun, 09 Mar 2025 06:39:18 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Mar/08/Resolving-Paths-To-Server-Relative-Paths-in-NET-Code#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4783718</wfw:commentRss>
      <category>ASP.NET</category>
      <description>ASP.NET Core has support for resolving Urls in Controllers and Razor Pages via embedded `~/` links in Views and via `Url.Content()`. But, these features are tied to Controllers or Razor Pages - what if you need to resolve Urls elsewhere, in middleware or even inside of business logic? In this post I describe how you can build a couple of helpers that are more flexible and also provide some additional functionality of resolving site root and relative paths to full site root paths.</description>
    </item>
    <item>
      <title>Inline Confirmations in JavaScript UI</title>
      <link>https://weblog.west-wind.com/posts/2025/Feb/26/Inline-Confirmations-in-JavaScript-UI</link>
      <guid isPermaLink="false">4772830_202502270902</guid>
      <pubDate>Thu, 27 Feb 2025 09:02:10 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Feb/26/Inline-Confirmations-in-JavaScript-UI#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4772830</wfw:commentRss>
      <category>Html</category>
      <category>JavaScript</category>
      <category>ASP.NET</category>
      <description>Confirmation dialogs or modal popups can be annoying in HTML applications. What if you could instead use an inline UI to confirm an operation? In this post I describe a simple way you can use an inline UI to confirm an operation that can be easily implemented with a few lines of code and a couple of binding directives.</description>
    </item>
    <item>
      <title>Retrieving Images from the Clipboard Reliably in WPF Revisited</title>
      <link>https://weblog.west-wind.com/posts/2025/Feb/21/Retrieving-Images-from-the-Clipboard-Reliably-in-WPF-Revisited</link>
      <guid isPermaLink="false">4768057_202502220809</guid>
      <pubDate>Sat, 22 Feb 2025 08:09:36 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Feb/21/Retrieving-Images-from-the-Clipboard-Reliably-in-WPF-Revisited#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4768057</wfw:commentRss>
      <category>WPF</category>
      <category>Windows</category>
      <description>The WPF Clipboard is notoriously bad for retrieving image data, mainly because of the funky behavior of the ImageSource control into which clipboard data is loaded. WPF cuts a lot of corners when retrieving images and there are many scenarios where Clipboard.GetImage() either crashes or doesn't render the supposedly successfully retrieved image. In this post I review some of the challenges and how you can work around the retrieving an ImageSource with an intermediary load of a bitmap in between to provide reliable image retrieval from the clipboard in WPF.</description>
    </item>
    <item>
      <title>Comparing Raw ASP.NET Request Throughput across Versions: 8.0 to 9.0 Edition</title>
      <link>https://weblog.west-wind.com/posts/2025/Jan/19/Comparing-Raw-ASPNET-Request-Throughput-across-Versions-80-to-90-Edition</link>
      <guid isPermaLink="false">4734648_202501192251</guid>
      <pubDate>Sun, 19 Jan 2025 22:51:02 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2025/Jan/19/Comparing-Raw-ASPNET-Request-Throughput-across-Versions-80-to-90-Edition#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4734648</wfw:commentRss>
      <category>.NET ASP.NET</category>
      <description>Once again I'm taking a look at the newish .NET release and how it compares to the previous release - this time .NET 9.0 from .NET 8.0. I'll run my simple load tests to compare performance and also discuss a number of anecdotes from running .NET 9.0 in production apps for the last couple of months.</description>
    </item>
    <item>
      <title>Back to Basics: Using the Parallel Library to Massively Boost Loop Performance</title>
      <link>https://weblog.west-wind.com/posts/2024/Dec/27/Back-to-Basics-Using-the-Parallel-Library-to-Massively-Boost-Loop-Performance</link>
      <guid isPermaLink="false">4712020_202412272337</guid>
      <pubDate>Fri, 27 Dec 2024 23:37:19 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Dec/27/Back-to-Basics-Using-the-Parallel-Library-to-Massively-Boost-Loop-Performance#Comments</comments>
      <slash:comments>8</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4712020</wfw:commentRss>
      <category>.NET</category>
      <category>CSharp</category>
      <description>I recently had another occasion to add Parallel.ForEachAsync() into an application to improve performance of an Http look up operation drastically. When I tweeted a note on X there was quite a bit of response, so I thought I follow up and discuss Parallel use in this use case and when it makes sense to use Parallel in applications.</description>
    </item>
    <item>
      <title>Getting the Current TabItem when the Tab is not selected in WPF</title>
      <link>https://weblog.west-wind.com/posts/2024/Nov/08/Getting-the-Current-TabItem-when-the-Tab-is-not-selected-in-WPF</link>
      <guid isPermaLink="false">4666224_202411090910</guid>
      <pubDate>Sat, 09 Nov 2024 09:10:44 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Nov/08/Getting-the-Current-TabItem-when-the-Tab-is-not-selected-in-WPF#Comments</comments>
      <slash:comments>3</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4666224</wfw:commentRss>
      <category>WPF</category>
      <description>There's no direct support in WPF to find the control that the mouse is hovering over in Items controls like the TabControl, so if you need to bring up context sensitive information like a context menu or tooltip it takes a little extra effort to get the correct item to work with in this case ContextMenuOpening. </description>
    </item>
    <item>
      <title>Using SQL Server on Windows ARM</title>
      <link>https://weblog.west-wind.com/posts/2024/Oct/24/Using-Sql-Server-on-Windows-ARM</link>
      <guid isPermaLink="false">4648676_202410242159</guid>
      <pubDate>Thu, 24 Oct 2024 21:59:54 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Oct/24/Using-Sql-Server-on-Windows-ARM#Comments</comments>
      <slash:comments>21</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4648676</wfw:commentRss>
      <category>.NET</category>
      <category>SQL Server</category>
      <category>Windows</category>
      <description>I recently got a SnapDragon X Elite ARM device to play with and while I've been impressed with all things that work very well on it, one thing did not install easily: SQL Server. There's no ARM version of SQL Server and the x64 versions don't run on ARM devices. Docker images are also amd64 so that didn't work well either. Turns out you can use LocalDb onin emulation mode, but setting it up is anything but intuitive. In this post I discuss how to get SQL Server to run on a Windows ARM device.</description>
    </item>
    <item>
      <title>Getting the ASP.NET Core Server Hosting Urls at Startup and in Requests</title>
      <link>https://weblog.west-wind.com/posts/2024/Sep/03/Getting-the-ASPNET-Core-Server-Hosting-Urls-at-Startup-and-in-Requests</link>
      <guid isPermaLink="false">4569156_202409040931</guid>
      <pubDate>Wed, 04 Sep 2024 09:31:35 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Sep/03/Getting-the-ASPNET-Core-Server-Hosting-Urls-at-Startup-and-in-Requests#Comments</comments>
      <slash:comments>4</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4569156</wfw:commentRss>
      <category>asp.net</category>
      <description>Ever need to retrieve an ASP.NET application's hosting Urls? There are million ways to set these URLs but there's no easy fixed location where you can pick the addresses up from. Instead you have to go through a bit of a dance, and use one of two approaches depending on whether you need the addresses during startup or inside of a request.</description>
    </item>
    <item>
      <title>Nuking Local Nuget Package Sources to show newly Published Packages</title>
      <link>https://weblog.west-wind.com/posts/2024/Aug/04/Nuking-Local-Nuget-Package-Sources-to-show-newly-Published-Packages</link>
      <guid isPermaLink="false">4521831_202408042352</guid>
      <pubDate>Sun, 04 Aug 2024 23:52:12 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Aug/04/Nuking-Local-Nuget-Package-Sources-to-show-newly-Published-Packages#Comments</comments>
      <slash:comments>1</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4521831</wfw:commentRss>
      <category>.NET</category>
      <category>NuGet</category>
      <description>Every once in while when I publish a NuGet package and then try to use the published package in another project I end up with a situation where the new package simply is not recognized. Most of the time it shows up after a few minutes, but on more than a few occasions I've stuck waiting for quite a while waiting for the package cache to refresh. Luckily there's a way to nuke the cache and force Nuget to re-read local packages and while that is the nuclear option that requires downloading a lot of packages it works to make your project compile - right now!</description>
    </item>
    <item>
      <title>Create a .NET PlantUML Markdown Render Extension</title>
      <link>https://weblog.west-wind.com/posts/2024/Jul/29/Create-a-NET-PlantUML-Markdown-Render-Extension</link>
      <guid isPermaLink="false">4512301_202407292216</guid>
      <pubDate>Mon, 29 Jul 2024 22:16:35 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Jul/29/Create-a-NET-PlantUML-Markdown-Render-Extension#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4512301</wfw:commentRss>
      <category>.NET</category>
      <category>Markdown Monster</category>
      <description>PlantUML is a Web based diagramming markup language that can be used to create diagrams using text descriptions that are rendered into images via a PlantUML server. In this post I describe how you can integrate PlantUML image rendering into your .NET application, specifically from a Markdown rendering perspective as Markdown documents are the most common mechanism that PlantUML output is delivered.</description>
    </item>
    <item>
      <title>Back to Basics: Await a Task with a Timeout</title>
      <link>https://weblog.west-wind.com/posts/2024/Jul/25/Back-to-Basics-Await-a-Task-with-a-Timeout</link>
      <guid isPermaLink="false">4505520_202407252148</guid>
      <pubDate>Thu, 25 Jul 2024 21:48:00 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Jul/25/Back-to-Basics-Await-a-Task-with-a-Timeout#Comments</comments>
      <slash:comments>8</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4505520</wfw:commentRss>
      <category>.NET</category>
      <description>Sometimes it's useful to call async methods and be able to stop waiting after a given timeout period. Unfortunately there's no native support on Task to provide this. In this short post I show how you can simulate timeouts and provide a couple of helper methods to make the process generically available.</description>
    </item>
    <item>
      <title>Work around the WebView2 NavigateToString() 2mb Size Limit</title>
      <link>https://weblog.west-wind.com/posts/2024/Jul/22/Work-around-the-WebView2-NavigateToString-2mb-Size-Limit</link>
      <guid isPermaLink="false">4500913_202407230753</guid>
      <pubDate>Tue, 23 Jul 2024 07:53:44 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Jul/22/Work-around-the-WebView2-NavigateToString-2mb-Size-Limit#Comments</comments>
      <slash:comments>4</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4500913</wfw:commentRss>
      <category>WebView</category>
      <description>The WebView2 control's NavigateToString() method has a limit for the string size of 2mb, which can be a problem especially for HTML pages with embedded content. In this post I'll describe how this problem can show up and show a couple of ways to work around the issue.</description>
    </item>
    <item>
      <title>Dealing with Platform Specific Classes and Methods in CrossPlatform .NET</title>
      <link>https://weblog.west-wind.com/posts/2024/Jul/18/Dealing-with-Platform-Specific-Classes-and-Methods-in-CrossPlatform-NET</link>
      <guid isPermaLink="false">4496078_202407190702</guid>
      <pubDate>Fri, 19 Jul 2024 07:02:59 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Jul/18/Dealing-with-Platform-Specific-Classes-and-Methods-in-CrossPlatform-NET#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4496078</wfw:commentRss>
      <category>.NET</category>
      <description>If you deal with old .NET library code that is sprinkled with some Windows specific code in places you've likely run into places where the Windows specific code is throwing up unwanted compiler warnings. Sometimes that matters, but often times these warnings are annoyances as these methods are highly unlikely to get called from x-platform code. In this post I describe some annoyances, how you can work around them and eventually fix the issues without throwing everything out the window.</description>
    </item>
    <item>
      <title>C# Version String Formatting</title>
      <link>https://weblog.west-wind.com/posts/2024/Jun/13/C-Version-String-Formatting</link>
      <guid isPermaLink="false">4445887_202406140612</guid>
      <pubDate>Fri, 14 Jun 2024 06:12:37 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Jun/13/C-Version-String-Formatting#Comments</comments>
      <slash:comments>9</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4445887</wfw:commentRss>
      <category>.NET</category>
      <category>C#</category>
      <description>I'm tired of trying to format versions for user facing interfaces after fumbling with it again and again. In this short post I show a small helper extension method that lets you configure how to form user friendly version strings to display to end users.</description>
    </item>
    <item>
      <title>Mime Base64 is a Thing?</title>
      <link>https://weblog.west-wind.com/posts/2024/May/26/Mime-Base64-is-a-Thing</link>
      <guid isPermaLink="false">4416829_202405270750</guid>
      <pubDate>Mon, 27 May 2024 07:50:45 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/May/26/Mime-Base64-is-a-Thing#Comments</comments>
      <slash:comments>3</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4416829</wfw:commentRss>
      <category>.NET</category>
      <description>Ran into an old legacy application recently that required that attached data was preformatted to Mime Base64 which I never even heard of before. Turns out it's a 'url-safe' version of base64 that replaces certain characters that can be present in base64 with 'safe' characters. In this short post I show a small helper that handles several Base64 Mime operations.</description>
    </item>
    <item>
      <title>Speed up your Start Menu by disabling Web Search</title>
      <link>https://weblog.west-wind.com/posts/2024/May/03/Speed-up-your-Start-Menu-by-disabling-Web-Search</link>
      <guid isPermaLink="false">4381163_202405040653</guid>
      <pubDate>Sat, 04 May 2024 06:53:30 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/May/03/Speed-up-your-Start-Menu-by-disabling-Web-Search#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4381163</wfw:commentRss>
      <category>Windows</category>
      <description>If you're like me, you've probably cursed the Windows Start menu from time to time, when it's either very slow to pop up, or in some instances fails to pop up at all when you press the Windows key. This simple tip can drastically improve performance of your Windows Start Menu by simply disabling Web search. </description>
    </item>
    <item>
      <title>ASP.NET Core Hosting Module with Shadow Copy Not Starting: Separate your Shadow Copy Folders!</title>
      <link>https://weblog.west-wind.com/posts/2024/Apr/28/ASPNET-Core-Hosting-Module-with-Shadow-Copy-Not-Starting-Separate-your-Shadow-Copy-Folders</link>
      <guid isPermaLink="false">4372790_202404290106</guid>
      <pubDate>Mon, 29 Apr 2024 01:06:35 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Apr/28/ASPNET-Core-Hosting-Module-with-Shadow-Copy-Not-Starting-Separate-your-Shadow-Copy-Folders#Comments</comments>
      <slash:comments>4</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4372790</wfw:commentRss>
      <category>ASP.NET IIS</category>
      <description>I recently ran into a major failure related to Shadow Copying for an ASP.NET Web app on IIS which was caused by corruption of the Shadow Copy directories. The app starts with the dreaded white ANCM Error page and event log entries that point at missing application folders. It turns out that this is caused by interference of multiple applications using the same shadow copy folder. In this post I describe the problem and how to work around it.</description>
    </item>
    <item>
      <title>Programmatic Html to PDF Generation using the WebView2 Control with .NET</title>
      <link>https://weblog.west-wind.com/posts/2024/Mar/26/Html-to-PDF-Generation-using-the-WebView2-Control</link>
      <guid isPermaLink="false">4319131_202403270759</guid>
      <pubDate>Wed, 27 Mar 2024 07:59:52 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Mar/26/Html-to-PDF-Generation-using-the-WebView2-Control#Comments</comments>
      <slash:comments>14</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4319131</wfw:commentRss>
      <category>.NET ASP.NET Windows</category>
      <description>In this post I describe how to use the Microsoft WebView2 control to automate HTML to PDF generation generically for any kind of Windows application, including services. We'll look at the WebView and it's printing functionality and some of the intricacies that are involved in hosting the WebView control outside of a desktop application context to provide unattended mode even in service context.</description>
    </item>
    <item>
      <title>Comparing Raw ASP.NET Request Throughput across Versions</title>
      <link>https://weblog.west-wind.com/posts/2024/Mar/08/Comparing-Raw-ASPNET-Request-Throughput-across-Versions</link>
      <guid isPermaLink="false">4285010_202403081021</guid>
      <pubDate>Fri, 08 Mar 2024 10:21:08 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Mar/08/Comparing-Raw-ASPNET-Request-Throughput-across-Versions#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4285010</wfw:commentRss>
      <category>ASP.NET</category>
      <description>When I set up a new machine I usually use a small ASP.NET test project to get a feel of performance of the machine and when that happens I also take a moment to compare performance across recent versions of .NET to see how things are improving - and improved they have. Both due to the new hardware I'm using but also ASP.NET continues to bump up performance in every new version that comes out. In this post I describe a simple project with minimal do nothing requests to test the ASP,.NET pipeline locally and how to test these request as well as discussing the results.</description>
    </item>
    <item>
      <title>Reading Raw ASP.NET Request.Body Multiple Times</title>
      <link>https://weblog.west-wind.com/posts/2024/Feb/20/Reading-Raw-ASPNET-RequestBody-Multiple-Times</link>
      <guid isPermaLink="false">4257209_202402202308</guid>
      <pubDate>Tue, 20 Feb 2024 23:08:09 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Feb/20/Reading-Raw-ASPNET-RequestBody-Multiple-Times#Comments</comments>
      <slash:comments>4</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4257209</wfw:commentRss>
      <category>ASP.NET</category>
      <description>Some time ago I wrote about accessing raw request body content in ASP.NET Core which ended up being one of the most popular posts on this blog. But I failed to mention one major caveat: By default Request.Body can only be read once. In this post I discuss why frequently when you need raw Request.Body access you actually need to read the body more than once, and you can enable that functionality and deal with the caveats of doing so.</description>
    </item>
    <item>
      <title>Sharing Tab Missing in Windows 11 Folder Properties</title>
      <link>https://weblog.west-wind.com/posts/2024/Jan/10/Sharing-Tab-Missing-in-Windows-11-Folder-Properties</link>
      <guid isPermaLink="false">4187850_202401102254</guid>
      <pubDate>Wed, 10 Jan 2024 22:54:27 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Jan/10/Sharing-Tab-Missing-in-Windows-11-Folder-Properties#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4187850</wfw:commentRss>
      <category>Windows</category>
      <description>For some unfathomable reason, Windows 11 has removed the Sharing Tab on the Explorer Properties Context menu by default. The Sharing Tab allows you to shared folders and drives for remote access. In this post I discuss how to get the Sharing Tab back and also touch on how to make sure your machine can actually accept remote connections so you can share your folders and drives.</description>
    </item>
    <item>
      <title>Working around the WPF ImageSource Blues</title>
      <link>https://weblog.west-wind.com/posts/2024/Jan/03/Working-around-the-WPF-ImageSource-Blues</link>
      <guid isPermaLink="false">4169641_202401040536</guid>
      <pubDate>Thu, 04 Jan 2024 05:36:29 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2024/Jan/03/Working-around-the-WPF-ImageSource-Blues#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4169641</wfw:commentRss>
      <category>WPF</category>
      <category>Windows</category>
      <description>The WPF Image control and its ImageSource property can be problematic if you are loading a lot of images in a list. Depending on where you load images from, and how, you can very easily get bogged down with slow, blocking load operations, and memory leaks when the controls are released. In this post I describe a couple of specific problems I ran into loading a sizable list of images from files and show a few ways how to avoid the potential pitfalls related to ImageSource peculiarities.</description>
    </item>
    <item>
      <title>Integrating OpenAI Image Generation into a .NET Application</title>
      <link>https://weblog.west-wind.com/posts/2023/Dec/21/Integrating-OpenAI-Image-Generation-into-a-NET-Application</link>
      <guid isPermaLink="false">4146698_202312212222</guid>
      <pubDate>Thu, 21 Dec 2023 22:22:07 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2023/Dec/21/Integrating-OpenAI-Image-Generation-into-a-NET-Application#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4146698</wfw:commentRss>
      <category>.NET</category>
      <category>AI</category>
      <description>Image Generation AIs are proving to be very good at creating images that can be used for all sorts of purposes. In this article I discuss how you can integrate image generation right into your own .NET applications using the OpenAI REST API. In addition I'll show how you can integrated this functionality into a larger application and discuss some general thoughts on image AI usage based on some of the experiences from a developer/non-designer user perspective.</description>
    </item>
    <item>
      <title>Embedding a minimal ASP.NET Web Server into a Desktop Application</title>
      <link>https://weblog.west-wind.com/posts/2023/Nov/27/Embedding-a-minimal-ASPNET-Web-Server-into-a-Desktop-Application</link>
      <guid isPermaLink="false">4108957_202311280842</guid>
      <pubDate>Tue, 28 Nov 2023 08:42:36 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2023/Nov/27/Embedding-a-minimal-ASPNET-Web-Server-into-a-Desktop-Application#Comments</comments>
      <slash:comments>3</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4108957</wfw:commentRss>
      <category>ASP.NET</category>
      <category>.NET</category>
      <category>WPF</category>
      <category>Windows</category>
      <description>Did you ever need to embed a Web Server into a non-Web application? In this post I describe how you can use host ASP.NET in a non-Web application and specifically in a WPF desktop app. What do you need, how is it different and some of the issues that you need to consider if you want to use ASP.NET in your non-Web applications.</description>
    </item>
    <item>
      <title>Save Files With Elevated Permissions on UnauthorizedAccessException</title>
      <link>https://weblog.west-wind.com/posts/2023/Nov/03/Save-Files-With-Elevated-Permissions-on-UnauthorizedAccessException</link>
      <guid isPermaLink="false">4073408_202311031948</guid>
      <pubDate>Fri, 03 Nov 2023 19:48:56 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2023/Nov/03/Save-Files-With-Elevated-Permissions-on-UnauthorizedAccessException#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4073408</wfw:commentRss>
      <category>Windows</category>
      <category>WPF</category>
      <description>If you have an application that generically allows you to edit and save files, you might on rare occasions need to save files in locations that where a regular user account does not have permissions to save. Rather than failing wouldn't it be nice to let the user know and optionally allow saving with elevated permissions? In this post I describe the workflow and implementation on how to do just that.</description>
    </item>
    <item>
      <title>Caching your WebView Environment to manage multiple WebView2 Controls</title>
      <link>https://weblog.west-wind.com/posts/2023/Oct/31/Caching-your-WebView-Environment-to-manage-multiple-WebView2-Controls</link>
      <guid isPermaLink="false">4068979_202310312210</guid>
      <pubDate>Tue, 31 Oct 2023 22:10:59 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2023/Oct/31/Caching-your-WebView-Environment-to-manage-multiple-WebView2-Controls#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4068979</wfw:commentRss>
      <category>WebView</category>
      <category>WPF</category>
      <category>Windows</category>
      <description>I've been struggling with rare WebView initialization errors in one of my applications, that have been difficult to debug and track down. After a lot of trial and error I discovered that the problem is related to WebView Environment instantiations that might be stepping on each other. In this post I describe the problem and a solution that involves creating a single WebView Environment and reusing it for all WebView initialization.</description>
    </item>
    <item>
      <title>Rolling Forward to Major Versions in .NET</title>
      <link>https://weblog.west-wind.com/posts/2023/Oct/02/Rolling-Forward-to-Major-Versions-in-NET</link>
      <guid isPermaLink="false">4028801_202310030812</guid>
      <pubDate>Tue, 03 Oct 2023 08:12:27 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2023/Oct/02/Rolling-Forward-to-Major-Versions-in-NET#Comments</comments>
      <slash:comments>2</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4028801</wfw:commentRss>
      <category>.NET</category>
      <description>.NET Core has sophisticated policies that allows your applications that are compiled to specific versions of the .NET Runtime can roll forward to newer versions. You can specify what part of the version to roll forward and that generally works well, except for preview releases which require an extra step.</description>
    </item>
    <item>
      <title>IIS Error 500.19 with ASP.NET Core Application</title>
      <link>https://weblog.west-wind.com/posts/2023/Sep/19/IIS-Error-50019-with-ASPNET-Core-Application</link>
      <guid isPermaLink="false">4009938_202309191002</guid>
      <pubDate>Tue, 19 Sep 2023 10:02:02 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2023/Sep/19/IIS-Error-50019-with-ASPNET-Core-Application#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=4009938</wfw:commentRss>
      <category>ASP.NET</category>
      <description>Running on IIS locally is pretty rare, but if for some odd reason you decide to run IIS locally on your dev machine you might find yourself getting a 500.19 error which relates to an issue in the web.config configuration. The solution is simple enough: Make sure the ASP.NET Core Hosting Module is installed. In this post I describe in more detail what the problem is and why you need a seemingly superfluous extra install to get IIS and ASP.NET Core to cooperate on local dev machine.</description>
    </item>
    <item>
      <title>Dotnet Tool Component not found on the Mac</title>
      <link>https://weblog.west-wind.com/posts/2023/Sep/11/Dotnet-Tool-Component-not-found-on-the-Mac</link>
      <guid isPermaLink="false">3999259_202309120205</guid>
      <pubDate>Tue, 12 Sep 2023 02:05:47 GMT</pubDate>
      <dc:creator>Rick Strahl</dc:creator>
      <comments>https://weblog.west-wind.com/posts/2023/Sep/11/Dotnet-Tool-Component-not-found-on-the-Mac#Comments</comments>
      <slash:comments>0</slash:comments>
      <wfw:commentRss>https://weblog.west-wind.com/commentrss.aspx?id=3999259</wfw:commentRss>
      <category>.NET</category>
      <description>I've run into this problem a few times: I install a new Mac OS and then install the .NET SDK. A bit later I install a dotnet tool using `dotnet tool install -g` and then try to run it, only to find out that the SDK is not able find it. This post is a note to self on how to fix the pathing for .NET tools to be found correctly and to be able to run your dotnet tools.</description>
    </item>
  </channel>
</rss>