Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions InertiaCore/Extensions/Configure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace InertiaCore.Extensions;

Expand Down Expand Up @@ -48,6 +49,7 @@ public static IServiceCollection AddInertia(this IServiceCollection services,

services.AddSingleton<IResponseFactory, ResponseFactory>();
services.AddSingleton<IGateway, Gateway>();
services.AddSingleton<IInertiaSerializer, DefaultInertiaSerializer>();

services.Configure<MvcOptions>(mvcOptions => { mvcOptions.Filters.Add<InertiaActionFilter>(); });

Expand All @@ -56,6 +58,16 @@ public static IServiceCollection AddInertia(this IServiceCollection services,
return services;
}

public static IServiceCollection UseInertiaSerializer<TImplementation>(this IServiceCollection services)
where TImplementation : IInertiaSerializer
{
services.Replace(
new ServiceDescriptor(typeof(IInertiaSerializer), typeof(TImplementation), ServiceLifetime.Singleton)
);

return services;
}

public static IServiceCollection AddViteHelper(this IServiceCollection services,
Action<ViteOptions>? options = null)
{
Expand Down
14 changes: 5 additions & 9 deletions InertiaCore/Response.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using InertiaCore.Extensions;
using InertiaCore.Models;
using InertiaCore.Props;
Expand All @@ -16,13 +14,15 @@ public class Response : IActionResult
private readonly Dictionary<string, object?> _props;
private readonly string _rootView;
private readonly string? _version;
private readonly IInertiaSerializer _serializer;

private ActionContext? _context;
private Page? _page;
private IDictionary<string, object>? _viewData;

internal Response(string component, Dictionary<string, object?> props, string rootView, string? version)
=> (_component, _props, _rootView, _version) = (component, props, rootView, version);
internal Response(string component, Dictionary<string, object?> props, string rootView, string? version,
IInertiaSerializer serializer)
=> (_component, _props, _rootView, _version, _serializer) = (component, props, rootView, version, serializer);

public async Task ExecuteResultAsync(ActionContext context)
{
Expand Down Expand Up @@ -172,11 +172,7 @@ protected internal JsonResult GetJson()
_context!.HttpContext.Response.Headers.Override("Vary", "Accept");
_context!.HttpContext.Response.StatusCode = 200;

return new JsonResult(_page, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
});
return _serializer.SerializeResult(_page);
}

private ViewResult GetView()
Expand Down
18 changes: 6 additions & 12 deletions InertiaCore/ResponseFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using InertiaCore.Models;
using InertiaCore.Props;
using InertiaCore.Ssr;
Expand Down Expand Up @@ -33,12 +31,14 @@ internal class ResponseFactory : IResponseFactory
{
private readonly IHttpContextAccessor _contextAccessor;
private readonly IGateway _gateway;
private readonly IInertiaSerializer _serializer;
private readonly IOptions<InertiaOptions> _options;

private object? _version;

public ResponseFactory(IHttpContextAccessor contextAccessor, IGateway gateway, IOptions<InertiaOptions> options) =>
(_contextAccessor, _gateway, _options) = (contextAccessor, gateway, options);
public ResponseFactory(IHttpContextAccessor contextAccessor, IGateway gateway, IInertiaSerializer serializer,
IOptions<InertiaOptions> options)
=> (_contextAccessor, _gateway, _serializer, _options) = (contextAccessor, gateway, serializer, options);

public Response Render(string component, object? props = null)
{
Expand All @@ -50,7 +50,7 @@ public Response Render(string component, object? props = null)
.ToDictionary(o => o.Name, o => o.GetValue(props))
};

return new Response(component, dictProps, _options.Value.RootView, GetVersion());
return new Response(component, dictProps, _options.Value.RootView, GetVersion(), _serializer);
}

public async Task<IHtmlContent> Head(dynamic model)
Expand Down Expand Up @@ -84,13 +84,7 @@ public async Task<IHtmlContent> Html(dynamic model)
}
}

var data = JsonSerializer.Serialize(model,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
});

var data = _serializer.Serialize(model);
var encoded = WebUtility.HtmlEncode(data);

return new HtmlString($"<div id=\"app\" data-page=\"{encoded}\"></div>");
Expand Down
14 changes: 5 additions & 9 deletions InertiaCore/Ssr/Gateway.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using InertiaCore.Utils;

namespace InertiaCore.Ssr;

Expand All @@ -13,17 +12,14 @@ internal interface IGateway
internal class Gateway : IGateway
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IInertiaSerializer _serializer;

public Gateway(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
public Gateway(IHttpClientFactory httpClientFactory, IInertiaSerializer serializer)
=> (_httpClientFactory, _serializer) = (httpClientFactory, serializer);

public async Task<SsrResponse?> Dispatch(dynamic model, string url)
{
var json = JsonSerializer.Serialize(model,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
});
var json = _serializer.Serialize(model);
var content = new StringContent(json.ToString(), Encoding.UTF8, "application/json");

var client = _httpClientFactory.CreateClient();
Expand Down
27 changes: 27 additions & 0 deletions InertiaCore/Utils/DefaultInertiaSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;

namespace InertiaCore.Utils;

public class DefaultInertiaSerializer : IInertiaSerializer
{
protected static JsonSerializerOptions GetOptions()
{
return new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
};
}

public string Serialize(object? obj)
{
return JsonSerializer.Serialize(obj, GetOptions());
}

public JsonResult SerializeResult(object? obj)
{
return new JsonResult(obj, GetOptions());
}
}
10 changes: 10 additions & 0 deletions InertiaCore/Utils/IInertiaSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Mvc;

namespace InertiaCore.Utils;

public interface IInertiaSerializer
{
public string Serialize(object? obj);

public JsonResult SerializeResult(object? obj);
}
5 changes: 3 additions & 2 deletions InertiaCoreTests/Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ public void Setup()
var contextAccessor = new Mock<IHttpContextAccessor>();
var httpClientFactory = new Mock<IHttpClientFactory>();

var gateway = new Gateway(httpClientFactory.Object);
var serializer = new DefaultInertiaSerializer();
var gateway = new Gateway(httpClientFactory.Object, serializer);
var options = new Mock<IOptions<InertiaOptions>>();
options.SetupGet(x => x.Value).Returns(new InertiaOptions());

_factory = new ResponseFactory(contextAccessor.Object, gateway, options.Object);
_factory = new ResponseFactory(contextAccessor.Object, gateway, serializer, options.Object);
}

/// <summary>
Expand Down
31 changes: 31 additions & 0 deletions InertiaCoreTests/UnitTestConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

namespace InertiaCoreTests;

internal class DummySerializer : DefaultInertiaSerializer
{
}

public partial class Tests
{
[Test]
Expand All @@ -28,6 +32,7 @@ public void TestConfiguration()

Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IResponseFactory)), Is.True);
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IGateway)), Is.True);
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IInertiaSerializer)), Is.True);
});

var mvcConfiguration =
Expand All @@ -45,4 +50,30 @@ public void TestConfiguration()

Assert.DoesNotThrow(() => Inertia.GetVersion());
}

[Test]
[Description("Test if the configuration registers properly custom JSON serializer.")]
public void TestSerializerConfiguration()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddInertia();

Assert.Multiple(() =>
{
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IInertiaSerializer)), Is.True);

Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DefaultInertiaSerializer)), Is.True);
Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DummySerializer)), Is.False);
});

Assert.DoesNotThrow(() => builder.Services.UseInertiaSerializer<DummySerializer>());

Assert.Multiple(() =>
{
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IInertiaSerializer)), Is.True);

Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DefaultInertiaSerializer)), Is.False);
Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DummySerializer)), Is.True);
});
}
}
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,73 @@ builder.Services.AddInertia(options =>
});
```

### Custom JSON serializer

You can use a custom JSON serializer in your app by creating a custom class implementing the `IInertiaSerializer`
interface:

```csharp
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;

public class CustomSerializer : IInertiaSerializer
{
// Used in HTML responses
public string Serialize(object? obj)
{
// Default serialization
return JsonSerializer.Serialize(obj, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
});
}

// Used in JSON responses
public JsonResult SerializeResult(object? obj)
{
// Default serialization
return new JsonResult(obj, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
});
}
}
```

or extending the `DefaultInertiaSerializer` class, which also implements the `IInertiaSerializer` interface:

```csharp
public class CustomSerializer : DefaultInertiaSerializer
{
protected new static JsonSerializerOptions GetOptions()
{
// Default options
return new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
};
}
}
```

You can then register it in the configuration:

```csharp
builder.Services.AddInertia();

[...]

builder.Services.UseInertiaSerializer<CustomSerializer>();

[...]

app.UseInertia();
```

### 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.
Expand Down