Skip to content

Commit bdda3e6

Browse files
Avoid memory allocations
1 parent 529df29 commit bdda3e6

20 files changed

+482
-255
lines changed

src/React.AspNet/ActionHtmlString.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2014-Present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
using System;
11+
using System.IO;
12+
13+
#if LEGACYASPNET
14+
using System.Text;
15+
using System.Web;
16+
#else
17+
using System.Text.Encodings.Web;
18+
using IHtmlString = Microsoft.AspNetCore.Html.IHtmlContent;
19+
#endif
20+
21+
#if LEGACYASPNET
22+
namespace React.Web.Mvc
23+
#else
24+
namespace React.AspNet
25+
#endif
26+
{
27+
/// <summary>
28+
/// IHtmlString or IHtmlString action wrapper implementation
29+
/// </summary>
30+
public class ActionHtmlString : IHtmlString
31+
{
32+
private readonly Action<TextWriter> _textWriter;
33+
34+
/// <summary>
35+
/// Constructor IHtmlString or IHtmlString action wrapper implementation
36+
/// </summary>
37+
/// <param name="textWriter"></param>
38+
public ActionHtmlString(Action<TextWriter> textWriter)
39+
{
40+
_textWriter = textWriter;
41+
}
42+
43+
#if LEGACYASPNET
44+
[ThreadStatic]
45+
private static StringWriter _sharedStringWriter;
46+
47+
/// <summary>Returns an HTML-encoded string.</summary>
48+
/// <returns>An HTML-encoded string.</returns>
49+
public string ToHtmlString()
50+
{
51+
var stringWriter = _sharedStringWriter;
52+
if (stringWriter != null)
53+
{
54+
stringWriter.GetStringBuilder().Clear();
55+
}
56+
else
57+
{
58+
_sharedStringWriter = stringWriter = new StringWriter(new StringBuilder(512));
59+
}
60+
61+
_textWriter(stringWriter);
62+
return stringWriter.ToString();
63+
}
64+
#else
65+
/// <summary>
66+
/// Writes the content by encoding it with the specified <paramref name="encoder" />
67+
/// to the specified <paramref name="writer" />.
68+
/// </summary>
69+
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written.</param>
70+
/// <param name="encoder">The <see cref="T:System.Text.Encodings.Web.HtmlEncoder" /> which encodes the content to be written.</param>
71+
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
72+
{
73+
_textWriter(writer);
74+
}
75+
#endif
76+
}
77+
}

src/React.AspNet/HtmlHelperExtensions.cs

Lines changed: 54 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,10 @@
1212

1313
#if LEGACYASPNET
1414
using System.Web;
15-
using System.Web.Mvc;
1615
using IHtmlHelper = System.Web.Mvc.HtmlHelper;
1716
#else
18-
using System.Text.Encodings.Web;
1917
using Microsoft.AspNetCore.Mvc.Rendering;
2018
using IHtmlString = Microsoft.AspNetCore.Html.IHtmlContent;
21-
using Microsoft.AspNetCore.Html;
2219
#endif
2320

2421
#if LEGACYASPNET
@@ -32,7 +29,6 @@ namespace React.AspNet
3229
/// </summary>
3330
public static class HtmlHelperExtensions
3431
{
35-
3632
/// <summary>
3733
/// Gets the React environment
3834
/// </summary>
@@ -70,24 +66,28 @@ public static IHtmlString React<T>(
7066
Action<Exception, string, string> exceptionHandler = null
7167
)
7268
{
73-
try
69+
return new ActionHtmlString(writer =>
7470
{
75-
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly, serverOnly);
76-
if (!string.IsNullOrEmpty(htmlTag))
71+
try
7772
{
78-
reactComponent.ContainerTag = htmlTag;
73+
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly, serverOnly);
74+
if (!string.IsNullOrEmpty(htmlTag))
75+
{
76+
reactComponent.ContainerTag = htmlTag;
77+
}
78+
79+
if (!string.IsNullOrEmpty(containerClass))
80+
{
81+
reactComponent.ContainerClass = containerClass;
82+
}
83+
84+
reactComponent.RenderHtml(writer, clientOnly, serverOnly, exceptionHandler);
7985
}
80-
if (!string.IsNullOrEmpty(containerClass))
86+
finally
8187
{
82-
reactComponent.ContainerClass = containerClass;
88+
Environment.ReturnEngineToPool();
8389
}
84-
var result = reactComponent.RenderHtml(clientOnly, serverOnly, exceptionHandler);
85-
return new HtmlString(result);
86-
}
87-
finally
88-
{
89-
Environment.ReturnEngineToPool();
90-
}
90+
});
9191
}
9292

9393
/// <summary>
@@ -116,25 +116,30 @@ public static IHtmlString ReactWithInit<T>(
116116
Action<Exception, string, string> exceptionHandler = null
117117
)
118118
{
119-
try
119+
return new ActionHtmlString(writer =>
120120
{
121-
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly);
122-
if (!string.IsNullOrEmpty(htmlTag))
121+
try
123122
{
124-
reactComponent.ContainerTag = htmlTag;
123+
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly);
124+
if (!string.IsNullOrEmpty(htmlTag))
125+
{
126+
reactComponent.ContainerTag = htmlTag;
127+
}
128+
129+
if (!string.IsNullOrEmpty(containerClass))
130+
{
131+
reactComponent.ContainerClass = containerClass;
132+
}
133+
134+
reactComponent.RenderHtml(writer, clientOnly, exceptionHandler: exceptionHandler);
135+
writer.WriteLine();
136+
WriteScriptTag(writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter));
125137
}
126-
if (!string.IsNullOrEmpty(containerClass))
138+
finally
127139
{
128-
reactComponent.ContainerClass = containerClass;
140+
Environment.ReturnEngineToPool();
129141
}
130-
var html = reactComponent.RenderHtml(clientOnly, exceptionHandler: exceptionHandler);
131-
132-
return new HtmlString(html + System.Environment.NewLine + RenderToString(GetScriptTag(reactComponent.RenderJavaScript())));
133-
}
134-
finally
135-
{
136-
Environment.ReturnEngineToPool();
137-
}
142+
});
138143
}
139144

140145
/// <summary>
@@ -144,55 +149,34 @@ public static IHtmlString ReactWithInit<T>(
144149
/// <returns>JavaScript for all components</returns>
145150
public static IHtmlString ReactInitJavaScript(this IHtmlHelper htmlHelper, bool clientOnly = false)
146151
{
147-
try
148-
{
149-
return GetScriptTag(Environment.GetInitJavaScript(clientOnly));
150-
}
151-
finally
152+
return new ActionHtmlString(writer =>
152153
{
153-
Environment.ReturnEngineToPool();
154-
}
154+
try
155+
{
156+
WriteScriptTag(writer, bodyWriter => Environment.GetInitJavaScript(bodyWriter, clientOnly));
157+
}
158+
finally
159+
{
160+
Environment.ReturnEngineToPool();
161+
}
162+
});
155163
}
156164

157-
private static IHtmlString GetScriptTag(string script)
165+
private static void WriteScriptTag(TextWriter writer, Action<TextWriter> bodyWriter)
158166
{
159-
#if LEGACYASPNET
160-
var tag = new TagBuilder("script")
161-
{
162-
InnerHtml = script,
163-
};
164-
167+
writer.Write("<script");
165168
if (Environment.Configuration.ScriptNonceProvider != null)
166169
{
167-
tag.Attributes.Add("nonce", Environment.Configuration.ScriptNonceProvider());
170+
writer.Write(" nonce=\"");
171+
writer.Write(Environment.Configuration.ScriptNonceProvider());
172+
writer.Write("\"");
168173
}
169174

170-
return new HtmlString(tag.ToString());
171-
#else
172-
var tag = new TagBuilder("script");
173-
tag.InnerHtml.AppendHtml(script);
175+
writer.Write(">");
174176

175-
if (Environment.Configuration.ScriptNonceProvider != null)
176-
{
177-
tag.Attributes.Add("nonce", Environment.Configuration.ScriptNonceProvider());
178-
}
177+
bodyWriter(writer);
179178

180-
return tag;
181-
#endif
182-
}
183-
184-
// In ASP.NET Core, you can no longer call `.ToString` on `IHtmlString`
185-
private static string RenderToString(IHtmlString source)
186-
{
187-
#if LEGACYASPNET
188-
return source.ToString();
189-
#else
190-
using (var writer = new StringWriter())
191-
{
192-
source.WriteTo(writer, HtmlEncoder.Default);
193-
return writer.ToString();
194-
}
195-
#endif
179+
writer.Write("</script>");
196180
}
197181
}
198182
}

src/React.Core/GuidExtensions.cs

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/React.Core/IReactComponent.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
using System;
11+
using System.IO;
1112

1213
namespace React
1314
{
@@ -63,5 +64,24 @@ public interface IReactComponent
6364
/// </summary>
6465
/// <returns>JavaScript</returns>
6566
string RenderJavaScript();
67+
68+
/// <summary>
69+
/// Renders the HTML for this component. This will execute the component server-side and
70+
/// return the rendered HTML.
71+
/// </summary>
72+
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written</param>
73+
/// <param name="renderContainerOnly">Only renders component container. Used for client-side only rendering.</param>
74+
/// <param name="renderServerOnly">Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.</param>
75+
/// <param name="exceptionHandler">A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)</param>
76+
/// <returns>HTML</returns>
77+
void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null);
78+
79+
/// <summary>
80+
/// Renders the JavaScript required to initialise this component client-side. This will
81+
/// initialise the React component, which includes attach event handlers to the
82+
/// server-rendered HTML.
83+
/// </summary>
84+
/// <returns>JavaScript</returns>
85+
void RenderJavaScript(TextWriter writer);
6686
}
6787
}

src/React.Core/IReactEnvironment.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*/
99

1010

11+
using System.IO;
12+
1113
namespace React
1214
{
1315
/// <summary>
@@ -100,6 +102,15 @@ public interface IReactEnvironment
100102
/// <returns>JavaScript for all components</returns>
101103
string GetInitJavaScript(bool clientOnly = false);
102104

105+
/// <summary>
106+
/// Renders the JavaScript required to initialise all components client-side. This will
107+
/// attach event handlers to the server-rendered HTML.
108+
/// </summary>
109+
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written</param>
110+
/// <param name="clientOnly">True if server-side rendering will be bypassed. Defaults to false.</param>
111+
/// <returns>JavaScript for all components</returns>
112+
void GetInitJavaScript(TextWriter writer, bool clientOnly = false);
113+
103114
/// <summary>
104115
/// Gets the JSX Transformer for this environment.
105116
/// </summary>

src/React.Core/IReactSiteConfiguration.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,5 +207,16 @@ public interface IReactSiteConfiguration
207207
/// <param name="provider"></param>
208208
/// <returns></returns>
209209
IReactSiteConfiguration SetScriptNonceProvider(Func<string> provider);
210+
211+
/// <summary>
212+
/// Get or set boolean that specifies whether the component should be checked to ensure that it exists.
213+
/// </summary>
214+
bool ComponentExistsChecks { get; set; }
215+
216+
/// <summary>
217+
/// Sets whether the component should be checked to ensure that it exists.
218+
/// </summary>
219+
/// <returns>The configuration, for chaining</returns>
220+
IReactSiteConfiguration SetComponentExistsChecks(bool componentExistsChecks);
210221
}
211222
}

src/React.Core/React.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<PackageReference Include="JavaScriptEngineSwitcher.Msie" Version="2.4.9" />
3838
<PackageReference Include="JSPool" Version="3.0.1" />
3939
<PackageReference Include="MsieJavaScriptEngine" Version="2.2.2" />
40-
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
40+
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
4141
</ItemGroup>
4242

4343
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">

0 commit comments

Comments
 (0)