Skip to content

Commit f39a6e6

Browse files
authored
[1.x] Add custom JSON serializer (#27)
* Implement custom JSON serializer configuration * Add custom JSON serializer to README * Remove unused imports
1 parent 5a1c858 commit f39a6e6

9 files changed

Lines changed: 166 additions & 32 deletions

File tree

InertiaCore/Extensions/Configure.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Mvc;
88
using Microsoft.AspNetCore.Mvc.ViewFeatures;
99
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.DependencyInjection.Extensions;
1011

1112
namespace InertiaCore.Extensions;
1213

@@ -48,6 +49,7 @@ public static IServiceCollection AddInertia(this IServiceCollection services,
4849

4950
services.AddSingleton<IResponseFactory, ResponseFactory>();
5051
services.AddSingleton<IGateway, Gateway>();
52+
services.AddSingleton<IInertiaSerializer, DefaultInertiaSerializer>();
5153

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

@@ -56,6 +58,16 @@ public static IServiceCollection AddInertia(this IServiceCollection services,
5658
return services;
5759
}
5860

61+
public static IServiceCollection UseInertiaSerializer<TImplementation>(this IServiceCollection services)
62+
where TImplementation : IInertiaSerializer
63+
{
64+
services.Replace(
65+
new ServiceDescriptor(typeof(IInertiaSerializer), typeof(TImplementation), ServiceLifetime.Singleton)
66+
);
67+
68+
return services;
69+
}
70+
5971
public static IServiceCollection AddViteHelper(this IServiceCollection services,
6072
Action<ViteOptions>? options = null)
6173
{

InertiaCore/Response.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Text.Json;
2-
using System.Text.Json.Serialization;
31
using InertiaCore.Extensions;
42
using InertiaCore.Models;
53
using InertiaCore.Props;
@@ -16,13 +14,15 @@ public class Response : IActionResult
1614
private readonly Dictionary<string, object?> _props;
1715
private readonly string _rootView;
1816
private readonly string? _version;
17+
private readonly IInertiaSerializer _serializer;
1918

2019
private ActionContext? _context;
2120
private Page? _page;
2221
private IDictionary<string, object>? _viewData;
2322

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

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

175-
return new JsonResult(_page, new JsonSerializerOptions
176-
{
177-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
178-
ReferenceHandler = ReferenceHandler.IgnoreCycles
179-
});
175+
return _serializer.SerializeResult(_page);
180176
}
181177

182178
private ViewResult GetView()

InertiaCore/ResponseFactory.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System.Net;
2-
using System.Text.Json;
3-
using System.Text.Json.Serialization;
42
using InertiaCore.Models;
53
using InertiaCore.Props;
64
using InertiaCore.Ssr;
@@ -33,12 +31,14 @@ internal class ResponseFactory : IResponseFactory
3331
{
3432
private readonly IHttpContextAccessor _contextAccessor;
3533
private readonly IGateway _gateway;
34+
private readonly IInertiaSerializer _serializer;
3635
private readonly IOptions<InertiaOptions> _options;
3736

3837
private object? _version;
3938

40-
public ResponseFactory(IHttpContextAccessor contextAccessor, IGateway gateway, IOptions<InertiaOptions> options) =>
41-
(_contextAccessor, _gateway, _options) = (contextAccessor, gateway, options);
39+
public ResponseFactory(IHttpContextAccessor contextAccessor, IGateway gateway, IInertiaSerializer serializer,
40+
IOptions<InertiaOptions> options)
41+
=> (_contextAccessor, _gateway, _serializer, _options) = (contextAccessor, gateway, serializer, options);
4242

4343
public Response Render(string component, object? props = null)
4444
{
@@ -50,7 +50,7 @@ public Response Render(string component, object? props = null)
5050
.ToDictionary(o => o.Name, o => o.GetValue(props))
5151
};
5252

53-
return new Response(component, dictProps, _options.Value.RootView, GetVersion());
53+
return new Response(component, dictProps, _options.Value.RootView, GetVersion(), _serializer);
5454
}
5555

5656
public async Task<IHtmlContent> Head(dynamic model)
@@ -84,13 +84,7 @@ public async Task<IHtmlContent> Html(dynamic model)
8484
}
8585
}
8686

87-
var data = JsonSerializer.Serialize(model,
88-
new JsonSerializerOptions
89-
{
90-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
91-
ReferenceHandler = ReferenceHandler.IgnoreCycles
92-
});
93-
87+
var data = _serializer.Serialize(model);
9488
var encoded = WebUtility.HtmlEncode(data);
9589

9690
return new HtmlString($"<div id=\"app\" data-page=\"{encoded}\"></div>");

InertiaCore/Ssr/Gateway.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Net.Http.Json;
22
using System.Text;
3-
using System.Text.Json;
4-
using System.Text.Json.Serialization;
3+
using InertiaCore.Utils;
54

65
namespace InertiaCore.Ssr;
76

@@ -13,17 +12,14 @@ internal interface IGateway
1312
internal class Gateway : IGateway
1413
{
1514
private readonly IHttpClientFactory _httpClientFactory;
15+
private readonly IInertiaSerializer _serializer;
1616

17-
public Gateway(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
17+
public Gateway(IHttpClientFactory httpClientFactory, IInertiaSerializer serializer)
18+
=> (_httpClientFactory, _serializer) = (httpClientFactory, serializer);
1819

1920
public async Task<SsrResponse?> Dispatch(dynamic model, string url)
2021
{
21-
var json = JsonSerializer.Serialize(model,
22-
new JsonSerializerOptions
23-
{
24-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
25-
ReferenceHandler = ReferenceHandler.IgnoreCycles
26-
});
22+
var json = _serializer.Serialize(model);
2723
var content = new StringContent(json.ToString(), Encoding.UTF8, "application/json");
2824

2925
var client = _httpClientFactory.CreateClient();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
using Microsoft.AspNetCore.Mvc;
4+
5+
namespace InertiaCore.Utils;
6+
7+
public class DefaultInertiaSerializer : IInertiaSerializer
8+
{
9+
protected static JsonSerializerOptions GetOptions()
10+
{
11+
return new JsonSerializerOptions
12+
{
13+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
14+
ReferenceHandler = ReferenceHandler.IgnoreCycles
15+
};
16+
}
17+
18+
public string Serialize(object? obj)
19+
{
20+
return JsonSerializer.Serialize(obj, GetOptions());
21+
}
22+
23+
public JsonResult SerializeResult(object? obj)
24+
{
25+
return new JsonResult(obj, GetOptions());
26+
}
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace InertiaCore.Utils;
4+
5+
public interface IInertiaSerializer
6+
{
7+
public string Serialize(object? obj);
8+
9+
public JsonResult SerializeResult(object? obj);
10+
}

InertiaCoreTests/Setup.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ public void Setup()
2222
var contextAccessor = new Mock<IHttpContextAccessor>();
2323
var httpClientFactory = new Mock<IHttpClientFactory>();
2424

25-
var gateway = new Gateway(httpClientFactory.Object);
25+
var serializer = new DefaultInertiaSerializer();
26+
var gateway = new Gateway(httpClientFactory.Object, serializer);
2627
var options = new Mock<IOptions<InertiaOptions>>();
2728
options.SetupGet(x => x.Value).Returns(new InertiaOptions());
2829

29-
_factory = new ResponseFactory(contextAccessor.Object, gateway, options.Object);
30+
_factory = new ResponseFactory(contextAccessor.Object, gateway, serializer, options.Object);
3031
}
3132

3233
/// <summary>

InertiaCoreTests/UnitTestConfiguration.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
namespace InertiaCoreTests;
1111

12+
internal class DummySerializer : DefaultInertiaSerializer
13+
{
14+
}
15+
1216
public partial class Tests
1317
{
1418
[Test]
@@ -28,6 +32,7 @@ public void TestConfiguration()
2832

2933
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IResponseFactory)), Is.True);
3034
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IGateway)), Is.True);
35+
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IInertiaSerializer)), Is.True);
3136
});
3237

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

4651
Assert.DoesNotThrow(() => Inertia.GetVersion());
4752
}
53+
54+
[Test]
55+
[Description("Test if the configuration registers properly custom JSON serializer.")]
56+
public void TestSerializerConfiguration()
57+
{
58+
var builder = WebApplication.CreateBuilder();
59+
builder.Services.AddInertia();
60+
61+
Assert.Multiple(() =>
62+
{
63+
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IInertiaSerializer)), Is.True);
64+
65+
Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DefaultInertiaSerializer)), Is.True);
66+
Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DummySerializer)), Is.False);
67+
});
68+
69+
Assert.DoesNotThrow(() => builder.Services.UseInertiaSerializer<DummySerializer>());
70+
71+
Assert.Multiple(() =>
72+
{
73+
Assert.That(builder.Services.Any(s => s.ServiceType == typeof(IInertiaSerializer)), Is.True);
74+
75+
Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DefaultInertiaSerializer)), Is.False);
76+
Assert.That(builder.Services.Any(s => s.ImplementationType == typeof(DummySerializer)), Is.True);
77+
});
78+
}
4879
}

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,73 @@ builder.Services.AddInertia(options =>
217217
});
218218
```
219219

220+
### Custom JSON serializer
221+
222+
You can use a custom JSON serializer in your app by creating a custom class implementing the `IInertiaSerializer`
223+
interface:
224+
225+
```csharp
226+
using System.Text.Json;
227+
using System.Text.Json.Serialization;
228+
using Microsoft.AspNetCore.Mvc;
229+
230+
public class CustomSerializer : IInertiaSerializer
231+
{
232+
// Used in HTML responses
233+
public string Serialize(object? obj)
234+
{
235+
// Default serialization
236+
return JsonSerializer.Serialize(obj, new JsonSerializerOptions
237+
{
238+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
239+
ReferenceHandler = ReferenceHandler.IgnoreCycles
240+
});
241+
}
242+
243+
// Used in JSON responses
244+
public JsonResult SerializeResult(object? obj)
245+
{
246+
// Default serialization
247+
return new JsonResult(obj, new JsonSerializerOptions
248+
{
249+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
250+
ReferenceHandler = ReferenceHandler.IgnoreCycles
251+
});
252+
}
253+
}
254+
```
255+
256+
or extending the `DefaultInertiaSerializer` class, which also implements the `IInertiaSerializer` interface:
257+
258+
```csharp
259+
public class CustomSerializer : DefaultInertiaSerializer
260+
{
261+
protected new static JsonSerializerOptions GetOptions()
262+
{
263+
// Default options
264+
return new JsonSerializerOptions
265+
{
266+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
267+
ReferenceHandler = ReferenceHandler.IgnoreCycles
268+
};
269+
}
270+
}
271+
```
272+
273+
You can then register it in the configuration:
274+
275+
```csharp
276+
builder.Services.AddInertia();
277+
278+
[...]
279+
280+
builder.Services.UseInertiaSerializer<CustomSerializer>();
281+
282+
[...]
283+
284+
app.UseInertia();
285+
```
286+
220287
### Vite Helper
221288

222289
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.

0 commit comments

Comments
 (0)