Skip to content

Commit cb086eb

Browse files
DaniilSokolyukdustinsoftware
authored andcommitted
Native IHtmlContent/IHtmlString (#527)
1 parent d49bb11 commit cb086eb

File tree

4 files changed

+125
-77
lines changed

4 files changed

+125
-77
lines changed

Diff for: src/React.AspNet/ActionHtmlString.cs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.Web;
15+
#else
16+
using System.Text.Encodings.Web;
17+
using IHtmlString = Microsoft.AspNetCore.Html.IHtmlContent;
18+
#endif
19+
20+
#if LEGACYASPNET
21+
namespace React.Web.Mvc
22+
#else
23+
namespace React.AspNet
24+
#endif
25+
{
26+
/// <summary>
27+
/// IHtmlString or IHtmlString action wrapper implementation
28+
/// </summary>
29+
public class ActionHtmlString : IHtmlString
30+
{
31+
private readonly Action<TextWriter> _textWriter;
32+
33+
/// <summary>
34+
/// Constructor IHtmlString or IHtmlString action wrapper implementation
35+
/// </summary>
36+
/// <param name="textWriter"></param>
37+
public ActionHtmlString(Action<TextWriter> textWriter)
38+
{
39+
_textWriter = textWriter;
40+
}
41+
42+
#if LEGACYASPNET
43+
/// <summary>Returns an HTML-encoded string.</summary>
44+
/// <returns>An HTML-encoded string.</returns>
45+
public string ToHtmlString()
46+
{
47+
var sw = new StringWriter();
48+
_textWriter(sw);
49+
return sw.ToString();
50+
}
51+
#else
52+
/// <summary>
53+
/// Writes the content by encoding it with the specified <paramref name="encoder" />
54+
/// to the specified <paramref name="writer" />.
55+
/// </summary>
56+
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written.</param>
57+
/// <param name="encoder">The <see cref="T:System.Text.Encodings.Web.HtmlEncoder" /> which encodes the content to be written.</param>
58+
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
59+
{
60+
_textWriter(writer);
61+
}
62+
#endif
63+
}
64+
}

Diff for: src/React.AspNet/HtmlHelperExtensions.cs

+54-70
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+
writer.Write(reactComponent.RenderHtml(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+
writer.Write(reactComponent.RenderHtml(clientOnly, exceptionHandler: exceptionHandler));
135+
writer.WriteLine();
136+
WriteScriptTag(writer, bodyWriter => bodyWriter.Write(reactComponent.RenderJavaScript()));
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 => bodyWriter.Write(Environment.GetInitJavaScript(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
}

Diff for: src/React.Web.Mvc4/React.Web.Mvc4.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</PropertyGroup>
2222

2323
<ItemGroup>
24-
<Compile Include="..\SharedAssemblyInfo.cs;..\React.AspNet\HtmlHelperExtensions.cs" />
24+
<Compile Include="..\SharedAssemblyInfo.cs;..\React.AspNet\HtmlHelperExtensions.cs;..\React.AspNet\ActionHtmlString.cs" />
2525
<Compile Include="..\SharedAssemblyVersionInfo.cs" />
2626
<Content Include="Content\**\*">
2727
<Pack>true</Pack>

Diff for: tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void ReactWithInitShouldReturnHtmlAndScript()
5050
componentName: "ComponentName",
5151
props: new { },
5252
htmlTag: "span"
53-
);
53+
).ToHtmlString();
5454
Assert.Equal(
5555
"HTML" + System.Environment.NewLine + "<script>JS</script>",
5656
result.ToString()
@@ -91,7 +91,7 @@ public void ScriptNonceIsReturned()
9191
componentName: "ComponentName",
9292
props: new { },
9393
htmlTag: "span"
94-
);
94+
).ToHtmlString();
9595
Assert.Equal(
9696
"HTML" + System.Environment.NewLine + "<script>JS</script>",
9797
result.ToString()
@@ -105,7 +105,7 @@ public void ScriptNonceIsReturned()
105105
componentName: "ComponentName",
106106
props: new { },
107107
htmlTag: "span"
108-
);
108+
).ToHtmlString();
109109
Assert.Equal(
110110
"HTML" + System.Environment.NewLine + "<script nonce=\"" + nonce + "\">JS</script>",
111111
result.ToString()
@@ -134,7 +134,7 @@ public void EngineIsReturnedToPoolAfterRender()
134134
htmlTag: "span",
135135
clientOnly: true,
136136
serverOnly: false
137-
);
137+
).ToHtmlString();
138138
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == false), null), Times.Once);
139139
environment.Verify(x => x.ReturnEngineToPool(), Times.Once);
140140
}
@@ -160,7 +160,7 @@ public void ReactWithClientOnlyTrueShouldCallRenderHtmlWithTrue()
160160
htmlTag: "span",
161161
clientOnly: true,
162162
serverOnly: false
163-
);
163+
).ToHtmlString();
164164
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == false), null), Times.Once);
165165
}
166166

@@ -185,7 +185,7 @@ public void ReactWithServerOnlyTrueShouldCallRenderHtmlWithTrue()
185185
htmlTag: "span",
186186
clientOnly: false,
187187
serverOnly: true
188-
);
188+
).ToHtmlString();
189189
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == false), It.Is<bool>(z => z == true), null), Times.Once);
190190
}
191191
}

0 commit comments

Comments
 (0)