Pharmica.AssetGen
1.1.0
dotnet add package Pharmica.AssetGen --version 1.1.0
NuGet\Install-Package Pharmica.AssetGen -Version 1.1.0
<PackageReference Include="Pharmica.AssetGen" Version="1.1.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Pharmica.AssetGen" Version="1.1.0" />
<PackageReference Include="Pharmica.AssetGen"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Pharmica.AssetGen --version 1.1.0
#r "nuget: Pharmica.AssetGen, 1.1.0"
#:package Pharmica.AssetGen@1.1.0
#addin nuget:?package=Pharmica.AssetGen&version=1.1.0
#tool nuget:?package=Pharmica.AssetGen&version=1.1.0
<img width="1280" height="446" alt="banner" src="https://github.com/user-attachments/assets/7f9a09cf-d75b-41bd-a715-7d93160792a4" />
A Roslyn source generator that provides compile-time type safety for wwwroot assets in ASP.NET Core applications.
Why Use This?
Instead of hardcoding paths like "/images/logo.png" in your code (which can break at runtime if the file is renamed or deleted), you can reference them as StaticAssets.Images.LogoPng. If the file doesn't exist or is renamed, you'll get a compile-time error instead of a runtime 404.
Key Features
- ✅ Compile-time safety - Catch missing/renamed assets before deployment
- ✅ IntelliSense support - Browse assets with autocomplete
- ✅ Zero runtime overhead - All code generation happens at build time
- ✅ Hardcoded path analyzer - Get warnings for hardcoded paths in your code
- ✅ Configurable - Customize class name and extension handling
- ✅ Monorepo friendly - Works seamlessly in solutions with multiple projects
Installation
dotnet add package Pharmica.AssetGen
Quick Start
1. Install the Package
<ItemGroup>
<PackageReference Include="Pharmica.AssetGen" />
</ItemGroup>
2. Add wwwroot Files as AdditionalFiles
<ItemGroup>
<AdditionalFiles Include="wwwroot/**/*" />
</ItemGroup>
3. Use the Generated Class
Given this wwwroot structure:
wwwroot/
├── images/
│ ├── logo.png
│ └── icon-small.svg
├── css/
│ └── style.min.css
└── js/
└── app.js
The generator creates:
public static class StaticAssets
{
public static class Images
{
public const string LogoPng = "/images/logo.png";
public const string IconSmallSvg = "/images/icon-small.svg";
}
public static class Css
{
public const string StyleMinCss = "/css/style.min.css";
}
public static class Js
{
public const string AppJs = "/js/app.js";
}
}
4. Reference Assets in Your Code
Razor/Blazor:
<img src="@StaticAssets.Images.LogoPng" alt="Logo" />
<link rel="stylesheet" href="@StaticAssets.Css.StyleMinCss" />
<script src="@StaticAssets.Js.AppJs"></script>
Minimal API:
app.MapGet("/logo", () => Results.File(StaticAssets.Images.LogoPng));
Controllers:
public IActionResult Index()
{
ViewData["LogoPath"] = StaticAssets.Images.LogoPng;
return View();
}
Configuration
Customize Class Name
By default, the generated class is named StaticAssets. You can customize this to avoid conflicts:
<PropertyGroup>
<AssetGen_ClassName>WebAssets</AssetGen_ClassName>
</PropertyGroup>
Then use: WebAssets.Images.LogoPng
File Extension Handling
By default, file extensions are "flattened" into PascalCase:
logo.png→LogoPngstyle.min.css→StyleMinCss
To disable this behavior and keep extensions separate:
<PropertyGroup>
<AssetGen_FlattenExtensions>false</AssetGen_FlattenExtensions>
</PropertyGroup>
Naming Rules
- PascalCase: All identifiers are converted to PascalCase
- Special Characters: Dots (
.), hyphens (-), underscores (_), and spaces are removed, and the next character is capitalized - Invalid Identifiers: Names starting with numbers are prefixed with an underscore
Examples:
my-logo.png→MyLogoPngicon small.svg→IconSmallSvg3d-model.obj→_3dModelObj
Hardcoded Path Analyzer
The package includes an analyzer (ASSET002) that warns when you use hardcoded paths to static assets:
// ⚠️ Warning ASSET002: Hardcoded path '/images/logo.png' should use StaticAssets class
public string Logo => "/images/logo.png";
// ✅ No warning
public string Logo => StaticAssets.Images.LogoPng;
The analyzer detects common asset patterns:
- Paths starting with
/images/,/css/,/js/,/fonts/,/lib/,/assets/ - Common file extensions:
.css,.js,.png,.jpg,.svg,.webp,.ico,.woff, etc.
Performance Characteristics
Build Time Impact
- Incremental Builds: Minimal overhead (~50-200ms for typical projects)
- Clean Builds: Proportional to number of assets (1000 assets ~500ms)
- Incremental Generator: Only regenerates when wwwroot files change
The generator uses Roslyn's incremental generation pipeline, so it only runs when:
- wwwroot files are added, removed, or renamed
- Configuration properties change
- Clean builds are performed
Output Directory
- wwwroot files are NOT duplicated - They're only included as
AdditionalFilesfor analysis - No runtime overhead - All generated code is compile-time constants
- Generated file size: Approximately 50 bytes per asset + hierarchy structure
Memory Usage
Minimal - the generator processes files in a streaming fashion and doesn't load file contents into memory (only file paths).
Error Handling
Duplicate Keys (ASSET001)
If multiple files would generate the same identifier, the generator reports a compile error:
error ASSET001: Multiple assets map to the same key 'StaticAssets.Images.Logo'.
Conflicting file: /wwwroot/images/logo.png.
Consider using different file names or folder structure.
Common Scenarios:
images/logo.pngandimages/logo_png→ Both generateLogoPng- Files with complex naming that collapse to the same identifier
Solution: Rename one of the files to be distinct.
Requirements
- .NET Standard 2.0+ (works with both .NET Framework 4.7.2+ and .NET Core/.NET 5+)
- ASP.NET Core projects with wwwroot folder
Best Practices
- Use Consistent Naming: Prefer kebab-case or snake_case for file names
- Organize by Type: Use subdirectories (
images/,css/,js/) for better organization - Avoid Special Characters: While supported, avoid unusual characters in file names
- Enable Analyzer: Keep ASSET002 warnings enabled to catch hardcoded paths
- Commit Generated Code: For transparency, consider emitting generated files to source control:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
Troubleshooting
Generator Not Running
- Verify
wwwroot/**/*is included inAdditionalFiles - Check that the project references the generator correctly
- Try a clean rebuild:
dotnet clean && dotnet build
Analyzer Not Showing Warnings
- Verify the generator is referenced as an analyzer
- Check your IDE analyzer settings
- Restart your IDE/Roslyn language server
FAQ
Q: Does this affect my published wwwroot folder?
A: No. The generator only reads file paths - it doesn't modify or duplicate your wwwroot files. They're published normally.
Q: What about dynamic asset paths?
A: For dynamic paths (e.g., user uploads, CDN URLs), continue using strings. This tool is for static assets that ship with your application.
Q: Can I use this with Blazor WebAssembly?
A: Yes! It works with any ASP.NET Core project type (MVC, Razor Pages, Blazor Server, Blazor WASM, Minimal APIs).
Q: Does this work with asset fingerprinting/bundling?
A: The generator provides the logical path (/images/logo.png). If you're using asset fingerprinting (e.g., logo.abc123.png), you'll need to pipe the constant through your fingerprinting system. Future versions may include built-in support.
Q: How do I disable the analyzer warnings?
A: Add this to your project file:
<PropertyGroup>
<NoWarn>$(NoWarn);ASSET002</NoWarn>
</PropertyGroup>
Contributing
Contributions are welcome! This project is open source under the MIT license.
Development Setup
- Clone the repository
- Run
dotnet build - Run tests:
dotnet test
Reporting Issues
Please include:
- Your project structure (especially wwwroot layout)
- The generated code (if applicable)
- Steps to reproduce
- Expected vs actual behavior
License
MIT License - see LICENSE file for details.
Acknowledgments
Built with:
- Roslyn Source Generators
- Incremental Generator Pipeline
- C# Syntax Analysis
Inspired by the need for type-safe asset references in modern ASP.NET Core applications and the lessons learned from SQL file generators.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp (>= 5.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
See CHANGELOG.md for release notes.