- Validation error handling.
- Shared data.
- Partial and async lazy props.
- Server-side rendering.
- Vite helper.
- Cycle-safe model with relations data serialization.
- Properly working PATCH, PUT and DELETE redirections.
You can check out these examples to have some starting point for your new application.
- Using Package Manager:
PM> Install-Package AspNetCore.InertiaCore
- Using .NET CLI:
dotnet add package AspNetCore.InertiaCore
You need to add few lines to the Program.cs
or Starup.cs
file.
using InertiaCore.Extensions;
[...]
builder.Services.AddInertia();
[...]
app.UseInertia();
Create a file /Views/App.cshtml
.
@using InertiaCore
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title inertia>My App</title>
</head>
<body>
@await Inertia.Html(Model)
<script src="/js/app.js"></script>
</body>
</html>
You can change the root view file using:
builder.Services.AddInertia(options =>
{
options.RootView = "~/Views/Main.cshtml";
});
To pass data to a page component, use Inertia.Render()
.
public async Task<IActionResult> Index()
{
var posts = await _context.Posts.ToListAsync();
var data = new
{
Posts = posts,
};
return Inertia.Render("Posts", data);
}
To make a form endpoint, remember to add [FromBody]
to your model parameter, because the request data is passed using
JSON.
[HttpPost]
public async Task<IActionResult> Create([FromBody] Post post)
{
if (!ModelState.IsValid)
{
// The validation errors are passed automatically.
return await Index();
}
_context.Add(post);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
You can add some shared data to your views using for example middlewares:
using InertiaCore;
[...]
app.Use(async (context, next) =>
{
var userId = context.Session.GetInt32("userId");
Inertia.Share("auth", new
{
UserId = userId
});
// Or
Inertia.Share(new Dictionary<string, object?>
{
["auth"] => new
{
UserId = userId
}
});
});
You can use async lazy props to load data asynchronously in your components. This is useful for loading data that is not needed for the initial render of the page.
// simply use the LazyProps the same way you normally would, except pass in an async function
public async Task<IActionResult> Index()
{
var posts = new LazyProp(async () => await _context.Posts.ToListAsync());
var data = new
{
Posts = posts,
};
return Inertia.Render("Posts", data);
}
If you want to enable SSR in your Inertia app, remember to add Inertia.Head()
to your layout:
@using InertiaCore
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title inertia>My App</title>
@await Inertia.Head(Model)
</head>
<body>
@await Inertia.Html(Model)
<script src="/js/app.js"></script>
</body>
</html>
and enable the SSR option:
builder.Services.AddInertia(options =>
{
options.SsrEnabled = true;
// You can optionally set a different URL than the default.
options.SsrUrl = "http://127.0.0.1:13714/render"; // default
});
A Vite helper class is available to automatically load your generated styles or scripts by simply using the @Vite.Input("src/main.tsx")
helper. You can also enable HMR when using React by using the @Vite.ReactRefresh()
helper. This pairs well with the laravel-vite-plugin
npm package.
To get started with the Vite Helper, you will need to add one more line to the Program.cs
or Starup.cs
file.
using InertiaCore.Extensions;
[...]
builder.Services.AddViteHelper();
// Or with options (default values shown)
builder.Services.AddViteHelper(options =>
{
options.PublicDirectory = "wwwroot";
options.BuildDirectory = "build";
options.HotFile = "hot";
options.ManifestFilename = "manifest.json";
});
Here's an example for a TypeScript React app with HMR:
@using InertiaCore
@using InertiaCore.Utils
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title inertia>My App</title>
</head>
<body>
@* This has to go first, otherwise preamble error *@
@Vite.ReactRefresh()
@await Inertia.Html(Model)
@Vite.Input("src/main.tsx")
</body>
</html>
And here is the corresponding vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import laravel from "laravel-vite-plugin";
import path from "path";
import { mkdirSync } from "fs";
// Auto-initialize the default output directory
const outDir = "../wwwroot/build";
mkdirSync(outDir, { recursive: true });
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
laravel({
input: ["src/main.tsx"],
publicDirectory: outDir,
}),
react(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
build: {
outDir,
emptyOutDir: true,
},
});
Here's an example for a TypeScript Vue app with Hot Reload:
@using InertiaCore
@using InertiaCore.Utils
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title inertia>My App</title>
</head>
<body>
@await Inertia.Html(Model)
@Vite.Input("src/app.ts")
</body>
</html>
And here is the corresponding vite.config.js
import {defineConfig} from 'vite';
import vue from '@vitejs/plugin-vue';
import laravel from "laravel-vite-plugin";
import path from "path";
import {mkdirSync} from "fs";
const outDir = "../wwwroot/build";
mkdirSync(outDir, {recursive: true});
export default defineConfig({
plugins: [
laravel({
input: ["src/app.ts"],
publicDirectory: outDir,
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
build: {
outDir,
emptyOutDir: true,
},
});
Here's an example that just produces a single CSS file:
@using InertiaCore
@using InertiaCore.Utils
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
@await Inertia.Html(Model)
@Vite.Input("src/main.scss")
</body>
</html>