Skip to content

Commit

Permalink
Merge pull request #37 from fortedigital/global-context-support
Browse files Browse the repository at this point in the history
Added global context to render in renderToString.js script.
  • Loading branch information
smydolf authored Aug 8, 2024
2 parents f80eb82 + 2869aa7 commit c96cd6c
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 32 deletions.
6 changes: 6 additions & 0 deletions Forte.Web.React/Configuration/ReactConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public class ReactConfiguration
/// </summary>
public string NameOfObjectToSaveProps { get; set; } = "__reactProps";

/// <summary>
/// Name of the object used to save global data object. Default value is "__globalData".
/// <remarks>NameOfGlobalDataToSave is supported by <see cref="IReactService.RenderToStringAsync"/> method.</remarks>
/// </summary>
public string NameOfGlobalDataToSave { get; set; } = "__globalData";

/// <summary>
/// Indicates whether caching is used. Default value is "true".
/// <remarks>
Expand Down
4 changes: 3 additions & 1 deletion Forte.Web.React/ForteWebReactExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public static void AddReact(this IServiceCollection services,
}

public static void UseReact(this IApplicationBuilder app, IEnumerable<string> scriptUrls, Version reactVersion,
bool disableServerSideRendering = false, string? nameOfObjectToSaveProps = null, bool? useCache = null, bool? strictMode = null)
bool disableServerSideRendering = false, string? nameOfObjectToSaveProps = null,
string? nameOfGlobalDataToSave = null, bool? useCache = null, bool? strictMode = null)
{
var config = app.ApplicationServices.GetService<ReactConfiguration>();

Expand All @@ -61,6 +62,7 @@ public static void UseReact(this IApplicationBuilder app, IEnumerable<string> sc
config.ScriptUrls = scriptUrls.ToList();
config.ReactVersion = reactVersion;
config.NameOfObjectToSaveProps = nameOfObjectToSaveProps ?? config.NameOfObjectToSaveProps;
config.NameOfGlobalDataToSave = nameOfGlobalDataToSave ?? config.NameOfGlobalDataToSave;
config.UseCache = useCache ?? true;
config.StrictMode = strictMode ?? false;
}
Expand Down
52 changes: 35 additions & 17 deletions Forte.Web.React/HtmlHelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,34 @@ namespace Forte.Web.React;
public static class HtmlHelperExtensions
{
#if NET48
public static IHtmlString React<T>(this HtmlHelper _, string componentName, T props)
public static IHtmlString React<T>(this HtmlHelper _, string componentName, T props, object? globalData = null)
{
var reactService = DependencyResolver.Current.GetService<IReactService>();
var renderedComponent = reactService.RenderToStringAsync(componentName, props).GetAwaiter().GetResult();
var renderedComponent = reactService.RenderToStringAsync(componentName, props, globalData: globalData)
.GetAwaiter().GetResult();

return new HtmlString(renderedComponent);
}

public static IHtmlString React<TComponent>(this HtmlHelper _, TComponent component) where TComponent : IReactComponent

public static IHtmlString React<TComponent>(this HtmlHelper _, TComponent component, object? globalData = null)
where TComponent : IReactComponent
{
var reactService = DependencyResolver.Current.GetService<IReactService>();
var renderedComponent = reactService.RenderToStringAsync(component.Path, null, component.RenderingMode).GetAwaiter().GetResult();
var renderedComponent = reactService
.RenderToStringAsync(component.Path, null, component.RenderingMode, globalData)
.GetAwaiter().GetResult();

return new HtmlString(renderedComponent);
}

public static IHtmlString React<TComponent, TProps>(this HtmlHelper _, TComponent component) where TComponent : IReactComponent<TProps> where TProps : IReactComponentProps

public static IHtmlString React<TComponent, TProps>(this HtmlHelper _, TComponent component,
object? globalData = null)
where TComponent : IReactComponent<TProps> where TProps : IReactComponentProps
{
var reactService = DependencyResolver.Current.GetService<IReactService>();
var renderedComponent = reactService.RenderToStringAsync(component.Path, component.Props, component.RenderingMode).GetAwaiter().GetResult();
var renderedComponent = reactService
.RenderToStringAsync(component.Path, component.Props, component.RenderingMode, globalData).GetAwaiter()
.GetResult();

return new HtmlString(renderedComponent);
}
Expand All @@ -47,28 +55,38 @@ public static IHtmlString InitJavascript(this HtmlHelper _)

return new HtmlString(reactService.GetInitJavascript());
}
}
#endif

#if NET6_0_OR_GREATER
public static async Task<IHtmlContent> ReactAsync<T>(this IHtmlHelper htmlHelper, string componentName, T props)
public static async Task<IHtmlContent> ReactAsync<T>(this IHtmlHelper htmlHelper, string componentName, T props,
object? globalData = null)
{
var reactService = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService<IReactService>();

return new HtmlString(await reactService.RenderToStringAsync(componentName, props));
return new HtmlString(
await reactService.RenderToStringAsync(componentName, props, globalData: globalData));
}

public static async Task<IHtmlContent> ReactAsync<TComponent>(this IHtmlHelper htmlHelper, TComponent component) where TComponent : IReactComponent

public static async Task<IHtmlContent> ReactAsync<TComponent>(this IHtmlHelper htmlHelper, TComponent component,
object? globalData = null)
where TComponent : IReactComponent
{
var reactService = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService<IReactService>();

return new HtmlString(await reactService.RenderToStringAsync(component.Path, null, component.RenderingMode));
return new HtmlString(
await reactService.RenderToStringAsync(component.Path, null, component.RenderingMode, globalData));
}

public static async Task<IHtmlContent> ReactAsync<TComponent, TProps>(this IHtmlHelper htmlHelper, TComponent component) where TComponent : IReactComponent<TProps> where TProps : IReactComponentProps

public static async Task<IHtmlContent> ReactAsync<TComponent, TProps>(this IHtmlHelper htmlHelper,
TComponent component, object? globalData =
null) where TComponent : IReactComponent<TProps> where TProps : IReactComponentProps
{
var reactService = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService<IReactService>();

return new HtmlString(await reactService.RenderToStringAsync(component.Path, component.Props, component.RenderingMode));
return new HtmlString(
await reactService.RenderToStringAsync(component.Path, component.Props, component.RenderingMode,
globalData));
}

public static IHtmlContent InitJavascript(this IHtmlHelper htmlHelper)
Expand All @@ -77,5 +95,5 @@ public static IHtmlContent InitJavascript(this IHtmlHelper htmlHelper)

return new HtmlString(reactService.GetInitJavascript());
}
#endif
}
#endif
36 changes: 23 additions & 13 deletions Forte.Web.React/React/ReactService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public interface IReactService
Task<IReadOnlyCollection<string>> GetAvailableComponentNames();

Task RenderAsync(TextWriter writer, string componentName, object? props = null, RenderOptions? options = null);
Task<string> RenderToStringAsync(string componentName, object? props = null, RenderingMode renderingMode = RenderingMode.ClientAndServer);

Task<string> RenderToStringAsync(string componentName, object? props = null,
RenderingMode renderingMode = RenderingMode.ClientAndServer, object? globalData = null);
}

public class ReactService : IReactService
Expand Down Expand Up @@ -65,7 +67,7 @@ public ReactService(INodeJSService nodeJsService, IJsonSerializationService json
}
#endif

private async Task<T> InvokeRenderTo<T>(Component component, object? props = null, params object[] args)
private async Task<T> InvokeRenderTo<T>(Component component, object? props = null, object? globalData = null, params object[] args)
{
var allArgs = new List<object>()
{
Expand All @@ -74,6 +76,8 @@ private async Task<T> InvokeRenderTo<T>(Component component, object? props = nul
props,

Check warning on line 76 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.

Check warning on line 76 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.

Check warning on line 76 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.

Check warning on line 76 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.
_config.ScriptUrls,
_config.NameOfObjectToSaveProps,
_config.NameOfGlobalDataToSave,
globalData

Check warning on line 80 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.

Check warning on line 80 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.

Check warning on line 80 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.

Check warning on line 80 in Forte.Web.React/React/ReactService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'item' in 'void List<object>.Add(object item)'.
};
allArgs.AddRange(args);

Expand Down Expand Up @@ -103,7 +107,8 @@ private async Task<T> InvokeRenderTo<T>(Component component, object? props = nul
}


public async Task<string> RenderToStringAsync(string componentName, object? props = null, RenderingMode renderingMode = RenderingMode.ClientAndServer)
public async Task<string> RenderToStringAsync(string componentName, object? props = null,
RenderingMode renderingMode = RenderingMode.ClientAndServer, object? globalData = null)
{
var component = new Component(componentName, props, renderingMode);
Components.Add(component);
Expand All @@ -113,12 +118,13 @@ public async Task<string> RenderToStringAsync(string componentName, object? prop
return WrapRenderedStringComponent(string.Empty, component);
}

var result = await InvokeRenderTo<string>(component, props).ConfigureAwait(false);
var result = await InvokeRenderTo<string>(component, props, globalData).ConfigureAwait(false);

return WrapRenderedStringComponent(result, component);
}

public async Task RenderAsync(TextWriter writer, string componentName, object? props = null, RenderOptions? options = null)
public async Task RenderAsync(TextWriter writer, string componentName, object? props = null,
RenderOptions? options = null)
{
options ??= new RenderOptions();
var component = new Component(componentName, props, options.RenderingMode);
Expand All @@ -139,7 +145,8 @@ public async Task RenderAsync(TextWriter writer, string componentName, object? p
IdentifierPrefix = _config.UseIdentifierPrefix ? component.ContainerId : null,
};

var result = await InvokeRenderTo<HttpResponseMessage>(component, props, streamingOptions).ConfigureAwait(false);
var result = await InvokeRenderTo<HttpResponseMessage>(component, props, streamingOptions)
.ConfigureAwait(false);

using var reader = new StreamReader(await result.Content.ReadAsStreamAsync().ConfigureAwait(false));

Expand All @@ -161,7 +168,7 @@ public async Task<IReadOnlyCollection<string>> GetAvailableComponentNames()
if (_config.UseCache)
{
var (success, cachedResult) = await _nodeJsService
.TryInvokeFromCacheAsync<string[]>(getAvailableComponentNames, args: new [] { _config.ScriptUrls })
.TryInvokeFromCacheAsync<string[]>(getAvailableComponentNames, args: new[] { _config.ScriptUrls })
.ConfigureAwait(false);

if (success)
Expand All @@ -173,7 +180,7 @@ public async Task<IReadOnlyCollection<string>> GetAvailableComponentNames()
using var stream = GetStreamFromEmbeddedScript(getAvailableComponentNames);

var result = await _nodeJsService.InvokeFromStreamAsync<string[]>(stream,
getAvailableComponentNames, args: new [] { _config.ScriptUrls })
getAvailableComponentNames, args: new[] { _config.ScriptUrls })
.ConfigureAwait(false);

return result!;
Expand All @@ -185,7 +192,8 @@ private static Stream GetStreamFromEmbeddedScript(string scriptName)

var manifestResourceName = $"Forte.Web.React.Scripts.{scriptName}";
var stream = currentAssembly.GetManifestResourceStream(manifestResourceName) ??
throw new InvalidOperationException($"Could not get manifest resource with name - {manifestResourceName}");
throw new InvalidOperationException(
$"Could not get manifest resource with name - {manifestResourceName}");

return stream;
}
Expand Down Expand Up @@ -219,7 +227,8 @@ private string GetInitJavascriptSource(Component c)

private string CreateElement(Component component)
{
var element = $"React.createElement(window.__react.{component.Path}, window.{_config.NameOfObjectToSaveProps}[\"{component.JsonContainerId}\"])";
var element =
$"React.createElement(window.__react.{component.Path}, window.{_config.NameOfObjectToSaveProps}[\"{component.JsonContainerId}\"])";

return _config.StrictMode ? $"React.createElement(React.StrictMode, null, {element})" : element;
}
Expand Down Expand Up @@ -259,11 +268,12 @@ public class RenderOptions
public RenderOptions() : this(RenderingMode.ClientAndServer, true)
{
}

public RenderOptions(bool serverOnly, bool enableStreaming = true) : this(serverOnly ? RenderingMode.Server : RenderingMode.ClientAndServer, enableStreaming)

public RenderOptions(bool serverOnly, bool enableStreaming = true) : this(
serverOnly ? RenderingMode.Server : RenderingMode.ClientAndServer, enableStreaming)
{
}

public RenderOptions(RenderingMode renderingMode, bool enableStreaming = true)
{
RenderingMode = renderingMode;
Expand Down
5 changes: 4 additions & 1 deletion Forte.Web.React/Scripts/renderToString.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
jsonContainerId,
props = {},
scriptFiles,
nameOfObjectToSaveProps
nameOfObjectToSaveProps,
nameOfGlobalDataToSave,
globalData = {}
) => {
scriptFiles.forEach((scriptFile) => {
require(scriptFile);
Expand All @@ -13,6 +15,7 @@
const ReactDOMServer = global["ReactDOMServer"];
const React = global["React"];
const componentRepository = global["__react"] || {};
global[nameOfGlobalDataToSave] = globalData;

const path = componentName.split(".");
let component = componentRepository[path[0]];
Expand Down

0 comments on commit c96cd6c

Please sign in to comment.