Skip to content

Commit c699eee

Browse files
committed
[2.x] Support for a custom URL resolver
inertiajs/inertia-laravel#739
1 parent 81aa149 commit c699eee

File tree

4 files changed

+160
-4
lines changed

4 files changed

+160
-4
lines changed

InertiaCore/Inertia.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using InertiaCore.Props;
33
using InertiaCore.Utils;
44
using Microsoft.AspNetCore.Html;
5+
using Microsoft.AspNetCore.Mvc;
56

67
[assembly: InternalsVisibleTo("InertiaCoreTests")]
78

@@ -31,6 +32,8 @@ public static class Inertia
3132

3233
public static void Share(IDictionary<string, object?> data) => _factory.Share(data);
3334

35+
public static void ResolveUrlUsing(Func<ActionContext, string> urlResolver) => _factory.ResolveUrlUsing(urlResolver);
36+
3437
public static AlwaysProp Always(string value) => _factory.Always(value);
3538

3639
public static AlwaysProp Always(Func<string> callback) => _factory.Always(callback);

InertiaCore/Response.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ public class Response : IActionResult
1818
private readonly string? _version;
1919
private readonly bool _encryptHistory;
2020
private readonly bool _clearHistory;
21+
private readonly Func<ActionContext, string>? _urlResolver;
2122

2223
private ActionContext? _context;
2324
private Page? _page;
2425
private IDictionary<string, object>? _viewData;
2526

26-
internal Response(string component, Dictionary<string, object?> props, string rootView, string? version, bool encryptHistory, bool clearHistory)
27-
=> (_component, _props, _rootView, _version, _encryptHistory, _clearHistory) = (component, props, rootView, version, encryptHistory, clearHistory);
27+
internal Response(string component, Dictionary<string, object?> props, string rootView, string? version, bool encryptHistory, bool clearHistory, Func<ActionContext, string>? urlResolver = null)
28+
=> (_component, _props, _rootView, _version, _encryptHistory, _clearHistory, _urlResolver) = (component, props, rootView, version, encryptHistory, clearHistory, urlResolver);
2829

2930
public async Task ExecuteResultAsync(ActionContext context)
3031
{
@@ -41,7 +42,7 @@ protected internal async Task ProcessResponse()
4142
{
4243
Component = _component,
4344
Version = _version,
44-
Url = _context!.RequestedUri(),
45+
Url = _urlResolver?.Invoke(_context!) ?? _context!.RequestedUri(),
4546
Props = props,
4647
EncryptHistory = _encryptHistory,
4748
ClearHistory = _clearHistory,

InertiaCore/ResponseFactory.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using InertiaCore.Utils;
88
using Microsoft.AspNetCore.Html;
99
using Microsoft.AspNetCore.Http;
10+
using Microsoft.AspNetCore.Mvc;
1011
using Microsoft.Extensions.Options;
1112

1213
namespace InertiaCore;
@@ -24,6 +25,7 @@ internal interface IResponseFactory
2425
public void Share(IDictionary<string, object?> data);
2526
public void ClearHistory(bool clear = true);
2627
public void EncryptHistory(bool encrypt = true);
28+
public void ResolveUrlUsing(Func<ActionContext, string> urlResolver);
2729
public AlwaysProp Always(object? value);
2830
public AlwaysProp Always(Func<object?> callback);
2931
public AlwaysProp Always(Func<Task<object?>> callback);
@@ -40,6 +42,7 @@ internal class ResponseFactory : IResponseFactory
4042
private object? _version;
4143
private bool _clearHistory;
4244
private bool? _encryptHistory;
45+
private Func<ActionContext, string>? _urlResolver;
4346

4447
public ResponseFactory(IHttpContextAccessor contextAccessor, IGateway gateway, IOptions<InertiaOptions> options) =>
4548
(_contextAccessor, _gateway, _options) = (contextAccessor, gateway, options);
@@ -54,7 +57,7 @@ public Response Render(string component, object? props = null)
5457
.ToDictionary(o => o.Name, o => o.GetValue(props))
5558
};
5659

57-
return new Response(component, dictProps, _options.Value.RootView, GetVersion(), _encryptHistory ?? _options.Value.EncryptHistory, _clearHistory);
60+
return new Response(component, dictProps, _options.Value.RootView, GetVersion(), _encryptHistory ?? _options.Value.EncryptHistory, _clearHistory, _urlResolver);
5861
}
5962

6063
public async Task<IHtmlContent> Head(dynamic model)
@@ -139,6 +142,8 @@ public void Share(IDictionary<string, object?> data)
139142

140143
public void EncryptHistory(bool encrypt = true) => _encryptHistory = encrypt;
141144

145+
public void ResolveUrlUsing(Func<ActionContext, string> urlResolver) => _urlResolver = urlResolver;
146+
142147
public LazyProp Lazy(Func<object?> callback) => new(callback);
143148
public LazyProp Lazy(Func<Task<object?>> callback) => new(callback);
144149
public AlwaysProp Always(object? value) => new(value);
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using InertiaCore.Models;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Moq;
5+
6+
namespace InertiaCoreTests;
7+
8+
public partial class Tests
9+
{
10+
[Test]
11+
[Description("Test if custom URL resolver is used when provided.")]
12+
public async Task TestCustomUrlResolver()
13+
{
14+
// Set up a custom URL resolver
15+
_factory.ResolveUrlUsing(context => "/custom/url");
16+
17+
var response = _factory.Render("Test/Page", new
18+
{
19+
Test = "Test"
20+
});
21+
22+
var context = PrepareContext();
23+
24+
response.SetContext(context);
25+
await response.ProcessResponse();
26+
27+
var page = response.GetJson().Value as Page;
28+
29+
Assert.That(page?.Url, Is.EqualTo("/custom/url"));
30+
Assert.That(page?.Props, Is.EqualTo(new Dictionary<string, object?>
31+
{
32+
{ "test", "Test" },
33+
{ "errors", new Dictionary<string, string>(0) }
34+
}));
35+
}
36+
37+
[Test]
38+
[Description("Test if default URL resolver is used when no custom resolver is provided.")]
39+
public async Task TestDefaultUrlResolver()
40+
{
41+
var response = _factory.Render("Test/Page", new
42+
{
43+
Test = "Test"
44+
});
45+
46+
var context = PrepareContext();
47+
48+
response.SetContext(context);
49+
await response.ProcessResponse();
50+
51+
var page = response.GetJson().Value as Page;
52+
53+
// Should use the default RequestedUri() method
54+
Assert.That(page?.Url, Is.Not.Null);
55+
Assert.That(page?.Props, Is.EqualTo(new Dictionary<string, object?>
56+
{
57+
{ "test", "Test" },
58+
{ "errors", new Dictionary<string, string>(0) }
59+
}));
60+
}
61+
62+
[Test]
63+
[Description("Test if custom URL resolver receives correct ActionContext.")]
64+
public async Task TestUrlResolverReceivesContext()
65+
{
66+
ActionContext? receivedContext = null;
67+
68+
// Set up a custom URL resolver that captures the context
69+
_factory.ResolveUrlUsing(context =>
70+
{
71+
receivedContext = context;
72+
return "/captured/context/url";
73+
});
74+
75+
var response = _factory.Render("Test/Page", new
76+
{
77+
Test = "Test"
78+
});
79+
80+
var context = PrepareContext();
81+
82+
response.SetContext(context);
83+
await response.ProcessResponse();
84+
85+
var page = response.GetJson().Value as Page;
86+
87+
Assert.That(page?.Url, Is.EqualTo("/captured/context/url"));
88+
Assert.That(receivedContext, Is.Not.Null);
89+
Assert.That(receivedContext, Is.EqualTo(context));
90+
}
91+
92+
[Test]
93+
[Description("Test if custom URL resolver can access request information.")]
94+
public async Task TestUrlResolverAccessesRequest()
95+
{
96+
// Set up a custom URL resolver that uses request path
97+
_factory.ResolveUrlUsing(context =>
98+
{
99+
var path = context.HttpContext.Request.Path;
100+
return $"/custom{path}";
101+
});
102+
103+
var response = _factory.Render("Test/Page", new
104+
{
105+
Test = "Test"
106+
});
107+
108+
var context = PrepareContext();
109+
110+
response.SetContext(context);
111+
await response.ProcessResponse();
112+
113+
var page = response.GetJson().Value as Page;
114+
115+
Assert.That(page?.Url, Is.Not.Null);
116+
Assert.That(page?.Url, Does.StartWith("/custom"));
117+
}
118+
119+
[Test]
120+
[Description("Test if URL resolver can be changed between requests.")]
121+
public async Task TestUrlResolverCanBeChanged()
122+
{
123+
// First resolver
124+
_factory.ResolveUrlUsing(context => "/first/url");
125+
126+
var response1 = _factory.Render("Test/Page", new { Test = "Test1" });
127+
var context1 = PrepareContext();
128+
129+
response1.SetContext(context1);
130+
await response1.ProcessResponse();
131+
132+
var page1 = response1.GetJson().Value as Page;
133+
Assert.That(page1?.Url, Is.EqualTo("/first/url"));
134+
135+
// Change resolver
136+
_factory.ResolveUrlUsing(context => "/second/url");
137+
138+
var response2 = _factory.Render("Test/Page", new { Test = "Test2" });
139+
var context2 = PrepareContext();
140+
141+
response2.SetContext(context2);
142+
await response2.ProcessResponse();
143+
144+
var page2 = response2.GetJson().Value as Page;
145+
Assert.That(page2?.Url, Is.EqualTo("/second/url"));
146+
}
147+
}

0 commit comments

Comments
 (0)