{"id":18246692,"url":"https://github.com/datvm/aspstatic","last_synced_at":"2025-10-08T08:37:46.651Z","repository":{"id":143659156,"uuid":"598041670","full_name":"datvm/AspStatic","owner":"datvm","description":"Generate Static web pages from ASP.NET Core website with support for dynamic content.","archived":false,"fork":false,"pushed_at":"2023-05-15T16:55:47.000Z","size":997,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-05-01T12:18:55.923Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/datvm.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-06T09:18:17.000Z","updated_at":"2024-02-10T04:27:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"0c27d83a-7867-4aa2-9ca1-b73ca3b3f52b","html_url":"https://github.com/datvm/AspStatic","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/datvm%2FAspStatic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/datvm%2FAspStatic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/datvm%2FAspStatic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/datvm%2FAspStatic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/datvm","download_url":"https://codeload.github.com/datvm/AspStatic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247912820,"owners_count":21017050,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T09:27:18.662Z","updated_at":"2025-10-08T08:37:41.611Z","avatar_url":"https://github.com/datvm.png","language":"C#","readme":"`AspStatic` is a simple library to generate static HTML files from ASP.NET Core websites. It simply sends requests to the pages and saves the result to a folder or zipped file that can then be served by any static file server.\n\n# Why another Static Generator?\n\n- You have the full power of ASP.NET Core (database, service, dependency injections for example) and file access at the _runtime_ (when the static files are generated). This means **you don't have to write custom or extra template or layout**, you write the usual ASP.NET website.\n\n- Open abstractions for both `Grabber` and `Writer` so you can add your own implementations.\n\n- A single code base for both a ASP.NET Website and a static website. This package is intended to be run once in your development machine and you can easily \"unplug\" it from your Release build if you want a dynamic version of your website.\n\n# Installation\n\nInstall [`AspStatic` package from Nuget](https://www.nuget.org/packages/AspStatic):\n\n```ps\ndotnet add package AspStatic\n```\n\n# Usage\n\nSee full [demo project](https://github.com/datvm/AspStatic/tree/master/AppStatic.Demo).\n\n1. Call `AddHttpContextAccessor`, `AddHttpClient` and `AddAspStatic` methods to add necessary services to the website's DI. Optionally call `AddAspStaticUrlGatherer` if you want to use it. See [URL Gatherer](#url-gatherer) below.\n\n**Program.cs**\n```cs\n// ...\nbuilder.Services\n    // These 2 are required:\n    .AddHttpContextAccessor()\n    .AddHttpClient()\n\n    // Optionally add URL Gatherer\n    .AddAspStaticUrlGatherer()\n    \n    .AddAspStatic(options =\u003e\n    {\n        // By default, AspStaticOptions already has two grabbers:\n        // options.GrabWwwRootFiles();\n        // options.GrabRazorPages();\n\n        // You must call one Write method or else nothing will be written\n        options.WriteToFolder(@\"bin\\aspstatic\", true); // Write to a folder\n        options.WriteToZip(@\"bin\\aspstatic.zip\"); // Write to a Zip file\n\n        // Grab some URLs that are not covered by the default grabbers\n        // You can even use DI here to access database or other services\n        options.GrabCustomUrls(async (ctx) =\u003e\n        {\n            var services = ctx.RequestServices;\n\n            var result = new List\u003cstring\u003e()\n            {\n                \"/AppStatic.Demo.styles.css\",\n            };\n\n            // Get the product service to list all product pages\n            var prodSrv = services.GetRequiredService\u003cIProductService\u003e();\n            var prods = await prodSrv.GetAllAsync();\n\n            result.AddRange(prods\n                .Select(p =\u003e $\"/products/{p.Id}\"));\n\n            return result;\n        });\n    })\n```\n\n2. Call `app.UseAspStatic();` to add the middleware to the pipeline. This should be called before any resources is served and therefore should be right after the `app.UseHttpsRedirection()` call. You can also call `app.UseAspStaticUrlGatherer()` if you use the URL Gatherer.\n\n```cs\n// ...\napp.UseHttpsRedirection();\n\napp.UseAspStatic();\napp.UseAspStaticUrlGatherer();\n\napp.UseStaticFiles();\n// ...\n```\n\n\u003e **Note**  \n\u003e If you plan to use this as your production website as well, put these code inbetween `#if !DEBUG` and `#endif` or use `app.Environment` to put the code into approriate environments only.\n\n3. Run and access your web app. The first request will be hijacked by the `AspStatic` middleware. If everything runs smoothly, the output would be generated and you should see the response \"ASP Static built.\"\n\n# Grabbers\n\nGrabbers `IGrabber` determine what URLs are requested and saved to the output. The library provides 3 grabbers by default:\n\n- `WwwRootGrabber`: Grab all files in the `wwwroot` folder.\n\n- `RazorPagesGrabber`: Grab all Razor Pages in the website. Only works for pages without parameters in their paths like `/products` or `/info` but _not_ `/products/{id}`..\n\n- `CustomGrabber`: A grabber that you can customize to grab any URLs you want.\n\n\u003e **Note**  \n\u003e The default grabbers have an optional `requireSuccessfulResponse` option (**default: `true`**) that determines whether the response must be successful to be saved.\n\n\u003e **Note**  \n\u003e `IGrabber.GrabAsync(HttpContext context)` are called at runtime, providing great flexibility to what you can do because you have everything available to you.\n\nThe `AspStaticOptions` exposes the `List\u003cIGrabber\u003e Grabbers` so you can freely add or remove grabbers. You can also use the provided methods for quick access to those grabbers.\n\n\u003e **Note**  \n\u003e If you want to write your own Grabber, you may find the `BaseUrlGrabber` class useful. \n\n# Writers\n\nWriters `IWriter` determine how the output is saved. The library provides 2 writers by default:\n\n- `FsWriter`: Write to a folder on your File System. The optional `clearBeforeBuild` option (**default: `true`**) determines whether the folder is cleared before writing.\n\n  - The `WriteToFolder()` method writes to the folder relative to the web project (`env.ContentRootPath`).\n  \n  - To write to an absolute folder, use `WriteToAbsoluteFolder` or the more generic `WriteToFolder(Func\u003cHttpContext, string\u003e folderFunc, bool clearBeforeBuild)` method.\n\n\u003e **Note**  \n\u003e A decision was made to write path like `/product/2` to `/product/2/index.html` _instead of_ `/product/2.html` for cases when the website contains paths like `/product/2/details` as well. Any path that the end part has a dot `.` (like static files, `/css/index.css`) are kept and no folder is made.\n\n- `ZipWriter`: Write to a Zip file. The optional `relativeToContentRootPath` option (**default: `true`**) determines whether the provided path is relative to the web project (`env.ContentRootPath`).\n\n## Implementing your own writer\n\nYou can implement your own writer by implementing the `IWriter` interface:\n\n```cs\npublic interface IWriter\n{\n    Task InitializeAsync(HttpContext context);\n    Task\u003cStream\u003e GetOutputStreamAsync(string path);\n    // ...\n}\n```\n\n- `InitializeAsync` is called once before the first write. You can use this to create the output folder or file or get a relative folder resolved because an `HttpContext` instance is available to you.\n\n- `GetOutputStreamAsync(string path)` asks you to provide a `Stream` so the library can write the content to it. `path` is the path part of the URL, for example `/css/app.css` or `/product/2`.\n\n# URL Gatherer\n\nThe **URL Gatherer** is a separate component to the generator that simply logs all requests to the web app. Sometimes it may be difficult to gather all the URLs that you want to generate. For example, Blazor apps may request additional files from the framework.\n\n\u003e **Note**  \n\u003e The gatherer is simply a logger. Once you have the list, you have to manually put it to any use you want, for example feed it to `GrabCustomUrls`. You can write it to a file since `GrabCustomUrls` support `async` as well as all services you may need.\n\nYou need to call `builder.Services.AddAspStaticUrlGatherer()` and `app.UseAspStaticUrlGatherer()` to activate the URL Gatherer. This make an `IUrlGathererService` singleton instance available to your DI:\n\n```cshtml\n@inject AspStatic.IUrlGathererService Urls;\n\n@* ... *@\n\n@foreach (var item in Urls.GetGatheredUrls())\n{\n    \u003ctr\u003e\n        \u003ctd\u003e@(item.Url)\u003c/td\u003e\n        \u003ctd\u003e@(item.StatusCode is null ? \"N/A\" : (int)item.StatusCode)\u003c/td\u003e\n    \u003c/tr\u003e\n}\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdatvm%2Faspstatic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdatvm%2Faspstatic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdatvm%2Faspstatic/lists"}