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
8 changes: 6 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ name: .NET

on:
push:
branches: [ "main" ]
branches:
- "main"
- "v1"
pull_request:
branches: [ "main" ]
branches:
- "main"
- "v1"

jobs:
build:
Expand Down
5 changes: 2 additions & 3 deletions InertiaCore/Extensions/Configure.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.IO.Abstractions;
using System.Net;
using InertiaCore.Models;
using InertiaCore.Ssr;
Expand All @@ -25,7 +24,7 @@ public static IApplicationBuilder UseInertia(this IApplicationBuilder app)
{
if (context.IsInertiaRequest()
&& context.Request.Method == "GET"
&& context.Request.Headers["X-Inertia-Version"] != Inertia.GetVersion())
&& context.Request.Headers[InertiaHeader.Version] != Inertia.GetVersion())
{
await OnVersionChange(context, app);
return;
Expand Down Expand Up @@ -69,7 +68,7 @@ private static async Task OnVersionChange(HttpContext context, IApplicationBuild

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

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

await context.Response.CompleteAsync();
Expand Down
48 changes: 30 additions & 18 deletions InertiaCore/Extensions/InertiaExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using InertiaCore.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -7,40 +8,51 @@ namespace InertiaCore.Extensions;

internal static class InertiaExtensions
{
internal static IEnumerable<string> Only(this object obj, IEnumerable<string> only) =>
obj.GetType().GetProperties().Select(c => c.Name)
.Intersect(only, StringComparer.OrdinalIgnoreCase).ToList();
internal static Dictionary<string, object?> OnlyProps(this ActionContext context, Dictionary<string, object?> props)
{
var onlyKeys = context.HttpContext.Request.Headers[InertiaHeader.PartialOnly]
.ToString().Split(',')
.Select(k => k.Trim())
.Where(k => !string.IsNullOrEmpty(k))
.ToList();

return props.Where(kv => onlyKeys.Contains(kv.Key, StringComparer.OrdinalIgnoreCase))
.ToDictionary(kv => kv.Key, kv => kv.Value);
}

internal static List<string> GetPartialData(this ActionContext context) =>
context.HttpContext.Request.Headers["X-Inertia-Partial-Data"]
.FirstOrDefault()?.Split(",")
.Where(s => !string.IsNullOrEmpty(s))
.ToList() ?? new List<string>();
internal static Dictionary<string, object?> ExceptProps(this ActionContext context,
Dictionary<string, object?> props)
{
var exceptKeys = context.HttpContext.Request.Headers[InertiaHeader.PartialExcept]
.ToString().Split(',')
.Select(k => k.Trim())
.Where(k => !string.IsNullOrEmpty(k))
.ToList();

return props.Where(kv => exceptKeys.Contains(kv.Key, StringComparer.OrdinalIgnoreCase) == false)
.ToDictionary(kv => kv.Key, kv => kv.Value);
}

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

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

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

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

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

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

internal static bool Override<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
if (dictionary.ContainsKey(key))
{
dictionary[key] = value;
return true;
}

dictionary.Add(key, value);
return false;
if (dictionary.TryAdd(key, value)) return false;
dictionary[key] = value;

return true;
}
}
8 changes: 8 additions & 0 deletions InertiaCore/Inertia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,13 @@ public static class Inertia

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

public static AlwaysProp Always(object? value) => _factory.Always(value);

public static AlwaysProp Always(Func<object?> callback) => _factory.Always(callback);

public static AlwaysProp Always(Func<Task<object?>> callback) => _factory.Always(callback);

public static LazyProp Lazy(Func<object?> callback) => _factory.Lazy(callback);

public static LazyProp Lazy(Func<Task<object?>> callback) => _factory.Lazy(callback);
}
78 changes: 56 additions & 22 deletions InertiaCore/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,10 @@ protected internal void ProcessResponse()
{
Component = _component,
Version = _version,
Url = _context!.RequestedUri()
Url = _context!.RequestedUri(),
Props = ResolveProperties(_props.GetType().GetProperties().ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)))
};

var partial = _context!.GetPartialData();
if (partial.Any() && _context!.IsInertiaPartialComponent(_component))
{
var only = _props.Only(partial);
var partialProps = only.ToDictionary(o => o.ToCamelCase(), o =>
_props.GetType().GetProperty(o)?.GetValue(_props));

page.Props = partialProps;
}
else
{
var props = _props.GetType().GetProperties()
.Where(o => o.PropertyType != typeof(LazyProp))
.ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props));

page.Props = props;
}

page.Props = PrepareProps(page.Props);

var shared = _context!.HttpContext.Features.Get<InertiaSharedData>();
if (shared != null)
page.Props = shared.GetMerged(page.Props);
Expand All @@ -75,13 +56,14 @@ protected internal void ProcessResponse()
{
Func<object?> f => f.Invoke(),
LazyProp l => l.Invoke(),
AlwaysProp l => l.Invoke(),
_ => pair.Value
});
}

protected internal JsonResult GetJson()
{
_context!.HttpContext.Response.Headers.Override("X-Inertia", "true");
_context!.HttpContext.Response.Headers.Override(InertiaHeader.Inertia, "true");
_context!.HttpContext.Response.Headers.Override("Vary", "Accept");
_context!.HttpContext.Response.StatusCode = 200;

Expand Down Expand Up @@ -127,4 +109,56 @@ public Response WithViewData(IDictionary<string, object> viewData)
_viewData = viewData;
return this;
}

private Dictionary<string, object?> ResolveProperties(Dictionary<string, object?> props)
{
var isPartial = _context!.IsInertiaPartialComponent(_component);

if (!isPartial)
{
props = props
.Where(kv => kv.Value is not LazyProp)
.ToDictionary(kv => kv.Key, kv => kv.Value);
}
else
{
props = props.ToDictionary(kv => kv.Key, kv => kv.Value);

if (_context!.HttpContext.Request.Headers.ContainsKey(InertiaHeader.PartialOnly))
{
props = ResolveOnly(props);
}

if (_context!.HttpContext.Request.Headers.ContainsKey(InertiaHeader.PartialExcept))
{
props = ResolveExcept(props);
}
}

props = ResolveAlways(props);
props = PrepareProps(props);

return props;
}

private Dictionary<string, object?> ResolveOnly(Dictionary<string, object?> props)
{
return _context!.OnlyProps(props);
}

private Dictionary<string, object?> ResolveExcept(Dictionary<string, object?> props)
{
return _context!.ExceptProps(props);
}

private Dictionary<string, object?> ResolveAlways(Dictionary<string, object?> props)
{
var alwaysProps = _props.GetType().GetProperties()
.Where(o => o.PropertyType == typeof(AlwaysProp))
.ToDictionary(o => o.Name.ToCamelCase(), o => o.GetValue(_props)); ;

return props
.Where(kv => kv.Value is not AlwaysProp)
.Concat(alwaysProps).ToDictionary(kv => kv.Key, kv => kv.Value);
}
}
6 changes: 6 additions & 0 deletions InertiaCore/ResponseFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ internal interface IResponseFactory
public LocationResult Location(string url);
public void Share(string key, object? value);
public void Share(IDictionary<string, object?> data);
public AlwaysProp Always(object? value);
public AlwaysProp Always(Func<object?> callback);
public AlwaysProp Always(Func<Task<object?>> callback);
public LazyProp Lazy(Func<object?> callback);
public LazyProp Lazy(Func<Task<object?>> callback);
}
Expand Down Expand Up @@ -121,4 +124,7 @@ public void Share(IDictionary<string, object?> data)

public LazyProp Lazy(Func<object?> callback) => new LazyProp(callback);
public LazyProp Lazy(Func<Task<object?>> callback) => new LazyProp(callback);
public AlwaysProp Always(object? value) => new AlwaysProp(value);
public AlwaysProp Always(Func<object?> callback) => new AlwaysProp(callback);
public AlwaysProp Always(Func<Task<object?>> callback) => new AlwaysProp(callback);
}
25 changes: 25 additions & 0 deletions InertiaCore/Utils/AlwaysProp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace InertiaCore.Utils;

public class AlwaysProp
{
private readonly object? _value;

public AlwaysProp(object? value)
{
_value = value;
}

public object? Invoke()
{
// Check if the value is a callable delegate
return Task.Run(async () =>
{
return _value switch
{
Func<Task<object?>> asyncCallable => await asyncCallable.Invoke(),
Func<object?> callable => callable.Invoke(),
_ => _value
};
}).GetAwaiter().GetResult();
}
}
18 changes: 18 additions & 0 deletions InertiaCore/Utils/InertiaHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace InertiaCore.Utils;

public static class InertiaHeader
{
public const string Inertia = "X-Inertia";

public const string ErrorBag = "X-Inertia-Error-Bag";

public const string Location = "X-Inertia-Location";

public const string Version = "X-Inertia-Version";

public const string PartialComponent = "X-Inertia-Partial-Component";

public const string PartialOnly = "X-Inertia-Partial-Data";

public const string PartialExcept = "X-Inertia-Partial-Except";
}
2 changes: 1 addition & 1 deletion InertiaCore/Utils/LocationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public async Task ExecuteResultAsync(ActionContext context)
{
if (context.IsInertiaRequest())
{
context.HttpContext.Response.Headers.Override("X-Inertia-Location", _url);
context.HttpContext.Response.Headers.Override(InertiaHeader.Location, _url);
await new StatusCodeResult((int)HttpStatusCode.Conflict).ExecuteResultAsync(context);
return;
}
Expand Down
Loading
Loading