Skip to content
Open
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
105 changes: 102 additions & 3 deletions InertiaCore/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using InertiaCore.Models;
using InertiaCore.Props;
using InertiaCore.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
Expand Down Expand Up @@ -60,9 +61,10 @@ protected internal async Task ProcessResponse()
var props = _props;

props = ResolveSharedProps(props);
props = ResolveInertiaPropertyProviders(props);
props = ResolvePartialProperties(props);
props = ResolveAlways(props);
props = await ResolvePropertyInstances(props);
props = await ResolvePropertyInstances(props, _context!.HttpContext.Request);

return props;
}
Expand All @@ -79,6 +81,32 @@ protected internal async Task ProcessResponse()
return props;
}

/// <summary>
/// Resolve properties from objects implementing ProvidesInertiaProperties.
/// </summary>
private Dictionary<string, object?> ResolveInertiaPropertyProviders(Dictionary<string, object?> props)
{
var context = new RenderContext(_component, _context!.HttpContext.Request);

foreach (var pair in props.ToList())
{
if (pair.Value is ProvidesInertiaProperties provider)
{
// Remove the provider object itself
props.Remove(pair.Key);

// Add the properties it provides
var providedProps = provider.ToInertiaProperties(context);
foreach (var providedProp in providedProps)
{
props[providedProp.Key] = providedProp.Value;
}
}
}

return props;
}

/// <summary>
/// Resolve the `only` and `except` partial request props.
/// </summary>
Expand Down Expand Up @@ -147,7 +175,7 @@ protected internal async Task ProcessResponse()
/// <summary>
/// Resolve all necessary class instances in the given props.
/// </summary>
private static async Task<Dictionary<string, object?>> ResolvePropertyInstances(Dictionary<string, object?> props)
private static async Task<Dictionary<string, object?>> ResolvePropertyInstances(Dictionary<string, object?> props, HttpRequest request)
{
return (await Task.WhenAll(props.Select(async pair =>
{
Expand All @@ -158,12 +186,13 @@ protected internal async Task ProcessResponse()
Func<object?> f => (key, await f.ResolveAsync()),
Task t => (key, await t.ResolveResult()),
InvokableProp p => (key, await p.Invoke()),
ProvidesInertiaProperty pip => (key, pip.ToInertiaProperty(new PropertyContext(key, props, request))),
_ => (key, pair.Value)
};

if (value.Item2 is Dictionary<string, object?> dict)
{
value = (key, await ResolvePropertyInstances(dict));
value = (key, await ResolvePropertyInstances(dict, request));
}

return value;
Expand Down Expand Up @@ -218,4 +247,74 @@ public Response WithViewData(IDictionary<string, object> viewData)
_viewData = viewData;
return this;
}

/// <summary>
/// Add additional properties to the page.
/// </summary>
/// <param name="key">The property key, a dictionary of properties, or a ProvidesInertiaProperties object</param>
/// <param name="value">The property value (only used when key is a string)</param>
/// <returns>The Response instance for method chaining</returns>
public Response With(string key, object? value)
{
_props[key] = value;
return this;
}

/// <summary>
/// Add additional properties to the page from a dictionary.
/// </summary>
/// <param name="properties">Dictionary of properties to add</param>
/// <returns>The Response instance for method chaining</returns>
public Response With(IDictionary<string, object?> properties)
{
foreach (var kvp in properties)
{
_props[kvp.Key] = kvp.Value;
}
return this;
}

/// <summary>
/// Add additional properties to the page from a ProvidesInertiaProperties object.
/// </summary>
/// <param name="provider">The property provider</param>
/// <returns>The Response instance for method chaining</returns>
public Response With(ProvidesInertiaProperties provider)
{
// Generate a unique key for the provider
var providerKey = $"__provider_{Guid.NewGuid():N}";
_props[providerKey] = provider;
return this;
}

/// <summary>
/// Add additional properties to the page from an anonymous object.
/// </summary>
/// <param name="properties">Anonymous object with properties to add</param>
/// <returns>The Response instance for method chaining</returns>
public Response With(object properties)
{
if (properties == null) return this;

if (properties is IDictionary<string, object?> dict)
{
return With(dict);
}

if (properties is ProvidesInertiaProperties provider)
{
return With(provider);
}

// Convert anonymous object to dictionary
var props = properties.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(properties));

foreach (var kvp in props)
{
_props[kvp.Key] = kvp.Value;
}

return this;
}
}
37 changes: 37 additions & 0 deletions InertiaCore/Utils/PropertyContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Http;

namespace InertiaCore.Utils;

/// <summary>
/// Provides context information for individual property transformation.
/// </summary>
public class PropertyContext
{
/// <summary>
/// The key of the property being transformed.
/// </summary>
public string Key { get; }

/// <summary>
/// All properties in the response.
/// </summary>
public Dictionary<string, object?> Props { get; }

/// <summary>
/// The current HTTP request.
/// </summary>
public HttpRequest Request { get; }

/// <summary>
/// Initializes a new instance of the PropertyContext class.
/// </summary>
/// <param name="key">The property key</param>
/// <param name="props">All properties</param>
/// <param name="request">The HTTP request</param>
public PropertyContext(string key, Dictionary<string, object?> props, HttpRequest request)
{
Key = key;
Props = props;
Request = request;
}
}
16 changes: 16 additions & 0 deletions InertiaCore/Utils/ProvidesInertiaProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections;

namespace InertiaCore.Utils;

/// <summary>
/// Interface for objects that can provide dynamic Inertia properties.
/// </summary>
public interface ProvidesInertiaProperties
{
/// <summary>
/// Generates Inertia properties based on the current render context.
/// </summary>
/// <param name="context">The render context containing component name and request information</param>
/// <returns>An enumerable of key-value pairs representing the properties</returns>
IEnumerable<KeyValuePair<string, object?>> ToInertiaProperties(RenderContext context);
}
14 changes: 14 additions & 0 deletions InertiaCore/Utils/ProvidesInertiaProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace InertiaCore.Utils;

/// <summary>
/// Interface for objects that can transform themselves into an Inertia property value.
/// </summary>
public interface ProvidesInertiaProperty
{
/// <summary>
/// Transforms the object into an Inertia property value based on the given context.
/// </summary>
/// <param name="context">The property context containing key, props, and request information</param>
/// <returns>The transformed property value</returns>
object? ToInertiaProperty(PropertyContext context);
}
30 changes: 30 additions & 0 deletions InertiaCore/Utils/RenderContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Http;

namespace InertiaCore.Utils;

/// <summary>
/// Provides context information for Inertia property generation.
/// </summary>
public class RenderContext
{
/// <summary>
/// The name of the component being rendered.
/// </summary>
public string Component { get; }

/// <summary>
/// The current HTTP request.
/// </summary>
public HttpRequest Request { get; }

/// <summary>
/// Initializes a new instance of the RenderContext class.
/// </summary>
/// <param name="component">The component name</param>
/// <param name="request">The HTTP request</param>
public RenderContext(string component, HttpRequest request)
{
Component = component;
Request = request;
}
}
Loading