Skip to content

Commit e5d3c25

Browse files
adrumkapi2289
andauthored
[1.x] Add Always Prop (kapi2289#19)
* added header constants * refactor resolve props * added always prop * added always prop test * added async task wrapper * enable v1 workflows * added tests for partial except header * match the correct behavior for partial props --------- Co-authored-by: kapi2289 <kacper@ziubryniewicz.pl>
1 parent e5644b4 commit e5d3c25

File tree

11 files changed

+442
-47
lines changed

11 files changed

+442
-47
lines changed

.github/workflows/dotnet.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ name: .NET
55

66
on:
77
push:
8-
branches: [ "main" ]
8+
branches:
9+
- "main"
10+
- "v1"
911
pull_request:
10-
branches: [ "main" ]
12+
branches:
13+
- "main"
14+
- "v1"
1115

1216
jobs:
1317
build:

InertiaCore/Extensions/Configure.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.IO.Abstractions;
21
using System.Net;
32
using InertiaCore.Models;
43
using InertiaCore.Ssr;
@@ -25,7 +24,7 @@ public static IApplicationBuilder UseInertia(this IApplicationBuilder app)
2524
{
2625
if (context.IsInertiaRequest()
2726
&& context.Request.Method == "GET"
28-
&& context.Request.Headers["X-Inertia-Version"] != Inertia.GetVersion())
27+
&& context.Request.Headers[InertiaHeader.Version] != Inertia.GetVersion())
2928
{
3029
await OnVersionChange(context, app);
3130
return;
@@ -69,7 +68,7 @@ private static async Task OnVersionChange(HttpContext context, IApplicationBuild
6968

7069
if (tempData.Any()) tempData.Keep();
7170

72-
context.Response.Headers.Override("X-Inertia-Location", context.RequestedUri());
71+
context.Response.Headers.Override(InertiaHeader.Location, context.RequestedUri());
7372
context.Response.StatusCode = (int)HttpStatusCode.Conflict;
7473

7574
await context.Response.CompleteAsync();
Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json;
2+
using InertiaCore.Utils;
23
using Microsoft.AspNetCore.Http;
34
using Microsoft.AspNetCore.Http.Extensions;
45
using Microsoft.AspNetCore.Mvc;
@@ -7,40 +8,51 @@ namespace InertiaCore.Extensions;
78

89
internal static class InertiaExtensions
910
{
10-
internal static IEnumerable<string> Only(this object obj, IEnumerable<string> only) =>
11-
obj.GetType().GetProperties().Select(c => c.Name)
12-
.Intersect(only, StringComparer.OrdinalIgnoreCase).ToList();
11+
internal static Dictionary<string, object?> OnlyProps(this ActionContext context, Dictionary<string, object?> props)
12+
{
13+
var onlyKeys = context.HttpContext.Request.Headers[InertiaHeader.PartialOnly]
14+
.ToString().Split(',')
15+
.Select(k => k.Trim())
16+
.Where(k => !string.IsNullOrEmpty(k))
17+
.ToList();
18+
19+
return props.Where(kv => onlyKeys.Contains(kv.Key, StringComparer.OrdinalIgnoreCase))
20+
.ToDictionary(kv => kv.Key, kv => kv.Value);
21+
}
1322

14-
internal static List<string> GetPartialData(this ActionContext context) =>
15-
context.HttpContext.Request.Headers["X-Inertia-Partial-Data"]
16-
.FirstOrDefault()?.Split(",")
17-
.Where(s => !string.IsNullOrEmpty(s))
18-
.ToList() ?? new List<string>();
23+
internal static Dictionary<string, object?> ExceptProps(this ActionContext context,
24+
Dictionary<string, object?> props)
25+
{
26+
var exceptKeys = context.HttpContext.Request.Headers[InertiaHeader.PartialExcept]
27+
.ToString().Split(',')
28+
.Select(k => k.Trim())
29+
.Where(k => !string.IsNullOrEmpty(k))
30+
.ToList();
31+
32+
return props.Where(kv => exceptKeys.Contains(kv.Key, StringComparer.OrdinalIgnoreCase) == false)
33+
.ToDictionary(kv => kv.Key, kv => kv.Value);
34+
}
1935

2036
internal static bool IsInertiaPartialComponent(this ActionContext context, string component) =>
21-
context.HttpContext.Request.Headers["X-Inertia-Partial-Component"] == component;
37+
context.HttpContext.Request.Headers[InertiaHeader.PartialComponent] == component;
2238

2339
internal static string RequestedUri(this HttpContext context) =>
2440
Uri.UnescapeDataString(context.Request.GetEncodedPathAndQuery());
2541

2642
internal static string RequestedUri(this ActionContext context) => context.HttpContext.RequestedUri();
2743

2844
internal static bool IsInertiaRequest(this HttpContext context) =>
29-
bool.TryParse(context.Request.Headers["X-Inertia"], out _);
45+
bool.TryParse(context.Request.Headers[InertiaHeader.Inertia], out _);
3046

3147
internal static bool IsInertiaRequest(this ActionContext context) => context.HttpContext.IsInertiaRequest();
3248

3349
internal static string ToCamelCase(this string s) => JsonNamingPolicy.CamelCase.ConvertName(s);
3450

3551
internal static bool Override<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
3652
{
37-
if (dictionary.ContainsKey(key))
38-
{
39-
dictionary[key] = value;
40-
return true;
41-
}
42-
43-
dictionary.Add(key, value);
44-
return false;
53+
if (dictionary.TryAdd(key, value)) return false;
54+
dictionary[key] = value;
55+
56+
return true;
4557
}
4658
}

InertiaCore/Inertia.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,13 @@ public static class Inertia
2828

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

31+
public static AlwaysProp Always(object? value) => _factory.Always(value);
32+
33+
public static AlwaysProp Always(Func<object?> callback) => _factory.Always(callback);
34+
35+
public static AlwaysProp Always(Func<Task<object?>> callback) => _factory.Always(callback);
36+
3137
public static LazyProp Lazy(Func<object?> callback) => _factory.Lazy(callback);
38+
39+
public static LazyProp Lazy(Func<Task<object?>> callback) => _factory.Lazy(callback);
3240
}

InertiaCore/Response.cs

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,10 @@ protected internal void ProcessResponse()
3737
{
3838
Component = _component,
3939
Version = _version,
40-
Url = _context!.RequestedUri()
40+
Url = _context!.RequestedUri(),
41+
Props = ResolveProperties(_props.GetType().GetProperties().ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)))
4142
};
4243

43-
var partial = _context!.GetPartialData();
44-
if (partial.Any() && _context!.IsInertiaPartialComponent(_component))
45-
{
46-
var only = _props.Only(partial);
47-
var partialProps = only.ToDictionary(o => o.ToCamelCase(), o =>
48-
_props.GetType().GetProperty(o)?.GetValue(_props));
49-
50-
page.Props = partialProps;
51-
}
52-
else
53-
{
54-
var props = _props.GetType().GetProperties()
55-
.Where(o => o.PropertyType != typeof(LazyProp))
56-
.ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props));
57-
58-
page.Props = props;
59-
}
60-
61-
page.Props = PrepareProps(page.Props);
62-
6344
var shared = _context!.HttpContext.Features.Get<InertiaSharedData>();
6445
if (shared != null)
6546
page.Props = shared.GetMerged(page.Props);
@@ -75,13 +56,14 @@ protected internal void ProcessResponse()
7556
{
7657
Func<object?> f => f.Invoke(),
7758
LazyProp l => l.Invoke(),
59+
AlwaysProp l => l.Invoke(),
7860
_ => pair.Value
7961
});
8062
}
8163

8264
protected internal JsonResult GetJson()
8365
{
84-
_context!.HttpContext.Response.Headers.Override("X-Inertia", "true");
66+
_context!.HttpContext.Response.Headers.Override(InertiaHeader.Inertia, "true");
8567
_context!.HttpContext.Response.Headers.Override("Vary", "Accept");
8668
_context!.HttpContext.Response.StatusCode = 200;
8769

@@ -127,4 +109,56 @@ public Response WithViewData(IDictionary<string, object> viewData)
127109
_viewData = viewData;
128110
return this;
129111
}
112+
113+
private Dictionary<string, object?> ResolveProperties(Dictionary<string, object?> props)
114+
{
115+
var isPartial = _context!.IsInertiaPartialComponent(_component);
116+
117+
if (!isPartial)
118+
{
119+
props = props
120+
.Where(kv => kv.Value is not LazyProp)
121+
.ToDictionary(kv => kv.Key, kv => kv.Value);
122+
}
123+
else
124+
{
125+
props = props.ToDictionary(kv => kv.Key, kv => kv.Value);
126+
127+
if (_context!.HttpContext.Request.Headers.ContainsKey(InertiaHeader.PartialOnly))
128+
{
129+
props = ResolveOnly(props);
130+
}
131+
132+
if (_context!.HttpContext.Request.Headers.ContainsKey(InertiaHeader.PartialExcept))
133+
{
134+
props = ResolveExcept(props);
135+
}
136+
}
137+
138+
props = ResolveAlways(props);
139+
props = PrepareProps(props);
140+
141+
return props;
142+
}
143+
144+
private Dictionary<string, object?> ResolveOnly(Dictionary<string, object?> props)
145+
{
146+
return _context!.OnlyProps(props);
147+
}
148+
149+
private Dictionary<string, object?> ResolveExcept(Dictionary<string, object?> props)
150+
{
151+
return _context!.ExceptProps(props);
152+
}
153+
154+
private Dictionary<string, object?> ResolveAlways(Dictionary<string, object?> props)
155+
{
156+
var alwaysProps = _props.GetType().GetProperties()
157+
.Where(o => o.PropertyType == typeof(AlwaysProp))
158+
.ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)); ;
159+
160+
return props
161+
.Where(kv => kv.Value is not AlwaysProp)
162+
.Concat(alwaysProps).ToDictionary(kv => kv.Key, kv => kv.Value);
163+
}
130164
}

InertiaCore/ResponseFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ internal interface IResponseFactory
2121
public LocationResult Location(string url);
2222
public void Share(string key, object? value);
2323
public void Share(IDictionary<string, object?> data);
24+
public AlwaysProp Always(object? value);
25+
public AlwaysProp Always(Func<object?> callback);
26+
public AlwaysProp Always(Func<Task<object?>> callback);
2427
public LazyProp Lazy(Func<object?> callback);
2528
public LazyProp Lazy(Func<Task<object?>> callback);
2629
}
@@ -121,4 +124,7 @@ public void Share(IDictionary<string, object?> data)
121124

122125
public LazyProp Lazy(Func<object?> callback) => new LazyProp(callback);
123126
public LazyProp Lazy(Func<Task<object?>> callback) => new LazyProp(callback);
127+
public AlwaysProp Always(object? value) => new AlwaysProp(value);
128+
public AlwaysProp Always(Func<object?> callback) => new AlwaysProp(callback);
129+
public AlwaysProp Always(Func<Task<object?>> callback) => new AlwaysProp(callback);
124130
}

InertiaCore/Utils/AlwaysProp.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace InertiaCore.Utils;
2+
3+
public class AlwaysProp
4+
{
5+
private readonly object? _value;
6+
7+
public AlwaysProp(object? value)
8+
{
9+
_value = value;
10+
}
11+
12+
public object? Invoke()
13+
{
14+
// Check if the value is a callable delegate
15+
return Task.Run(async () =>
16+
{
17+
return _value switch
18+
{
19+
Func<Task<object?>> asyncCallable => await asyncCallable.Invoke(),
20+
Func<object?> callable => callable.Invoke(),
21+
_ => _value
22+
};
23+
}).GetAwaiter().GetResult();
24+
}
25+
}

InertiaCore/Utils/InertiaHeader.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace InertiaCore.Utils;
2+
3+
public static class InertiaHeader
4+
{
5+
public const string Inertia = "X-Inertia";
6+
7+
public const string ErrorBag = "X-Inertia-Error-Bag";
8+
9+
public const string Location = "X-Inertia-Location";
10+
11+
public const string Version = "X-Inertia-Version";
12+
13+
public const string PartialComponent = "X-Inertia-Partial-Component";
14+
15+
public const string PartialOnly = "X-Inertia-Partial-Data";
16+
17+
public const string PartialExcept = "X-Inertia-Partial-Except";
18+
}

InertiaCore/Utils/LocationResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public async Task ExecuteResultAsync(ActionContext context)
1414
{
1515
if (context.IsInertiaRequest())
1616
{
17-
context.HttpContext.Response.Headers.Override("X-Inertia-Location", _url);
17+
context.HttpContext.Response.Headers.Override(InertiaHeader.Location, _url);
1818
await new StatusCodeResult((int)HttpStatusCode.Conflict).ExecuteResultAsync(context);
1919
return;
2020
}

0 commit comments

Comments
 (0)