AspNet.AssetManager
4.4.1
dotnet add package AspNet.AssetManager --version 4.4.1
NuGet\Install-Package AspNet.AssetManager -Version 4.4.1
<PackageReference Include="AspNet.AssetManager" Version="4.4.1" />
<PackageVersion Include="AspNet.AssetManager" Version="4.4.1" />
<PackageReference Include="AspNet.AssetManager" />
paket add AspNet.AssetManager --version 4.4.1
#r "nuget: AspNet.AssetManager, 4.4.1"
#:package AspNet.AssetManager@4.4.1
#addin nuget:?package=AspNet.AssetManager&version=4.4.1
#tool nuget:?package=AspNet.AssetManager&version=4.4.1
AspNet.AssetManager
Use Vite or Webpack with ASP.NET Core MVC and Razor Pages. AspNet.AssetManager reads your bundler's manifest and renders the correct <script> and <link> tags: from the dev server (with hot reload) in development, and from hashed static files in production.
<link-bundle fallback="Layout" />
<script-bundle defer fallback="Layout" />
Features
- Tag helpers:
<link-bundle />,<script-bundle />and<style-bundle />. - Per-view bundles by convention: the bundle name is inferred from the view being rendered, e.g.
Views/Home/Index.cshtml→Views_Home_Index. - Fallbacks: if a view has no bundle of its own, a shared one (e.g.
Layout) is used. - Vite and Webpack: supports Vite manifests and key/value manifests such as
webpack-assets-manifest. - Same markup in every environment: assets come from the dev server in development and from
wwwrootin production. - Full Vite CSS:
<link-bundle />collects stylesheets from imported shared chunks, in the same order Vite applies them. - Targets .NET 8, 9 and 10.
Getting started
The quickest way to start is a project template from AspNet.Frontend.Templates. It sets up the .NET side and the bundler config for you:
dotnet new install AspNet.Frontend.Templates
dotnet new mvcvite # also: mvcvitets, mvcwebpack(ts), razorvite(ts), razorwebpack(ts)
To add AspNet.AssetManager to an existing project, follow the steps below.
1. Install
dotnet add package AspNet.AssetManager
2. Register services
Program.cs:
using AspNet.AssetManager;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddAssetManager(builder.Configuration, builder.Environment);
var app = builder.Build();
app.UseStaticFiles();
// ...
3. Import the tag helpers
Views/_ViewImports.cshtml:
@addTagHelper *, AspNet.AssetManager
4. Render bundles in your layout
Views/Shared/_Layout.cshtml:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"]</title>
<link-bundle fallback="Layout" />
</head>
<body>
@RenderBody()
<script-bundle defer fallback="Layout" />
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Each page now loads its own bundle (Views_Home_Index, Views_Account_Login, ...) if one exists, and the Layout bundle if not.
5. Configure your bundler
Vite
appsettings.json:
{
"AssetManager": {
"PublicDevServer": "http://localhost:5173",
"ManifestType": "Vite"
}
}
vite.config.js. aspnet-buildtools finds the entry points for your views and serves a manifest from the Vite dev server:
import { viteConfig } from 'aspnet-buildtools';
import { defineConfig } from 'vite';
export default defineConfig({
...viteConfig,
build: {
...viteConfig.build,
assetsDir: '',
manifest: 'assets-manifest.json', // must match AssetManager:ManifestFile
outDir: 'wwwroot/dist', // must match wwwroot + AssetManager:PublicPath
},
server: {
cors: true,
},
});
For hot module replacement, add the Vite client to your layout in development:
<environment include="Development">
<script src="http://localhost:5173/@@vite/client" type="module"></script>
</environment>
Webpack
appsettings.json:
{
"AssetManager": {
"PublicDevServer": "http://localhost:9000"
}
}
webpack.config.js, using webpack-assets-manifest and aspnet-buildtools:
import path from 'node:path';
import { webpackConfig } from 'aspnet-buildtools';
import { WebpackAssetsManifest } from 'webpack-assets-manifest';
export default {
...webpackConfig,
output: {
path: path.resolve(import.meta.dirname, 'wwwroot/dist'),
filename: '[name]-[contenthash].js',
publicPath: '/dist/',
},
plugins: [new WebpackAssetsManifest()],
devServer: {
headers: { 'Access-Control-Allow-Origin': '*' },
port: 9000,
},
};
Tag helpers
| Tag helper | Renders | Attributes |
|---|---|---|
<link-bundle /> |
One or more <link rel="stylesheet"> tags |
name, fallback |
<script-bundle /> |
A <script src="..."> tag |
name, fallback, async, defer, module |
<style-bundle /> |
An inline <style> tag with the bundle's CSS (useful for critical CSS) |
name, fallback |
name: bundle to render. If omitted, it is resolved as described in Bundle names.fallback: bundle to render ifnamecannot be found in the manifest.module: addstype="module". Defaults totruefor Vite manifests andfalseotherwise.
Nothing is rendered if neither bundle exists, so it is safe to put these tags in a shared layout.
Bundle names
The bundle for a tag helper is chosen in this order:
- The
nameattribute, e.g.<script-bundle name="Checkout" />. ViewData["Bundle"], set in a view or controller. A value starting with/is converted the same way as view paths:"/Some/Bundle"→Some_Bundle.- The path of the view being rendered:
| View | Bundle name |
|---|---|
Views/Home/Index.cshtml |
Views_Home_Index |
Pages/Account/Login.cshtml |
Pages_Account_Login |
Areas/Admin/Views/Users/Edit.cshtml |
Areas_Admin_Views_Users_Edit |
If that bundle is not in the manifest, the fallback bundle is used.
aspnet-buildtools produces these names: a Views/Home/Index.cshtml.js (or .ts) next to a view becomes the Views_Home_Index entry, and any *.bundle.js file becomes a named bundle (Layout.bundle.js → Layout).
Configuration
All settings live in the AssetManager section of appsettings.json:
| Setting | Default | Description |
|---|---|---|
PublicDevServer |
— | Required in development. The dev server URL the browser uses, e.g. http://localhost:5173. |
InternalDevServer |
PublicDevServer |
The dev server URL the ASP.NET Core app uses to fetch the manifest. Set this when the app runs in Docker, WSL, etc. and reaches the dev server at a different address. |
PublicPath |
/dist/ |
Base path for built assets, under wwwroot in production. |
ManifestFile |
assets-manifest.json |
File name of the manifest, relative to PublicPath. |
ManifestType |
KeyValue |
KeyValue (flat { "name.js": "name-hash.js" }, e.g. webpack-assets-manifest) or Vite. |
Development vs. production
The app counts as being in development when IWebHostEnvironment.IsDevelopment() returns true.
| Development | Production | |
|---|---|---|
| Manifest | Fetched from the dev server on each request | Read from wwwroot + PublicPath + ManifestFile once, then cached |
| Asset URLs | PublicDevServer + PublicPath (Vite: PublicDevServer + /) |
PublicPath, served by UseStaticFiles() |
| Extra attributes | crossorigin="anonymous" |
— |
If the dev server is not running, a DevServerException ("Development server not started!") is thrown.
Rendering from code
For full control, inject IAssetService:
@using AspNet.AssetManager
@inject IAssetService AssetService
@{
var bundle = ViewData.GetBundleName() ?? Html.GetBundleName();
}
@* Tags *@
@await AssetService.GetScriptTagAsync(bundle, "Layout", ScriptLoad.Defer)
@await AssetService.GetLinkTagAsync(bundle, "Layout")
@await AssetService.GetStyleTagAsync("Critical")
@* Paths *@
@AssetService.WebPath
@await AssetService.GetBundlePathAsync("SomeBundle.js")
@await AssetService.GetBundlePathAsync("SomeBundle", FileType.CSS)
ScriptLoad can be Normal, Async, Defer or AsyncDefer.
Related projects
- AspNet.Frontend.Templates:
dotnet newtemplates and example projects for Vite and Webpack. - aspnet-buildtools: npm package that generates bundler entry points and aliases from your Razor views.
Building from source
The build uses NUKE:
./build.sh Test # Windows: .\build.cmd Test
src/AspNet.AssetManager.Demo is a small Webpack-based MVC app for trying changes locally (npm install && npm start, then dotnet run).
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- System.IO.Abstractions (>= 22.3.0)
-
net8.0
- System.IO.Abstractions (>= 22.3.0)
-
net9.0
- System.IO.Abstractions (>= 22.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.