<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss version="2.0">
  <channel>
    <title>Rick Strahl's Web Log</title>
    <link>https://weblog.west-wind.com/</link>
    <image>
      <url>ImageUrl</url>
      <title>Rick Strahl's Weblog</title>
      <link>https://weblog.west-wind.com/</link>
    </image>
    <description>Wind, waves, code and everything in between</description>
    <copyright>(c) West Wind Technologies 2006-2026</copyright>
    <pubDate>2026-10-09T20:29:30.2892517Z</pubDate>
    <lastBuildDate>2026-10-02T10:00:00Z</lastBuildDate>
    <generator>Rick Strahl's West Wind Weblog</generator>
    <xhtml:meta xmlns:xhtml="http://www.w3.org/1999/xhtml" content="noindex" name="robots"/><item>
      <title>Back to Basics: CSS @container Filtering</title>
      <description><![CDATA[<p><img src="https://weblog.west-wind.com/imageContent/2026/2026-10/ContainerBanner.jpg" alt="Container Banner"></p>
<p>I've been using <code>@media</code> queries in CSS forever, but on a number of occasions these type of Viewport-only limits on width are problematic, especially in UIs that are user resizable or can otherwise dynamically change the sizing of components or panels.</p>
<p>My specific scenario is a <strong>resizable panel display</strong> where a fixed <code>@media</code> width causes problems due to the container, rather than the window, needs resizing.</p>
<p>Most of us have likely used the <code>@media</code> at-rule for responsive design, but it's limited to the Viewport width.</p>
<p>There's another, more recent type of conditional CSS at-rule  that can be used to restrict sizing: <code>@container</code> which I have been unaware of until recently. <code>@container</code> is a relatively new addition to CSS that was only introduced to browsers a few years back in 2022/23, so perhaps I'm not the only one who got caught <em>unawares</em> that this even exists.</p>
<p><a href="https://markdownmonster.west-wind.com?ut=weblog"  target="_blank"
	  title="Markdown Monster - Easy to use, yet powerfully productive Markdown Editing for Windows">
<img src="https://weblog.west-wind.com/images/sponsors/MarkdownMonster-Display2.jpg" class="da-content-image" />
</a></p>
<p>Here's are how the two <strong>at-rules</strong> differ from each other:</p>
<ul>
<li><p><code>@media</code><br>
Works off of min/max values of the <strong>ViewPort</strong> of the host frame.<br>
<small><em><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media">more info</a></em></small></p>
</li>
<li><p><code>@container</code><br>
Allows specifying of a <strong>Parent Container Name</strong> to apply the min/max values to.<br>
<small><em><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@container">more info</a></em></small></p>
</li>
</ul>
<h2 id="the-problems-with-media-queries">The Problems with @media Queries</h2>
<p><code>@media</code> queries are great, when you're dealing with page wide content. If your page consists of a single host container that doesn't change, using <code>@media</code> queries works perfectly fine and it's a staple of Responsive and Mobile First Design. Especially in Mobile layouts that tend to be relatively simple single pane layouts <code>@media</code> constraints are just fine.</p>
<p>But... if you have multiple content panes in your page, or resizable or movable ones at that, <code>@media</code> queries can be too broad of a brush to apply to get the result you need to control nested container content.</p>
<p>For reference <code>@media</code> queries look like this:</p>
<pre><code class="language-css">.topic-outline {
    display: none;
}

@media(min-width: 1100px){ 
    .topic-outline {
        position: fixed;
        display: block;
        overflow-y: auto;

        top: 7em;
        right: 1em;
        max-height: calc(100vh - 10em);
        width: 255px;

        font-size: 0.725em !important;
        line-height: 1.4em;

        margin-top: 0px;
        margin-left: 10px;
        padding: 20px 3px 0 20px;
    }
}    
</code></pre>
<p>The <code>@media(min-width: 1100px) { }</code> block contains a subset of CSS that is applied when the condition is true - in this case if the Viewport width exceeds a minimum of <code>1100px</code>.</p>
<p>The above CSS is from a specific example from one of my documentation Web sites generated with <a href="https://documentationmonster.com">Documentation Monster</a>, which originally used <code>@media</code> queries in its topic templates used for rendering topics. DM renders the final output into a two-pane view that contains a table of contents on the left and a resizable content pane on the right with a splitter in the middle. The content pane then conditionally displays a left hand sidebar that shows a document outline if the pane is large enough.</p>
<p>The following screen capture demonstrates some of the problems with <code>@media</code> only queries. Keep an eye on the bookmark sidebar (<em>&quot;On this page:&quot;</em>) on the right. The intent is that as the content panel gets smaller than a certain size, the bookmark toolbar disappears and reappears as the width expands.</p>
<p><img src="https://raw.githubusercontent.com/RickStrahl/ImageDrop/master/BlogPosts/2026/mm-docs-container-sizing-media.gif" alt="mm docs container sizing media">
<small class="text-lowlight"><strong>Figure 1</strong> - Using <code>@media</code> queries allows sizing per window, which can cause problems if you have resizable <strong>content</strong> that needs to conditionally render or modify content. Here you can see some inconsistent behavior for the bookmarks sidebar and content pane when then content pane is resized.</small></p>
<p>If you look closely you can see that the initial full window resizes work properly with the sidebar showing and hiding as expected.</p>
<p>However, resizing with the splitter from a size where the bookmark sidebar shows, to a size where it should hide, you can see the sidebar causing the content to get crunched, rather than hiding the sidebar and allowing the content to flow wider which is the expected and designed behavior. It works with Window resizing but fails with container resizing. Similar behavior shows with the hamburger menu that is showing and hiding the TOC.</p>
<p>This example uses the <code>@media</code> at-rule, and it's misbehaving because the sizing condition is based on <strong>window sizing</strong>, not container sizing. The result is that in some instances you don't get the right behavior of the sidebar showing or hiding as it should.</p>
<p>Fixing this is not possible with <code>@media</code> queries alone, because it only works as expected when the entire window size changes.</p>
<p>Note that in this scenario the <code>@media</code> query also misrepresents the intent of the size restriction. Specifically in Documentation Monster there's a preview that uses the same HTML but always hides the TOC on the left. So right of the start the <code>@media</code> query sizing in that scenario is off by the 350px or so of the TOC panel. IOW the intent of the sizing here is clearly on the container, not the window, but a <code>@media</code> query alone doesn't really address that. There are ways around that with extra CSS class definitions but that gets messy quick as you have to duplicate many CSS attributes.</p>
<p><a href="https://open.spotify.com/track/174gSk2IavDs7h8d715468" target="top"
		  title="Anti-Trust - The Masters of Disaster">
<img src="https://weblog.west-wind.com/images/sponsors/TheMastersOfDisaster-Display.png" class="da-content-image" />
</a></p>
<h2 id="container-queries">@container Queries</h2>
<p>The <code>@container</code> at-rule fixes this by explicitly allowing you to specify a container to restrict by for the query condition. So rather than being tied to the window, a <code>@container</code> query is scoped to a specific container.</p>
<p>To get back to the Documentation example shown above here's is the example behaving correctly with the <code>@container</code> query:</p>
<p><img src="https://raw.githubusercontent.com/RickStrahl/ImageDrop/master/BlogPosts/2026/mm-docs-container-sizing.gif" alt="mm docs container sizing"><br>
<small class="text-lowlight"><strong>Figure 2</strong> - Using <code>@container</code> queries allows more control by selecting a specific container to apply a media query to. Here you can see the bookmarks sidebar correctly diplaying depending on the content container only.</small></p>
<p>Notice that now, using the slider or hamburger menu correctly shows and hides the bookmark sidebar as the the sizing is now dependent on the size of the content pane, rather than the size of the window.</p>
<p>Yay.</p>
<h3 id="container-syntax-a-two-step-process">@container Syntax: A two-step Process</h3>
<p>To use the <code>@container</code> at-rule in the same way as the <code>@media</code> query above, is a two part process:</p>
<ul>
<li><p><strong>Setting a <code>container-name</code> in the parent CSS Class</strong><br>
The parent container which is targeted by <code>@container</code> needs to be explicitly defined in the CSS with something like <code>container-name: main-content</code> to be used as a parent in a <code>@container</code> query.</p>
</li>
<li><p><strong>Applying the <code>@container</code> at-rule</strong><br>
Once a container has been defined with <code>container-name</code> you can then use it with:</p>
<pre><code class="language-css">@container main-content (min-width: 1100px) { /* css styles */  }
</code></pre>
<p>The behavior otherwise is identical to a <code>@media</code> query block including the same <code>min-width</code>/<code>max-width</code> rules.</p>
</li>
</ul>
<blockquote>
<h5 id="--a-space-after-the-name-is-required"><i class="fas fa-warning" style="font-size: 1.1em"></i>  A Space after the Name is Required!</h5>
<p>When I originally found out about <code>   </code>, I made the mistake of writing it <code>@container main-content(min-width: 1100px)</code>. Notice no space between the name and the <code>()</code> query condition and that did not work. And yes I spent 20 minutes cursing that this feature doesn't work, only to have an agent slap me in the face with the space requirement 😄. So:</p>
<p><strong>The space after the name is absolutely required!</strong></p>
<pre><code class="language-css">@container main-content (min-width: 1100px)
                       ^
</code></pre>
</blockquote>
<h3 id="no-container-name-works-too-but">No Container Name works too, but...</h3>
<p>Actually, the <code>container-name</code> and specification in the <code>@container</code> are not absolutely required. If you don't specify one, the <strong>eligible ancestor query container(s)</strong> of the style(s) in the CSS block are used for the container it's applied to.  So essentially the parent element(s)  of each of the styles represented.</p>
<p>This can be pretty arbitrary, and because of that I personally prefer to be explicit and provide a specific container via <code>container-name</code>.</p>
<p>So in this CSS:</p>
<pre><code class="language-css">@container (width &gt; 920px) {
    .topic-outline {
        display: block;
    }

    .content-pane {
        margin-right: 235px;
    }

    .footer {
        font-size: 0.9rem;
    }
}
</code></pre>
<p>Each style's parent container is considered rather than a single container. As you might imagine this can get confusing quickly.</p>
<p>If you don't use a <code>container-name</code> I'd make sure to stick to embedding a single style or at least any styles that have the same parent. Alternately if you want to control several elements, create a new CSS class and apply the <code>container-name</code> to it, then assign the class to all affected elements to be explicit. This avoids confusing and hard to debug scenarios later on.</p>
<h3 id="applying-the-container-at-rule">Applying the @container at-rule</h3>
<p>Let's break down the full syntax:</p>
<p>Start by setting a <code>container-name</code> in the container element that the resizing should be based on. I'm using an #id here, but you can point at any CSS selector:</p>
<pre><code class="language-css">#MainContent {
    container-name: main-content;
    container-type: inline-size;
}
</code></pre>
<p>In my case I have an explicit Id for the content container so I used that. If you don't have an explicit CSS style defined that matches your container exactly, you need to create one and add <code>container-name</code>.</p>
<p>Once you have <code>container-name</code> defined in your CSS, you can then reference the it in the <code>@container</code> directive:</p>
<pre><code class="language-css">@container main-content (min-width: 920px){
   .topic-outline {
       position: fixed;
       display: block;
       overflow-y: auto;

       top: 7em;
       right: 1em;
       max-height: calc(100vh - 10em);
       width: 255px;

       font-size: 0.725em !important;
       line-height: 1.4em;

       margin-top: 0px;
       margin-left: 10px;
       padding: 20px 3px 0 20px;
   }
@container main-content (min-width: 1100px) {
        .topic-outline {
            width: 400px;
            font-size: 0.725em !important;
        }
        .content-pane.topic-outline-visible {
            margin-right: 380px;
        }
    }
}   
</code></pre>
<p>And that's it!</p>
<h2 id="summary">Summary</h2>
<p>CSS <code>@container</code> queries provide a much-needed solution for container-level responsive design where viewport-based <code>@media</code> queries fall short.</p>
<p>With a few very minor changes to the CSS, I've resolved a very long running nagging CSS issue, that I put up with for a long time. At its original inception this issue had no quick resolution because <code>@container</code> didn't exist at the time other than resorting to script code.</p>
<p>But now, with this relatively new <code>@container</code> feature I get the intended behavior with minimal effort and a very simple change path from the original behavior. Nice!</p>
<p><code>@container</code> was new to me since it is a relatively new at-rule, so it might be new to you too, so I hope this is useful to some of you...</p>
<p><a href="https://goldenbeartshirts.com"  target="_blank"
	  title="Pickleballs and Bears">
<img src="https://weblog.west-wind.com/images/Sponsors/PickleBallBearTshirt-Display.jpg" class="da-content-image" alt="Pickleball Bear TShirt at Goldenbear T-Shirts"/>
</a></p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@container">@container MDN Documentation</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media">@media MDN Documentation</a></li>
</ul>
<div style="margin-top: 30px;font-size: 0.8em;
            border-top: 1px solid #eee;padding-top: 8px;">
    <img src="https://markdownmonster.west-wind.com/favicon.png" style="height: 20px;float: left; margin-right: 10px;">
    this post was created and published with the 
    <a href="https://markdownmonster.west-wind.com" target="top">Markdown Monster Editor</a> 
</div>
]]></description>
      <link>https://weblog.west-wind.com/posts/2026/Oct/02/Back-to-Basics-CSS-container-Filtering</link>
      <guid isPermaLink="false">znxhm5bohh8q</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Oct/02/Back-to-Basics-CSS-container-Filtering#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Oct/02/Back-to-Basics-CSS-container-Filtering</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[While viewport-based `    ` queries are a staple of responsive web design, they fall short in dynamic or resizable multi-pane layouts. The newish CSS `@container` at-rule solves this by allowing styles to adapt based on the size of a specific parent container rather than the entire browser window. In this post, explore the differences between `@media` and `@container`, and describe how you can quickly move queries from `@media` to `@container` for greater flexibility.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/2026-10/ContainerBanner.jpg</featuredImage>
    </item>
    <item>
      <title>Protecting File Access in the wwwroot Folder in ASP.NET</title>
      <description><![CDATA[<p><img src="https://weblog.west-wind.com/imageContent/2026/Protecting-File-Access-in-the-wwwroot-Folder-in-ASP-NET/wwwrootFileProtectionBanner.jpg" alt="wwwroot File Protection Banner"></p>
<p>In many of my Web applications I have a few files that are dynamically created as part of administrative tasks. These files tend to be cumulative and contain data that can be accessed directly from the file system. But... they are in effect <em>static</em> files, but they shouldn't be accessible by just anybody. They should still respect authentication rules.</p>
<p>For example, in several <strong>small apps</strong> I write application error logs into <code>/wwwroot/admin/temp</code>. In another application that does occasional order processing, I track processing errors in files in a similar manner.</p>
<p>But again - these files can potentially contain sensitive information so they shouldn't be accessible by just any user, but only by logged in Admin users or whatever elevated group of users has access to that type of information.</p>
<p><a href="https://markdownmonster.west-wind.com?ut=weblog"  target="_blank"
	  title="Markdown Monster - Easy to use, yet powerfully productive Markdown Editing for Windows">
<img src="https://weblog.west-wind.com/images/sponsors/MarkdownMonster-Display2.jpg" class="da-content-image" />
</a></p>
<h2 id="what-exactly-does-wwwroot-do">What exactly does wwwroot do?</h2>
<p>The <code>wwwroot</code> folder in your project is the '<em>static files</em>' folder for your ASP.NET Web application. As the name suggests it assumes static content that doesn't change.</p>
<h3 id="wwwroot-is-a-aspnet-project-thang">wwwroot is a ASP.NET Project Thang</h3>
<p>The way it works is that in your ASP.NET projects anything that you store in the <code>wwwroot</code> folder is mapped into the <strong>Web root</strong> of the published Web site so that a <code>/wwwroot/somefile.html</code> file maps to <code>/somefile.html</code> on your published Web site.</p>
<p>It's also possible to publish sites into subfolders or virtual folders/sites/applications (on IIS) in which case <code>wwwroot</code> maps to <code>/subfolder/somefile.html</code>.</p>
<ul>
<li><a href="https://weblog.west-wind.com/posts/2026/May/26/Running-ASPNET-Core-Applications-in-an-IIS-Subfolder-Application">Running ASP.NET Core Applications as a Subfolder Application</a></li>
</ul>
<p>The key here is that by default (if you don't mess with the property publish settings) all files in the <code>wwwroot</code> folder are published to the Web root on the publish site.</p>
<h3 id="static-file-middleware">Static File Middleware</h3>
<p>In order for static files to be actually served by ASP.NET you need to add the <code>StaticFiles</code> middleware in your app's startup code in <code>program.cs</code>:</p>
<pre><code class="language-cs">app.UseStaticFiles()
</code></pre>
<p>The middleware handles the actual semantics of translating paths into static files, and also provides a host of features like virtualization of file providers, content compression and caching.</p>
<p>By default everything in the <code>wwwroot</code> folder is <strong>published</strong> and then served as <strong>public</strong> by the middleware and directly accessible by anybody browsing your site. ASP.NET doesn't restrict access to static content by default.</p>
<p>In most cases that's exactly what you want. Static content mostly consists of support resources for your project:</p>
<ul>
<li>Images</li>
<li>CSS</li>
<li>JavaScript</li>
<li>Static Html</li>
<li>Media content</li>
<li>Downloadable files</li>
</ul>
<p>and in most cases that content <strong>should</strong> be directly accessible without any authentication or other restrictions beyond site level security.</p>
<p>The static file middleware is not limited to what you publish in your <code>wwwroot</code> folder. Anything else you manually copy into the Web Root folder or dynamically create and then store there is also accessible. The default file provider points at a folder and serves <strong>all files</strong> in and below that folder.</p>
<blockquote>
<h5 id="--static-files-middleware-can-do-more"><i class="fas fa-lightbulb" style="font-size: 1.1em"></i>  Static Files Middleware can do More!</h5>
<p>The default use case for the static files middleware is to serve root Web folder content from published <code>wwwroot</code> output, but you can use this middleware for many different scenarios by using alternate or customized the file providers (<code>IFileProvider</code>). For starters, you can point at a different folder than <code>wwwroot</code> or point at multiple folders to serve static files from, which is particularly useful for serving application specific resources that are stored separately from the Web app.</p>
<p>There are several different types of file stores that can be used to serve files from Resources, Azure etc. and you can implement your own <code>IFileProvider</code> implementations that serve from different sources.</p>
</blockquote>
<h2 id="protecting-folders-or-files-in-wwwroot">Protecting Folders or Files in wwwroot</h2>
<p>But... in some cases <strong>you do need to protect dynamically created files</strong> that are served as static content. Technically that's not static content, since it was generated, but it lives in the scope of the Static File middleware.</p>
<p>There's no 'built-in' way to secure static files served from the StaticFile middleware. If the middleware fires, the files get served.</p>
<p>So in order to limit access a separate approach is required that explicitly prohibits access to specific paths.</p>
<p>There are a few ways to do this, none of them particularly clean:</p>
<ul>
<li><p><strong>Move files outside of <code>wwwroot</code></strong><br>
One thing that can be done is move files out of wwwroot and then explicitly  have endpoints that either map individual files or wildcard catch-all routes for files.</p>
</li>
<li><p><strong>Use endpoint-routing with Catch All route</strong>
You can send minimal API routes or controller routes with <strong>catch-all</strong> route parameters (<code>{*path}</code> or <code>{**path}</code>) that give you the remaining path of the Url.</p>
</li>
<li><p><strong>Use custom middleware</strong>
Similarly you can create a generic Middleware handler that looks at all requests and explicitly filter out requests.</p>
</li>
</ul>
<p><code>StaticFileOptions.OnPrepareResponse</code> is sometimes suggested for this job, but I don't like it as an authorization boundary. That callback runs after the static-file middleware has already selected a file and prepared the response. It's useful for setting headers, but rejecting the request before static-file processing is easier to reason about.</p>
<p>There are other approaches I've seen suggested using custom Authorization policies and complex routing setups, but that all seems insanely complex for what is essentially a relatively simple task.</p>
<p>The simplest thing seems to be, intercepting the request either with a generic middleware handler, or a catch all route. Both of these use essentially the same concept of looking at the Url and deciding whether requests can go through or not - and if not forcing a login (or at least a 401 result).</p>
<p>My personal preference is the custom middleware which I'll describe below.</p>
<h2 id="use-custom-middleware">Use Custom Middleware</h2>
<p>For an existing application that already writes files below <code>wwwroot</code>, the least disruptive solution is often a small generic middleware handler that examines the request path and explicitly rejects requests that aren't authorized.</p>
<p>Here's the relevant part of my application setup which requires authentication for any file requests comes out of an <code>/admin</code> folder:</p>
<pre><code class="language-csharp">app.UseRouting();

// Authentication has to run before checking ctx.User
app.UseAuthentication();

// Protect everything below /admin, including static files
// has to run before the static files middleware
app.Use(async (ctx, next) =&gt;
{
    if (ctx.Request.Path.StartsWithSegments(
            &quot;/admin&quot;, StringComparison.OrdinalIgnoreCase))
    {
        if (ctx.User.Identity?.IsAuthenticated != true)
        {
            ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await ctx.Response.WriteAsync(&quot;401 Unauthorized&quot;);
            return;
        }

        // Retrieve application user state from claims and validate access
        var userState = UserState.CreateUserState&lt;WebStoreAppUserState&gt;(ctx);
        if (!userState.IsAdmin)
        {
            ctx.Response.StatusCode = StatusCodes.Status403Forbidden;
            await ctx.Response.WriteAsync(&quot;403 Forbidden&quot;);
            return;
        }
    }

    await next();
});

// Static files are served only after the /admin check
app.UseStaticFiles();

app.UseAuthorization();
</code></pre>
<p>The two <code>return</code> statements are important: denied requests must <strong>short-circuit the middleware pipeline</strong>. Setting a status code and then calling <code>next()</code> still allows the static-file middleware to process the request.</p>
<p>I'm using application-specific <code>UserState</code> code here, but you can replace that check with <code>ctx.User.IsInRole(&quot;Admin&quot;)</code>, a claim check, or whatever authorization logic your application uses.</p>
<p><code>StartsWithSegments()</code> also matters. A string test such as <code>Contains(&quot;/admin/&quot;)</code> can match unintended URLs and misses the exact <code>/admin</code> path. Segment matching clearly scopes the check to <code>/admin</code> and everything below it.</p>
<p>Finally, middleware order is critical:</p>
<ul>
<li><code>UseAuthentication()</code> has to run before the custom check so that <code>ctx.User</code> is available for my check scenario.</li>
<li>The custom check has to run <strong>before</strong> <code>UseStaticFiles()</code> so a rejected request never reaches static-file handling.</li>
<li>If a front-end Web server serves the file directly, none of this code runs. More on that shortly.</li>
</ul>
<h3 id="why-mapget-doesnt-intercept-the-file">Why MapGet() Doesn't Intercept the File</h3>
<p>A similar approach is to use minimal APIs or a controller endpoint with a catch-all route. For minimal APIs you can use a catch-all <code>MapGet()</code> handler such as <code>app.MapGet(&quot;/admin/{**path}&quot;)</code>.</p>
<blockquote>
<h5 id="--single-vs-double-asterisk-catch-all-parameters"><i class="fas fa-lightbulb" style="font-size: 1.1em"></i>  Single vs Double Asterisk Catch-All Parameters</h5>
<p>Catch-all routing parameters can use either one or two asterisks: <code>{*path}</code> or <code>{**path}</code>. Both match the remaining part of a URL, including slashes and other characters, and can also match an empty string.</p>
<p>The single-asterisk version URL-encodes forward slashes when generating a URL, while the double-asterisk version preserves them.</p>
</blockquote>
<p>Unfortunately, in my application, which uses controller routing and static-file middleware, the <code>MapGet()</code> handler never fired for an existing static file.</p>
<p>The following did not intercept the request:</p>
<pre><code class="language-csharp">app.MapGet(&quot;/admin/{**path}&quot;, async (HttpContext ctx, string path) =&gt;
{
  return &quot;Path: &quot; + path;
});

app.UseStaticFiles();
app.UseRouting();
</code></pre>
<p>This isn't really a route-priority problem. Static-file middleware handles a matching file and short-circuits the pipeline before the mapped endpoint executes. Moving declarations around in a Minimal API application's compact startup code can make this behavior less than obvious because route registration and middleware execution aren't the same thing.</p>
<p>You can make endpoint routing work by explicitly arranging the pipeline, or by having the endpoint serve the file itself. But for guarding an existing <code>UseStaticFiles()</code> setup, I find the slightly lower-level <code>app.Use()</code> handler more obvious and unambiguous.</p>
<p>In .NET 9 and later, <code>MapStaticAssets()</code> exposes static assets as endpoints and authorization metadata can be attached to those endpoints. That's useful for build-time application assets, but my files are generated at runtime and only one folder needs protection, so the small middleware check remains a better fit here.</p>
<p>But given the possibility of routing conflicts with <code>.MapGet()</code> or even an explicit route, I'm weary of using that in the future and just opt for the simpler solution of using a generic middleware handler with <code>app.Use()</code> as shown above. It's clear and straight forward and there's no second guessing on when it fires other than pipeline order.</p>
<h2 id="watch-out-for-your-hosting-platform">Watch out for your Hosting Platform</h2>
<p>Here's another gotcha you may have to watch for:</p>
<p>If you're running your ASP.NET Core application behind a Web server proxy, be aware that the server may be configured to serve static resources directly, effectively bypassing ASP.NET Core processing entirely!</p>
<p>Many of my Web applications still run on a self-hosted IIS VPSs.</p>
<p>In all of my IIS hosted apps I forward most common static file types directly through IIS because it's considerably faster than running them through the ASP.NET Core pipeline, and IIS provides automatic and highly efficient compression for text-based files:</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;configuration&gt;
	&lt;location path=&quot;.&quot; inheritInChildApplications=&quot;false&quot;&gt;
		&lt;system.webServer&gt;
            &lt;handlers&gt;
                &lt;!-- Watch out for this one if you're protecting HTML files! --&gt;
                &lt;add name=&quot;StaticFileModuleHtml&quot; path=&quot;*.htm*&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;

				&lt;add name=&quot;StaticFileModuleSvg&quot; path=&quot;*.svg&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModuleJs&quot; path=&quot;*.js&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModuleCss&quot; path=&quot;*.css&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModuleJpeg&quot; path=&quot;*.jpeg&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModuleJpg&quot; path=&quot;*.jpg&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModulePng&quot; path=&quot;*.png&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModuleGif&quot; path=&quot;*.gif&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModuleWoff2&quot; path=&quot;*.woff2&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;
				&lt;add name=&quot;StaticFileModuleWoff&quot; path=&quot;*.woff&quot; verb=&quot;*&quot; modules=&quot;StaticFileModule&quot; resourceType=&quot;File&quot; requireAccess=&quot;Read&quot; /&gt;

				&lt;!-- Everything else goes into Kestrel --&gt;
				&lt;add name=&quot;aspNetCore&quot; path=&quot;*&quot; verb=&quot;*&quot; modules=&quot;AspNetCoreModuleV2&quot; resourceType=&quot;Unspecified&quot; /&gt;
			&lt;/handlers&gt;
		&lt;/system.webServer&gt;
	&lt;/location&gt;
&lt;/configuration&gt;
</code></pre>
<p>If you do this and have a mapping for an affected extension (like <code>*.htm*</code> in this case for example), IIS serves the matching file without forwarding the request to Kestrel. Your entire ASP.NET Core pipeline, including authentication and the low-level middleware check, never fires. Everything discussed above is <strong>completely bypassed</strong>.</p>
<p>In my case this doesn't matter because the files I'm concerned about tend to use <code>.txt</code> or <code>.log</code> extensions, which I don't forward through IIS. But if you need to protect HTML documents, for example, you have to remove the <code>path=&quot;*.htm*&quot;</code> handler so those requests reach ASP.NET Core and the authorization check can fire or add a special <code>&lt;location&gt;</code> sub-section that explicitly excludes the files from IIS processing.</p>
<blockquote>
<p>On the flip side it's also possible to explicitly set file or folder permissions to block access to files using <code>&lt;location&gt;</code> element. However, the auth mechanism used is then outside of the ASP.NET application (Windows Auth?) which is at best... hokey. But for a quick fix if you find out you have a file that needs protecting immediately, that might do the trick.</p>
</blockquote>
<p>The same warning applies to CDNs, reverse proxies and container ingress configurations that can serve files without reaching the application. Always test the request through the actual production hosting path, not just against local debug environment which <strong>always hits Kestrel</strong>.</p>
<p><a href="https://www.linqpad.net/?affiliate=4n4zaa6t"  target="_blank"
		  title="LinqPad - The Scratchpad for .NET Developers">
<img src="https://weblog.west-wind.com/images/sponsors/LinqPad-DisplayAd.jpg" class="da-content-image" />
</a></p>
<h2 id="summary">Summary</h2>
<p>By default, ASP.NET Core serves all content within the wwwroot directory as public static assets without authorization checks. When applications write sensitive or dynamically generated files (such as error logs or admin reports) into wwwroot, access needs to be restricted.</p>
<p>The most straightforward approach described in this post, is to insert a lightweight custom middleware into the pipeline to explicitly reject requests that are not authenticated. In order to do this it's important to get the order of middleware right so that the checks occur before static files are served but after authentication has provided the necessary user context to decide whether requests can proceed or not.</p>
<p>Officially there are quite a few approaches that can be used but personally I prefer this much more simple and explicit approach to using code to examine path and auth to accept or reject requests, vs complex authorization policies or complex routing order schemes that are often mentioned.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-10.0">Serve static files in ASP.NET Core apps</a></li>
<li><a href="https://weblog.west-wind.com/posts/2026/May/26/Running-ASPNET-Core-Applications-in-an-IIS-Subfolder-Application">Running ASP.NET Core Applications as a Subfolder Application</a></li>
</ul>
<div style="margin-top: 30px;font-size: 0.8em;
            border-top: 1px solid #eee;padding-top: 8px;">
    <img src="https://markdownmonster.west-wind.com/favicon.png" style="height: 20px;float: left; margin-right: 10px;">
    this post was created and published with the 
    <a href="https://markdownmonster.west-wind.com" target="top">Markdown Monster Editor</a> 
</div>
]]></description>
      <link>https://weblog.west-wind.com/posts/2026/Sep/14/Protecting-File-Access-in-the-wwwroot-Folder-in-ASPNET</link>
      <guid isPermaLink="false">29ve0fkm6v0k</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Sep/14/Protecting-File-Access-in-the-wwwroot-Folder-in-ASPNET#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Sep/14/Protecting-File-Access-in-the-wwwroot-Folder-in-ASPNET</guid>
      <pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[ASP.NET Core treats files in wwwroot as public static content, but occasionally applications create files there that should only be available to authorized users. In this post I look at several ways to protect those files and show a small middleware solution that lets selectively access files easily.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/Protecting-File-Access-in-the-wwwroot-Folder-in-ASP-NET/wwwrootFileProtectionBanner.jpg</featuredImage>
    </item>
    <item>
      <title>Back to Basics: JavaScript and Timezones</title>
      <description><![CDATA[<p><img src="https://weblog.west-wind.com/imageContent/2026/JavaScript-and-Timezones/TimeZoneBanner.jpg" alt="Time Zone Banner"></p>
<p>Classic native <code>Date</code> values in JavaScript are internally represented as UTC dates. However, when you display values they are by default shown as local date/time values. I've run into this recently again with an application that was returning a mix of UTC and non-UTC dates from the server to the browser, causing some confusion. At that point I realized I was being inconsistent with my use of the my date display functions, and here I am reviewing various Date formatting and Date conversion operations.</p>
<p>JavaScript's core behavior is consistent once you separate two different concepts:</p>
<ul>
<li>The instant represented by a Date</li>
<li>The timezone used to display that instant</li>
</ul>
<p>An instant is essentially an instant in time expressed in an offset since the <strong>Unix Epoch</strong>:</p>
<p><strong>January 1, 1970 00:00:00 UTC</strong></p>
<p>The instant is represented via <code>new Date().getTime()</code> or a more precise value of <code>Temporal.Instant</code> which represents the unique point in time, with nanosecond precision. It is fundamentally represented as the number of nanoseconds since the Unix epoch (midnight at the beginning of January 1, 1970, UTC), without any time zone or calendar system.</p>
<p><a href="https://markdownmonster.west-wind.com?ut=weblog"  target="_blank"
	  title="Markdown Monster - Easy to use, yet powerfully productive Markdown Editing for Windows">
<img src="https://weblog.west-wind.com/images/sponsors/MarkdownMonster-Display2.jpg" class="da-content-image" />
</a></p>
<p>The most common usage of dates is via the <code>Date</code> object and its methods. Dates are stored as UTC values, but standard display of dates in the browser UI or via the debug tools shows dates in <strong>local time</strong>.</p>
<p>So when you output a date it looks like this (<code>PST</code> or <code>America/Los Angeles</code> timezone in this case):</p>
<pre><code class="language-js">new Date()
// Mon Aug 24 2026 11:33:26 GMT-0700 (Pacific Daylight Time)
</code></pre>
<p>my current <code>PST</code> timezone value is displayed with <code>toString()</code> or any string concat operation. Notice that the date formatter automatically adjusts for Daylight savings time of the active timezone.</p>
<p>I also get a time adjusted value, if I explicitly convert the value using <code>toLocaleTimeString()</code> which gives me just the time value as a string:</p>
<pre><code class="language-js">new Date().toLocaleTimeString()
// '11:33:46 AM'
</code></pre>
<p>I can also go the other way - if I want to actually display the UTC value I can use the following:</p>
<pre><code class="language-js">new Date().toLocaleTimeString(undefined, { timeZone: &quot;UTC&quot; });
'6:33:43 PM'
</code></pre>
<p>I can also specify any specific defined timezone using the familiar timezone abbreviations:</p>
<pre><code class="language-js">new Date().toLocaleTimeString(undefined, { timeZone: &quot;EST&quot; });
// '1:34:02 PM'
</code></pre>
<p>or using a full <a href="https://timeapi.io/documentation/iana-timezones">IANA Time Zones</a> identifier:</p>
<pre><code class="language-js">new Date().toLocaleTimeString(undefined, { timeZone: &quot;America/New_York&quot; });
// '1:34:02 PM'
</code></pre>
<blockquote>
<h5 id="--get-your-local-timezone"><i class="fad fa-lightbulb" style="font-size: 1.1em"></i>  Get your Local Timezone</h5>
<p>If you're like me you're probably used to seeing timezone values represented by their shortcut names like <code>PST</code>, <code>EST</code>, <code>CET</code> etc., but officially JavaScript uses <a href="https://timeapi.io/documentation/iana-timezones">IANA Time Zones</a> which are represented like <code>America/Los_Angeles</code>, <code>Pacific/Honolulu</code>, <code>Europe/Berlin</code> etc. There are actually many overlapping formats such as <code>Pacific/Honolulu</code>, <code>US/Hawaii</code> and <code>HST</code> for my Hawaii timezone that all represent the same UTC -10 timezone.</p>
<p>I'm in Hood River (near Portland, OR) currently, but the active PST timezone that gets returned is <code>America/Los_Angeles</code>, which seems semantically very wrong, but does represent the correct UTC -8 timezone.</p>
<p>You can find out the local timezone like this:</p>
<pre><code class="language-js">Intl.DateTimeFormat(undefined, { timeZoneName: &quot;short&quot; })
   .resolvedOptions().timeZone;
// 'America/Los_Angeles'
</code></pre>
<p>or for the abbreviation.</p>
<pre><code class="language-js">new Intl.DateTimeFormat(undefined, { timeZoneName: &quot;short&quot; })
.formatToParts(new Date())
.find(x =&gt; x.type === &quot;timeZoneName&quot;)?.value;
// PDT
</code></pre>
</blockquote>
<h2 id="dates-are-instants-display-is-local">Dates are Instants, Display is Local</h2>
<p>The most important thing to keep in mind is this:</p>
<pre><code class="language-js">const date = new Date(&quot;2026-08-24T18:33:46.000Z&quot;);

date.toISOString();
// '2026-08-24T18:33:46.000Z'

date.toString();
// 'Mon Aug 24 2026 11:33:46 GMT-0700 (Pacific Daylight Time)'
</code></pre>
<p><code>toISOString()</code> returns the instance UTC value with no date time offset regardless of whether the instance has a timezone or not ie. <code>2026-08-24T18:33:46.000Z</code>. The <code>Z</code> at the end indicates the UTC timezone.</p>
<p><code>toString()</code> and most other date output functions return <strong>local time</strong> values adjusted for the current timezone.</p>
<p>This is where JavaScript can be confusing because many <code>Date</code> methods are split between local-time and UTC-time versions:</p>
<pre><code class="language-js">const date = new Date(&quot;2026-08-24T18:33:46.000Z&quot;);

date.getHours();
// 11   local time for me

date.getUTCHours();
// 18   UTC time
</code></pre>
<p>Yeah that <code>.getHours()</code> value is confusing if the time instance is a UTC value, but the bottom line is that any of the 'standard' date operations use local dates unless you explicitly use the UTC or ISO functions.</p>
<p><a href="https://documentationmonster.com?ut=weblog"  target="_blank"
	  title="Documentation Monster - Creating documentation one Markdown at a time">
<img src="https://weblog.west-wind.com/images/Sponsors/DocumentationMonster-Display.jpg" alt="Documentation Monster" class="da-content-image" />
</a></p>
<h2 id="formatting-dates-with-intldatetimeformat">Formatting Dates with Intl.DateTimeFormat</h2>
<p>You can use <code>toLocaleString()</code> to format dates and times, but if you want more control you can use the <code>Intl.DateTimeFormat</code> object which makes formatting more explicit and reusable.</p>
<pre><code class="language-js">const formatter = new Intl.DateTimeFormat(undefined, {
	dateStyle: &quot;medium&quot;,
	timeStyle: &quot;short&quot;
});

formatter.format(new Date(&quot;2026-08-24T18:33:46.000Z&quot;));
// 'Aug 24, 2026, 11:33 AM'  // on my machine
</code></pre>
<p>The first parameter of the constructor is the <strong>locale</strong> ie. <code>en-US</code> or <code>de-DE</code>. Passing <code>undefined</code> use the current user's locale.</p>
<p>To override for a specific timezone you can explicitly specify it as part formatting parameters for <code>timeZone</code>. To explicitly show <code>UTC</code> time:</p>
<pre><code class="language-js">const utcFormatter = new Intl.DateTimeFormat(undefined, {
	year: &quot;numeric&quot;,
	month: &quot;short&quot;,
	day: &quot;numeric&quot;,
	hour: &quot;numeric&quot;,
	minute: &quot;2-digit&quot;,
	timeZone: &quot;UTC&quot;,
	timeZoneName: &quot;short&quot;
});

utcFormatter.format(new Date(&quot;2026-08-24T18:33:46.000Z&quot;));
// 'Aug 24, 2026, 6:33 PM UTC'
</code></pre>
<p>To show a specific user's timezone formatted for a specific locale:</p>
<pre><code class="language-js">const newYorkFormatter = new Intl.DateTimeFormat(&quot;en-US&quot;, {
	year: &quot;numeric&quot;,
	month: &quot;short&quot;,
	day: &quot;numeric&quot;,
	hour: &quot;numeric&quot;,
	minute: &quot;2-digit&quot;,
	timeZone: &quot;America/New_York&quot;,
	timeZoneName: &quot;short&quot;
});

newYorkFormatter.format(new Date(&quot;2026-08-24T18:33:46.000Z&quot;));
// 'Aug 24, 2026, 2:33 PM EDT'
</code></pre>
<h3 id="serve-me-this-client-me-that">Serve me this, Client me That</h3>
<p>The way that timezone display works in browsers makes sense 99% of the time as you typically want to display the timezone as local in the client browser.</p>
<p>But it gets a lot more complicated when the dates are server rendered or passed from the server to the client via API calls. The issue here is that the dates on the server might be stored for a specific timezone and perhaps are even returned with that timezone information encoded into the JSON payload.</p>
<p>This is why most applications store dates dates as UTC and then adjust the value on the client or on the server based on user preferences configured.</p>
<p>UTC dates are most consistent, but for users displaying a UTC date is definitely not ideal. It might work for logs or other systems related information, but for end users local time or a selected timezone display is a requirement.</p>
<p>For many applications I like the second option for user-facing screens: send the timestamp as UTC or ISO 8601 in the HTML, then let client-side JavaScript render it into the user's actual local timezone - potentially adjusted for a user specified timezone.</p>
<p>For example:</p>
<pre><code class="language-html">&lt;time datetime=&quot;2026-08-24T18:33:46.000Z&quot; class=&quot;local-time&quot;&gt;
	2026-08-24 18:33 UTC
&lt;/time&gt;
</code></pre>
<p>and then for browser local time:</p>
<pre><code class="language-js">const formatter = new Intl.DateTimeFormat(undefined, {
	year: &quot;numeric&quot;,
	month: &quot;short&quot;,
	day: &quot;numeric&quot;,
	hour: &quot;numeric&quot;,
	minute: &quot;2-digit&quot;,
	timeZoneName: &quot;short&quot;
});

document.querySelectorAll(&quot;time.local-time&quot;).forEach(element =&gt; {
	const date = new Date(element.dateTime);
	element.textContent = formatter.format(date);
});
</code></pre>
<p>For a specific timezone you can specify the timezone in the constructor:</p>
<pre><code class="language-js">let timeZone = userConfig.timeZone;
const formatter = new Intl.DateTimeFormat(undefined, {
	year: &quot;numeric&quot;,
	month: &quot;short&quot;,
	day: &quot;numeric&quot;,
	hour: &quot;numeric&quot;,
	minute: &quot;2-digit&quot;,
    timeZone: timeZone,  // America/Los Angeles or PST
	timeZoneName: &quot;short&quot;
});
</code></pre>
<p>This gives non-JavaScript clients and crawlers a useful UTC fallback, while normal browser users get a local or user specified timezone display value.</p>
<h4 id="getting-the-users-timezone-for-the-server">Getting the user's TimeZone for the Server</h4>
<p>In order for client applications to provide the timezone to the server you can detect the value on the client like this:</p>
<pre><code class="language-js">Intl.DateTimeFormat().resolvedOptions().timeZone;
// 'America/Los_Angeles'
</code></pre>
<p>and then send that to the server. AFAIK the client does not send timezone information to the server as part of browser headers by default, so some explicit API call or similar operation is required to communicate the default to the server.</p>
<h2 id="new-javascript-apis">New JavaScript APIs</h2>
<p>The classic JavaScript <code>Date</code> object has been around forever and it's not going away. It works fine for many things, especially if you treat it as an instant and use <code>Intl.DateTimeFormat</code> for display.</p>
<p>But <code>Date</code> is a pretty limited API. It mixes parsing, timestamps, local timezone accessors, UTC accessors, mutable setters, and display helpers into a single object. That's a lot of behavior packed into one small type.</p>
<h3 id="the-newish-temporal-api">The newish Temporal API</h3>
<p>The newer API to keep an eye on is <code>Temporal</code>. Depending on the JavaScript runtime you're targeting you may still need a polyfill, so <a href="https://caniuse.com/temporal">check current browser and runtime support</a> before using it directly in production code. But conceptually it's a much better model because it separates the things that <code>Date</code> blends together.</p>
<p>The important Temporal types are:</p>
<ul>
<li><code>Temporal.Instant</code> - a point in time, like a better <code>Date</code> timestamp</li>
<li><code>Temporal.PlainDate</code> - a calendar date without time or timezone</li>
<li><code>Temporal.PlainDateTime</code> - a date and time without timezone</li>
<li><code>Temporal.ZonedDateTime</code> - a date and time in a specific timezone</li>
</ul>
<p>That separation maps much more cleanly to real application data.</p>
<p>For example, an instant displayed in a timezone:</p>
<pre><code class="language-js">const instant = Temporal.Instant.from(&quot;2026-08-24T18:33:46.000Z&quot;);

const localTime = instant.toZonedDateTimeISO(&quot;America/Los_Angeles&quot;);
localTime.toString();
// '2026-08-24T11:33:46-07:00[America/Los_Angeles]'
</code></pre>
<p>A plain date that doesn't accidentally roll back a day:</p>
<pre><code class="language-js">const birthday = Temporal.PlainDate.from(&quot;2026-08-24&quot;);
birthday.toString();
// '2026-08-24'
</code></pre>
<p>And a timezone-aware future event:</p>
<pre><code class="language-js">const meeting = Temporal.ZonedDateTime.from({
	year: 2026,
	month: 8,
	day: 24,
	hour: 9,
	minute: 0,
	timeZone: &quot;America/New_York&quot;
});

meeting.toInstant().toString();
// '2026-08-24T13:00:00Z'
</code></pre>
<p>Even if you don't use Temporal yet, the model is useful: decide whether your value is an instant, a plain date, a local date/time, or a timezone-aware date/time. That decision prevents a lot of downstream confusion.</p>
<p>PlainDate/PlainDateTime is an odd one - it's time instant <strong>without any time zone associated with it</strong>. It's not UTC, but it's also not specifically tied to any other time zone. These values always print exactly what the values are without any transformation. One example where that's useful is Birthdays, which always are on a certain date and regardless of time zone adjustment stay on that particular date.</p>
<p>Personally I've not used Temporal, yet, but looking at the API I sure can see that it's useful because it allows you to be precise about what type of date you're dealing with. An Instant timestamp, a Plain date or date time value or a timezone specific date and time with the corresponding date formatting string functions to match.</p>
<p><a href="https://amzn.to/3SI8Whs"  target="_blank"
		  title="Broken Money - A Comprehensive Overview of the Past, Present, and Future of Money">
<img src="https://weblog.west-wind.com/images/sponsors/BrokenMoney-Display.jpg" class="da-content-image" />
</a></p>
<h2 id="summary">Summary</h2>
<p>JavaScript has a fair amount of options for formatting dates and dealing with dates in various time zone formats. The basic <code>Date</code> type is limited to using UTC or local time formats, but using <code>INTL.DateTimeFormat</code> allows you to create specific time instances that can be set to specific timezones which allows you to adjust dates based on user preferences beyond just the default browser time zone.</p>
<p>The <code>Temporal</code> class adds a more specific API for representing Date and Timezone values as a single instance, and goes beyond just displaying timezone adjusted values. I haven't run into a particular use case where this matters, as I typically use UTC dates and only need to display timezone adjusted values, but if you're passing date time values around between different APIs this might be useful.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://timeapi.io/documentation/iana-timezones">IANA Time Zones</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat">Intl.DateTimeFormat()</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal">Temporal</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant">Temporal.Instance</a></li>
</ul>
<div style="margin-top: 30px;font-size: 0.8em;
            border-top: 1px solid #eee;padding-top: 8px;">
    <img src="https://markdownmonster.west-wind.com/favicon.png" style="height: 20px;float: left; margin-right: 10px;">
    this post was created and published with the 
    <a href="https://markdownmonster.west-wind.com" target="top">Markdown Monster Editor</a> 
</div>
]]></description>
      <link>https://weblog.west-wind.com/posts/2026/Sep/01/JavaScript-and-Timezones</link>
      <guid isPermaLink="false">u33738s1e975</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Sep/01/JavaScript-and-Timezones#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Sep/01/JavaScript-and-Timezones</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[JavaScript's native `Date` object internally tracks UTC instants but defaults to local time for display, leading to common timezone pitfalls. In this post, I describe how to manage and display timezones using various JavaScript APIs old and new from the old and trusty `Date` object, `INTL.DateTimeFormat()` and the newish `Temporal` class.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/JavaScript-and-Timezones/TimeZoneBanner.jpg</featuredImage>
    </item>
    <item>
      <title>Getting Inherited Controller Routes to work in ASP.NET Core</title>
      <description><![CDATA[Controller inheritance in ASP.NET Core is an edge case, but if you need it you have to be mindful of how route inheritance works in ASP.NET.  This post explores why concrete class inheritance causes route duplication and provides a couple of solutions.]]></description>
      <link>https://weblog.west-wind.com/posts/2026/Jun/30/Getting-Inherited-Controller-Routes-to-work-in-ASPNET-Core</link>
      <guid isPermaLink="false">jq09wrhriyz8</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Jun/30/Getting-Inherited-Controller-Routes-to-work-in-ASPNET-Core#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Jun/30/Getting-Inherited-Controller-Routes-to-work-in-ASPNET-Core</guid>
      <pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[Controller inheritance in ASP.NET Core is an edge case, but if you need it you have to be mindful of how route inheritance works in ASP.NET.  This post explores why concrete class inheritance causes route duplication and provides a couple of solutions.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/Getting-Inherited-Controller-Routes-to-work-and-ASP-NET-Core/Banner.jpg</featuredImage>
    </item>
    <item>
      <title>Creating Dual Use Windows GUI and Console Applications</title>
      <description><![CDATA[Building a tool that provides both a rich Windows GUI and a functional CLI from a single executable presents unique challenges due to how Windows handles subsystem types. This post explores three approaches for dual-mode apps: attaching to consoles from a GUI, launching UIs from a console app, and creating separate specialized EXEs. I’ll share the "clean as possible" workarounds for console jank and window flashing used in my own production tools.]]></description>
      <link>https://weblog.west-wind.com/posts/2026/Jun/23/Creating-Dual-Use-Windows-GUI-and-Console-Applications</link>
      <guid isPermaLink="false">27v1kvj96o5y</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Jun/23/Creating-Dual-Use-Windows-GUI-and-Console-Applications#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Jun/23/Creating-Dual-Use-Windows-GUI-and-Console-Applications</guid>
      <pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[Building a tool that provides both a rich Windows GUI and a functional CLI from a single executable presents unique challenges due to how Windows handles subsystem types. This post explores three approaches for dual-mode apps: attaching to consoles from a GUI, launching UIs from a console app, and creating separate specialized EXEs. I’ll share the "clean as possible" workarounds for console jank and window flashing used in my own production tools.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/Creating-a-Windows-GUI-App-that-also-Doubles-as-a-CLI-App/GuiConsoleAppBanner.jpg</featuredImage>
    </item>
    <item>
      <title>Creating a Packaged Windows Single File Web Site Viewer Executable</title>
      <description><![CDATA[In this post I discuss how a self-contained Web Site Viewing tool using a single Windows executable that that packages and runs an entire website locally. I discuss the use case and implementation of creating a self-contained Windows executable that can be generated to contain both the executable itself and the data - in this case a zipped up Web site - to use. ]]></description>
      <link>https://weblog.west-wind.com/posts/2026/Jun/13/Creating-a-Packaged-Single-File-Web-Site-Viewer-Executable</link>
      <guid isPermaLink="false">v7x3mwmnhs55</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Jun/13/Creating-a-Packaged-Single-File-Web-Site-Viewer-Executable#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Jun/13/Creating-a-Packaged-Single-File-Web-Site-Viewer-Executable</guid>
      <pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[In this post I discuss how a self-contained Web Site Viewing tool using a single Windows executable that that packages and runs an entire website locally. I discuss the use case and implementation of creating a self-contained Windows executable that can be generated to contain both the executable itself and the data - in this case a zipped up Web site - to use. ]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/Creating-a-Packaged-Single-File-Web-Site-Viewer-Executable/Banner.jpg</featuredImage>
    </item>
    <item>
      <title>Lost ASP.NET Identity Cookies on IIS Application Pool Restarts</title>
      <description><![CDATA[If you find that your ASP.NET authentication cookies expire every time your IIS application pool restarts or recycles, the culprit is likely the DataProtection API not finding the previously stored keys.  This post describes one gotcha I ran into with the default storage location on IIS in the user profile due to a default Application Pool setting.]]></description>
      <link>https://weblog.west-wind.com/posts/2026/May/31/Lost-ASPNET-Cookies-on-IIS-Restarts</link>
      <guid isPermaLink="false">yd1j66azcyja</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/May/31/Lost-ASPNET-Cookies-on-IIS-Restarts#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/May/31/Lost-ASPNET-Cookies-on-IIS-Restarts</guid>
      <pubDate>Sun, 31 May 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[If you find that your ASP.NET authentication cookies expire every time your IIS application pool restarts or recycles, the culprit is likely the DataProtection API not finding the previously stored keys.  This post describes one gotcha I ran into with the default storage location on IIS in the user profile due to a default Application Pool setting.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/Lost-ASP-NET-Cookies-on-IIS-Restarts/CookieMonsterAttackOnIis.jpg</featuredImage>
    </item>
    <item>
      <title>Running ASP.NET Core Applications as a Subfolder Application</title>
      <description><![CDATA[While ASP.NET Core applications typically run from the root folder, some scenarios—such as hosting multiple blogs under a single domain—require running from a subfolder. This post explains how to configure ASP.NET Core with app.UsePathBase() for proper routing and ~/ path resolution, along with the IIS setup required for a dedicated Application Pool using "No Managed Code." It also covers key migration tips, including bulk updates for root-relative links and JavaScript adjustments to keep client-side functionality working in a subfolder environment.]]></description>
      <link>https://weblog.west-wind.com/posts/2026/May/26/Running-ASPNET-Core-Applications-in-an-IIS-Subfolder-Application</link>
      <guid isPermaLink="false">qzzxfojpce9j</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/May/26/Running-ASPNET-Core-Applications-in-an-IIS-Subfolder-Application#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/May/26/Running-ASPNET-Core-Applications-in-an-IIS-Subfolder-Application</guid>
      <pubDate>Tue, 26 May 2026 14:16:20 GMT</pubDate>
      <abstract><![CDATA[While ASP.NET Core applications typically run from the root folder, some scenarios—such as hosting multiple blogs under a single domain—require running from a subfolder. This post explains how to configure ASP.NET Core with app.UsePathBase() for proper routing and ~/ path resolution, along with the IIS setup required for a dedicated Application Pool using "No Managed Code." It also covers key migration tips, including bulk updates for root-relative links and JavaScript adjustments to keep client-side functionality working in a subfolder environment.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/imageContent/2026/Running-ASP-NET-Core-Applications-in-an-IIS-Subfolder-Application/PostBanner.jpg</featuredImage>
    </item>
    <item>
      <title>Getting the Client IP Address in ASP.NET Core</title>
      <description><![CDATA[When I need to pick up the client IP Address in ASP.NET Core I always forget where to find the connection information and/or forget about picking proxy forwarding instead of the actual IP address. To make things easy and reusable, here's a small HttpRequest extension method.]]></description>
      <link>https://weblog.west-wind.com/posts/2026/May/13/Getting-the-Client-IP-Address-in-ASPNET-Core</link>
      <guid isPermaLink="false">dkvnwxdbh42n</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/May/13/Getting-the-Client-IP-Address-in-ASPNET-Core#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/May/13/Getting-the-Client-IP-Address-in-ASPNET-Core</guid>
      <pubDate>Wed, 13 May 2026 10:35:26 GMT</pubDate>
      <abstract><![CDATA[When I need to pick up the client IP Address in ASP.NET Core I always forget where to find the connection information and/or forget about picking proxy forwarding instead of the actual IP address. To make things easy and reusable, here's a small HttpRequest extension method.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/images/2026/Getting-the-Client-IP-Address-in-ASP-NET-Core/ClientIpBanner.jpg</featuredImage>
    </item>
    <item>
      <title>Putting the Westwind.Scripting C# Templating Library to work, Part 2</title>
      <description><![CDATA[In part 2 of this post series I look at some of the issues you may have to deal with when using the Westwind.Scripting library as an offline document or Web site creation engine. While running simple templates is easy enough, when generating static output for Web site publishing or local preview requires some special considerations.]]></description>
      <link>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2</link>
      <guid isPermaLink="false">5314605</guid>
      <author> (Rick Strahl)</author>
      <comments>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2#Comments</comments>
      <guid>https://weblog.west-wind.com/posts/2026/Apr/23/Putting-the-WestwindScripting-Templating-Library-to-work-Part-2</guid>
      <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
      <abstract><![CDATA[In part 2 of this post series I look at some of the issues you may have to deal with when using the Westwind.Scripting library as an offline document or Web site creation engine. While running simple templates is easy enough, when generating static output for Web site publishing or local preview requires some special considerations.]]></abstract>
      <featuredImage>https://weblog.west-wind.com/images/2026/Revisiting-C-Scripting-with-the-Westwind-Scripting-Templating-Library,-Part-1/Part2-Banner.jpg</featuredImage>
    </item>
  </channel>
</rss>