AspNetStatic

repository·master·Indexed 41 days ago

https://github.com/zarehd/aspnetstatic

A tool that transforms ASP.NET Core applications into a Static Site Generator (SSG). It enables developers to use Blazor SSR, Razor Pages, or MVC to produce optimized static HTML, CSS, and JS files. It supports full standalone SSG for hosting on services like Netlify or Azure, as well as partial static sites where specific routes are served as static files while others remain dynamic.

Tokens
6.8K
Snippets
15
Records
20
Agent score
1%

What's inside AspNetStatic

  1. Transform ASP.NET Core into a Static Site Generator

    master

    AspNetStatic allows you to use your existing ASP.NET Core skills (Blazor SSR, Razor Pages, or MVC) to generate a static website. Instead of learning a new SSG stack like Jekyll or Hugo, you simply add this package to your project and configure which routes should be processed into static files.

    Important Requirement for Blazor: If using Blazor, pages must not rely on client-side functionality (JS, WASM) or behaviors that show placeholders (like spinners) before rendering. The content must be fully rendered by the time AspNetStatic receives it via the HTTP request.

  2. How Resource Locators work in AspNetStaticContrib.Stekeblad

    master

    Resource Locators allow for the dynamic discovery of pages and other resources after the ASP.NET Core application has fully started. This enables automation of the resource list by accessing all registered services, rather than maintaining a manual list of resources.

    Key Constraints:

    • Run Once: Resource locators only run once. They do not support rediscovering content if you are using periodic regeneration of static content.
    • No DI in Constructors: Resource locators are not created via Dependency Injection. Instead, they receive an IServiceProvider instance during the discovery phase. You must use this provided instance to retrieve any required services.
  3. Configure routes in IStaticResourcesInfoProvider.PageResources

    master

    When adding routes to the IStaticResourcesInfoProvider.PageResources collection, follow these rules:

    • Base URI: Routes must exclude the site's base URI (e.g., do not include http://localhost:5000 or https://www.example.com).
    • Index Pages: Avoid specifying an 'index' page name. Instead, use a route with a terminating slash (e.g., / instead of /index).
    • Output Files (OutFile): You can specify a custom pathname for the generated file. This path must be relative to the destination root folder. If left empty, the pathname is determined automatically based on the route and the AlwaysDefaultFile setting.
    • Route Parameters: Parameters are treated as part of the route and are used to construct the output file pathname.
    • Query Strings (Query): You can specify a Query property. To use the same Route with different query values, you must provide a unique OutFile for each instance.
    • Optimization (OptimizationType): You can skip optimization or choose a specific type via the OptimizationType property. The default is OptimizationType.Auto.
    • Encoding (OutputEncoding): You can set the encoding for output files. The default is UTF8.

    These rules also apply to OtherResources (CSS, JavaScript, and binary files).

  4. Understand how routes map to generated static files

    master

    The mapping from a URL (route + query) to a physical file on disk depends on the resource type and the AlwaysDefaultFile setting.

    Page Resources (PageResource)

    For page resources, the mapping depends on whether AlwaysDefaultFile is true or false:

    URL (route + query)Always Default: falseAlways Default: true
    /C:\MySite\index.htmlC:\MySite\index.html
    /indexC:\MySite\index.htmlC:\MySite\index.html
    /index/C:\MySite\index\index.htmlC:\MySite\index\index.html
    /pageC:\MySite\page.htmlC:\MySite\page\index.html
    /page/C:\MySite\page\index.htmlC:\MySite\page\index.html
    /page/123C:\MySite\page\123.htmlC:\MySite\page\123\index.html
    /page/123/C:\MySite\page\123\index.htmlC:\MySite\page\123\index.html
    /page/123?p1=v1C:\MySite\page\123.htmlC:\MySite\page\123\index.html
    /blog/articles/C:\MySite\blog\articles\index.htmlC:\MySite\blog\articles\index.html

    Non-Page Resources (CssResource, JsResource, BinResource)

    For these types, AlwaysDefaultFile is not applicable. The mapping is direct:

    URL (route + query)Generated File
    /file.cssC:\MySite\file.css
    /folder/file.cssC:\MySite\folder\file.css
    /file.css?v=123C:\MySite\file.css
    /file (as __CssResource__)C:\MySite\file.css
    /file (as __JsResource__)C:\MySite\file.js
    /file (as __BinResource__)C:\MySite\file.bin
    /file.pngC:\MySite\file.png
  5. Configure a Partial Static Site

    master

    A Partial Static Site allows some routes to be served as static .html files while others remain dynamic (e.g., APIs or views).

    Requirements for Partial SSG:

    1. The destination root must be app.Environment.WebRoot (the wwwroot folder).
    2. exitWhenDone must be set to false so the app continues running.
    3. You must either use the UseStaticPageFallback middleware or allow the generator to update links in <a> and <area> tags.

    Implementation Steps:

    1. Register services: builder.Services.AddStaticPageFallback();
    2. Use fallback middleware: app.UseStaticPageFallback(); (this re-routes requests to generated static files for routes in the PageResources collection).
    3. Use standard static files: app.UseStaticFiles();
    4. Call app.GenerateStaticContent(...) with app.Environment.WebRoot as the destination.
    // 1. Register services
    builder.Services.AddStaticPageFallback();
    
    // ... inside app configuration ...
    
    // 2. Use fallback middleware (re-routes to static files)
    app.UseStaticPageFallback();
    
    // 3. Standard static files middleware
    app.UseStaticFiles();
    
    app.UseRouting();
    
    // ... map routes ...
    
    // 4. Generate content to wwwroot
    app.GenerateStaticContent(
      app.Environment.WebRoot,
      exitWhenDone: false,
      alwaysDefaultFile: true,
      dontUpdateLinks: false);
    
    app.Run();
  6. Generate static content in app startup

    master

    After registering your provider in the DI container, call app.GenerateStaticContent("path_to_output") in your application startup sequence to trigger the generation process.

    // In Program.cs
    ...
    app.MapRazorPages();
    ...
    app.GenerateStaticContent(@"C:\SSG-Output-Folder");
    app.Run();
  7. Setup Resource Locators in your application

    master

    To transition from manual resource management to using Resource Locators, follow these steps:

    1. Register the extension: Call builder.Services.AddAspNetStaticContribStekeblad(Action<ContribStekebladOptions>) and enable the desired locators in the options action.
    2. Update the Provider: Instead of StaticResourcesInfoProvider, use or derive from LocatingStaticResourcesInfoProvider as your IStaticResourcesInfoProvider implementation.
    3. Add Locators: Use AddResourceLocator or the convenience extension methods (e.g., .AddSitemapLocator()) on the provider.
    4. Synchronize Execution: You must call app.LocateStaticResources(EventWaitHandle) before app.GenerateStaticContent(...).

    CRITICAL: The EventWaitHandle instance passed to LocateStaticResources must be the same instance passed to app.GenerateStaticContent(..., processStartSignal: signal). This ensures the static generator waits for discovery to complete, preventing concurrency exceptions.

    // 1. Enable locators in services
    builder.Services.AddAspNetStaticContribStekeblad(opts => 
    {
        opts.RegisterSitemapResourceLocator = true;
    });
    
    // 2. Use LocatingStaticResourcesInfoProvider and add locators
    builder.Services.AddSingleton<IStaticResourcesInfoProvider>( 
        new LocatingStaticResourcesInfoProvider()
            .AddSitemapLocator()
            .Add(builder.Environment.GetWebRootCssResources(["**/site.css"]))
    );
    
    // ...
    
    // 3. Create a shared signal
    var signal = new EventWaitHandle(false, EventResetMode.ManualReset);
    
    // 4. Tell the app to locate resources using the signal
    app.LocateStaticResources(signal);
    
    // 5. Pass the SAME signal to GenerateStaticContent
    app.GenerateStaticContent(
        app.Environment.WebRootPath,
        // ... other params
        signalTimeoutSeconds: 60, 
        processStartSignal: signal);
  8. Disable content optimization in AspNetStatic

    master

    By default, AspNetStatic uses optimizers to process resources (pages, CSS, JS, etc.) before writing them to the destination. To disable this feature entirely and prevent IOptimizerSelector and all optimizers from being called, pass true to the dontOptimizeContent parameter in the GenerateStaticContent method.

    app.GenerateStaticContent(
      ...
      dontOptimizeContent: true);
  9. Configure routes for static file generation

    master

    To tell AspNetStatic which routes to process, you must implement or configure an IStaticResourcesInfoProvider. You can do this by creating an instance of StaticResourcesInfoProvider (which derives from StaticResourcesInfoProviderBase) and populating its resource collections.

    Resource Types

    • PageResource: For HTML pages. You can specify the Route and an optional OutFile or Query string.
    • CssResource: For CSS files. Supports an OptimizationType property.
    • JsResource: For JavaScript files.
    • BinResource: For binary assets like images or favicons.

    Registration

    Register the provider in your DI container (typically in Program.cs) as a singleton.

    // Example 1: Using extension methods from AspNetStaticContrib
    builder.Services.AddSingleton<IStaticResourcesInfoProvider>(
        new StaticResourcesInfoProvider()
          .AddAllProjectRazorPages(builder.Environment)
          .AddAllWebRootContent(builder.Environment));
    
    // Example 2: Manual configuration of specific routes
    builder.Services.AddSingleton<IStaticResourcesInfoProvider>(
      new StaticResourcesInfoProvider(
        new []
        {
          new PageResource("/"),
          new PageResource("/privacy"),
          new PageResource("/blog/articles/posts/1") { OutFile = "blog/post-1.html" },
          new PageResource("/blog/articles/posts/2") { OutFile = "blog/post-2-dark.html", Query = "?theme=dark" },
          new CssResource("/bootstrap/bootstrap.min.css") { OptimizationType = OptimizationType.None },
          new CssResource("/site.css"),
          new JsResource("/site.js"),
          new BinResource("/favicon.png")
        }));
  10. Set up an SSG launch profile in launchSettings.json

    master

    To avoid generating static files every time you run the app during development, you can create a specific launchSettings.json profile for SSG mode. This allows you to run the app in 'SSG mode' (generate and exit) via one profile, and 'Development mode' (dynamic) via another.

    1. Add an SSG profile to launchSettings.json with a custom commandLineArgs value (e.g., ssg).
    2. In Program.cs, wrap the GenerateStaticContent call in a conditional check using args.HasExitWhenDoneArg().
    // launchSettings.json
    "profiles": {
      "SSG": {
          "commandName": "Project",
          "commandLineArgs": "ssg",
          "launchBrowser": false,
          "applicationUrl": "https://localhost:5000"
      }
    }
    // Program.cs
    if (args.HasExitWhenDoneArg())
    {
      app.GenerateStaticContent(
        @"path\to\destination\root\folder",
        exitWhenDone: true,
      );
    }
  11. Configure Standalone Static Site Generation (SSG)

    master

    Use GenerateStaticContent to create a completely static website intended for hosting on services like Netlify, Azure, or AWS storage. You can choose between two primary configuration patterns:

    • Generates a default file (e.g., index.html) only for routes ending in a slash.
    • Automatically updates href attributes in <a> and <area> tags to point to the new static file paths (e.g., changing /page to /page.html).
    • Best for: Simple deployments where you want the generator to handle link integrity.
    • Generates a default file for all routes (e.g., /page/ becomes /page/index.html).
    • Does not update href attributes in <a> and <area> tags.
    • Requires your web server to handle re-routing (e.g., mapping /page/ to /page/index.html).
    • Best for: Environments where you have fine-grained control over server routing.
    // Pattern 1: Slash-only default files + link updates
    app.GenerateStaticContent(
      "../SSG_Output",
      exitWhenDone: true);
    
    // Pattern 2: Full default files + no link updates
    // Note: exitWhenDone is often controlled by a CLI argument in SSG mode
    app.GenerateStaticContent(
      @"C:\path\to\destination\root\folder",
      exitWhenDone: exitWhenDone,
      alwaysDefaultFile: true,
      dontUpdateLinks: true);