Skip to content

mergehez/InertiaNetCore

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Inertia.js ASP.NET Core Adapter

NuGet NuGet License

This library is a fork of kapi2289/InertiaCore. (Last commit: Aug 18, 2023)

Some errors were fixed, and unnecessary dependencies were removed. The library will be maintained and updated whenever necessary.

It is compatible with .NET 7 and .NET 8. As soon as .NET 9 is released, the library will be updated to support it.

Feel free to contribute to the project by creating issues or pull requests.

Table of contents

Demo

Demo is available at https://inertianetcore-d5c7hcggg7afdqg0.germanywestcentral-01.azurewebsites.net/

If you want to see how it exactly works, you can clone this repository and play with InertiaNetCore.Demo. It contains a simple Vue.js frontend and an ASP.NET Core backend.

Installation

  1. Using Package Manager:
Install-Package InertiaNetCore
  1. Using .NET CLI:
dotnet add package InertiaNetCore
  1. Using NuGet Package Manager: search for InertiaNetCore

Getting started

1. Enabling the library

You need to add few lines to the Program.cs or Starup.cs file.

using InertiaNetCore.Extensions;

[...]

builder.Services.AddInertia();
builder.Services.AddViteHelper(); // assuming you are using Vite

[...]

app.UseInertia();

Go to the Configuration section to see all available options.

2. Creating the root view for the app

Create the file /Views/App.cshtml with the following content:

@using InertiaNetCore
@model InertiaPage

<!DOCTYPE html>
<html lang="en">
<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>

Note

Default root view is App.cshtml but you can change it by setting RootView in AddInertia method in Program.cs.

3. Passing data to the frontend

To pass data to a page component, use Inertia.Render().

[Route("about")]
public IActionResult About()
{
    return Inertia.Render("pages/PageAbout", new InertiaProps
    {
        ["Name"] = "InertiaNetCore",
        ["Version"] = Assembly.GetAssembly(typeof(Inertia))?.GetName().Version?.ToString()
    });
}

Note

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");
}

Configuration

Both AddInertia and AddViteHelper methods have optional parameters to configure the library.

For example, you can change JSON serializer settings to use Newtonsoft.Json instead of System.Text.Json.

builder.Services.AddInertia(options =>
{
    var options = new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    };
    o.Json = InertiaJsonOptions.Create(options); // there is also an optional parameter to customize the "Serialize" method
});

Visit the InertiaOptions and ViteOptions classes to see all available options.

Features

Shared data

You can add some shared data to your views using for example middlewares:

using InertiaNetCore;
using InertiaNetCore.Extensions;
using InertiaNetCore.Models;

[...]

app.Use(async (context, next) =>
{
    Inertia.Share( new InertiaProps
    {
        ["Auth"] = new InertiaProps
        {
            ["Token"] = "123456789",
            ["Username"] = "Mergehez",
        }
    });
            
    await next();
});

// you can also use AddInertiaSharedData extension method to do the same thing
app.AddInertiaSharedData(httpContext => new InertiaProps
{
    ["Auth"] = new InertiaProps
    {
        ["Token"] = "123456789",
        ["Username"] = "Mergehez",
    }
});

Flash Messages

You can add flash messages to your responses using the Inertia.Flash(...) or Inertia.Back(url).WithFlash(...) methods.

[HttpDelete("{id:int}")]
public async Task<IActionResult> Destroy(int id)
{
    // find user
    
    // delete user
    
    Inertia.Flash("success", "User deleted."); // set it anywhere in the app
    return Redirect("/users");
    
    // or one-liner in case you use Inertia.Back()
    return Inertia.Back().WithFlash("success", "User deleted.");
}

Deferred props

Deferred props allow you to defer the loading of certain page data until after the initial page render. (see Inertia.js docs)

In the example below, the Posts prop will be loaded after the initial page render.

public IActionResult Profile()
{
    return Inertia.Render("pages/Profile", new InertiaProps
    {
        ["Foo"] = "Bar", 
        ["Posts"] = Inertia.Defer(async () =>
        {
            return await _context.Posts.ToListAsync();
        })
    });
}

Note

Deferred props are supported starting from Inertia.js v2.0

Merging props

By default, Inertia overwrites props with the same name when reloading a page. However, there are instances, such as pagination or infinite scrolling, where that is not the desired behavior. In these cases, you can merge props instead of overwriting them. (see Inertia.js docs)

public IActionResult Users(int page, int perPage)
{
    return Inertia.Render("pages/Users", new InertiaProps
    {
        ["Results"] = Inertia.Merge(async () =>
        {
            return await GetPaginatedUsers(page, perPage);
        })
    });
}

Note

Merging props are supported starting from Inertia.js v2.0

History encryption

See Inertia.js docs for more information.

History encryption is an opt-in feature. There are several methods for enabling it:

Global encryption:

If you'd like to enable history encryption globally, set the EncryptHistory option to true in AddInertia method in Program.cs.

builder.Services.AddInertia(options =>
{
    options.EncryptHistory = true;
});

Per-request encryption:

To encrypt the history of an individual request, simply call the Inertia.EncryptHistory() method before returning the response.

Inertia.EncryptHistory();

Clearing encrypted history:

To clear the history state, you can call the Inertia.ClearHistory() method before returning the response.

Inertia.ClearHistory();

Note

History encryption is supported starting from Inertia.js v2.0

Server-side rendering

If you want to enable SSR in your Inertia app, remember to add Inertia.Head() to your layout:

@using InertiaNetCore
@model InertiaNetCore.InertiaPage

<!DOCTYPE html>
<html lang="en">
<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)

@Vite.Input("src/app.ts")
</body>
</html>

and enable the SSR option in Program.cs.

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
});

Vite Helper

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 have to use the AddViteHelper extension method in Program.cs.

using InertiaNetCore.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";
});

Examples

Here's an example for a TypeScript Vue app with Hot Reload:

@using InertiaNetCore

<!DOCTYPE html>
<html lang="en">
<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 vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import path from 'path';
import laravel from 'laravel-vite-plugin';
import { mkdirSync } from 'node:fs';

const outDir = '../../wwwroot/build';
mkdirSync(outDir, { recursive: true });

export default defineConfig({
    plugins: [
        laravel({
            input: ['src/app.ts', 'src/app.scss'],
            publicDirectory: outDir,
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src'),
        },
    },
    build: {
        outDir,
        emptyOutDir: true,
    },
});