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
64 changes: 64 additions & 0 deletions src/React.AspNet/ActionHtmlString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2014-Present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

using System;
using System.IO;

#if LEGACYASPNET
using System.Web;
#else
using System.Text.Encodings.Web;
using IHtmlString = Microsoft.AspNetCore.Html.IHtmlContent;
#endif

#if LEGACYASPNET
namespace React.Web.Mvc
#else
namespace React.AspNet
#endif
{
/// <summary>
/// IHtmlString or IHtmlString action wrapper implementation
/// </summary>
public class ActionHtmlString : IHtmlString
{
private readonly Action<TextWriter> _textWriter;

/// <summary>
/// Constructor IHtmlString or IHtmlString action wrapper implementation
/// </summary>
/// <param name="textWriter"></param>
public ActionHtmlString(Action<TextWriter> textWriter)
{
_textWriter = textWriter;
}

#if LEGACYASPNET
/// <summary>Returns an HTML-encoded string.</summary>
/// <returns>An HTML-encoded string.</returns>
public string ToHtmlString()
{
var sw = new StringWriter();
_textWriter(sw);
return sw.ToString();
}
#else
/// <summary>
/// Writes the content by encoding it with the specified <paramref name="encoder" />
/// to the specified <paramref name="writer" />.
/// </summary>
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written.</param>
/// <param name="encoder">The <see cref="T:System.Text.Encodings.Web.HtmlEncoder" /> which encodes the content to be written.</param>
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
_textWriter(writer);
}
#endif
}
}
124 changes: 54 additions & 70 deletions src/React.AspNet/HtmlHelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@

#if LEGACYASPNET
using System.Web;
using System.Web.Mvc;
using IHtmlHelper = System.Web.Mvc.HtmlHelper;
#else
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.Rendering;
using IHtmlString = Microsoft.AspNetCore.Html.IHtmlContent;
using Microsoft.AspNetCore.Html;
#endif

#if LEGACYASPNET
Expand All @@ -32,7 +29,6 @@ namespace React.AspNet
/// </summary>
public static class HtmlHelperExtensions
{

/// <summary>
/// Gets the React environment
/// </summary>
Expand Down Expand Up @@ -70,24 +66,28 @@ public static IHtmlString React<T>(
Action<Exception, string, string> exceptionHandler = null
)
{
try
return new ActionHtmlString(writer =>
{
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly, serverOnly);
if (!string.IsNullOrEmpty(htmlTag))
try
{
reactComponent.ContainerTag = htmlTag;
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly, serverOnly);
if (!string.IsNullOrEmpty(htmlTag))
{
reactComponent.ContainerTag = htmlTag;
}

if (!string.IsNullOrEmpty(containerClass))
{
reactComponent.ContainerClass = containerClass;
}

writer.Write(reactComponent.RenderHtml(clientOnly, serverOnly, exceptionHandler));
}
if (!string.IsNullOrEmpty(containerClass))
finally
{
reactComponent.ContainerClass = containerClass;
Environment.ReturnEngineToPool();
}
var result = reactComponent.RenderHtml(clientOnly, serverOnly, exceptionHandler);
return new HtmlString(result);
}
finally
{
Environment.ReturnEngineToPool();
}
});
}

/// <summary>
Expand Down Expand Up @@ -116,25 +116,30 @@ public static IHtmlString ReactWithInit<T>(
Action<Exception, string, string> exceptionHandler = null
)
{
try
return new ActionHtmlString(writer =>
{
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly);
if (!string.IsNullOrEmpty(htmlTag))
try
{
reactComponent.ContainerTag = htmlTag;
var reactComponent = Environment.CreateComponent(componentName, props, containerId, clientOnly);
if (!string.IsNullOrEmpty(htmlTag))
{
reactComponent.ContainerTag = htmlTag;
}

if (!string.IsNullOrEmpty(containerClass))
{
reactComponent.ContainerClass = containerClass;
}

writer.Write(reactComponent.RenderHtml(clientOnly, exceptionHandler: exceptionHandler));
writer.WriteLine();
WriteScriptTag(writer, bodyWriter => bodyWriter.Write(reactComponent.RenderJavaScript()));
}
if (!string.IsNullOrEmpty(containerClass))
finally
{
reactComponent.ContainerClass = containerClass;
Environment.ReturnEngineToPool();
}
var html = reactComponent.RenderHtml(clientOnly, exceptionHandler: exceptionHandler);

return new HtmlString(html + System.Environment.NewLine + RenderToString(GetScriptTag(reactComponent.RenderJavaScript())));
}
finally
{
Environment.ReturnEngineToPool();
}
});
}

/// <summary>
Expand All @@ -144,55 +149,34 @@ public static IHtmlString ReactWithInit<T>(
/// <returns>JavaScript for all components</returns>
public static IHtmlString ReactInitJavaScript(this IHtmlHelper htmlHelper, bool clientOnly = false)
{
try
{
return GetScriptTag(Environment.GetInitJavaScript(clientOnly));
}
finally
return new ActionHtmlString(writer =>
{
Environment.ReturnEngineToPool();
}
try
{
WriteScriptTag(writer, bodyWriter => bodyWriter.Write(Environment.GetInitJavaScript(clientOnly)));
}
finally
{
Environment.ReturnEngineToPool();
}
});
}

private static IHtmlString GetScriptTag(string script)
private static void WriteScriptTag(TextWriter writer, Action<TextWriter> bodyWriter)
{
#if LEGACYASPNET
var tag = new TagBuilder("script")
{
InnerHtml = script,
};

writer.Write("<script");
if (Environment.Configuration.ScriptNonceProvider != null)
{
tag.Attributes.Add("nonce", Environment.Configuration.ScriptNonceProvider());
writer.Write(" nonce=\"");
writer.Write(Environment.Configuration.ScriptNonceProvider());
writer.Write("\"");
}

return new HtmlString(tag.ToString());
#else
var tag = new TagBuilder("script");
tag.InnerHtml.AppendHtml(script);
writer.Write(">");

if (Environment.Configuration.ScriptNonceProvider != null)
{
tag.Attributes.Add("nonce", Environment.Configuration.ScriptNonceProvider());
}
bodyWriter(writer);

return tag;
#endif
}

// In ASP.NET Core, you can no longer call `.ToString` on `IHtmlString`
private static string RenderToString(IHtmlString source)
{
#if LEGACYASPNET
return source.ToString();
#else
using (var writer = new StringWriter())
{
source.WriteTo(writer, HtmlEncoder.Default);
return writer.ToString();
}
#endif
writer.Write("</script>");
}
}
}
2 changes: 1 addition & 1 deletion src/React.Web.Mvc4/React.Web.Mvc4.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\SharedAssemblyInfo.cs;..\React.AspNet\HtmlHelperExtensions.cs" />
<Compile Include="..\SharedAssemblyInfo.cs;..\React.AspNet\HtmlHelperExtensions.cs;..\React.AspNet\ActionHtmlString.cs" />
<Compile Include="..\SharedAssemblyVersionInfo.cs" />
<Content Include="Content\**\*">
<Pack>true</Pack>
Expand Down
12 changes: 6 additions & 6 deletions tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void ReactWithInitShouldReturnHtmlAndScript()
componentName: "ComponentName",
props: new { },
htmlTag: "span"
);
).ToHtmlString();
Assert.Equal(
"HTML" + System.Environment.NewLine + "<script>JS</script>",
result.ToString()
Expand Down Expand Up @@ -91,7 +91,7 @@ public void ScriptNonceIsReturned()
componentName: "ComponentName",
props: new { },
htmlTag: "span"
);
).ToHtmlString();
Assert.Equal(
"HTML" + System.Environment.NewLine + "<script>JS</script>",
result.ToString()
Expand All @@ -105,7 +105,7 @@ public void ScriptNonceIsReturned()
componentName: "ComponentName",
props: new { },
htmlTag: "span"
);
).ToHtmlString();
Assert.Equal(
"HTML" + System.Environment.NewLine + "<script nonce=\"" + nonce + "\">JS</script>",
result.ToString()
Expand Down Expand Up @@ -134,7 +134,7 @@ public void EngineIsReturnedToPoolAfterRender()
htmlTag: "span",
clientOnly: true,
serverOnly: false
);
).ToHtmlString();
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == false), null), Times.Once);
environment.Verify(x => x.ReturnEngineToPool(), Times.Once);
}
Expand All @@ -160,7 +160,7 @@ public void ReactWithClientOnlyTrueShouldCallRenderHtmlWithTrue()
htmlTag: "span",
clientOnly: true,
serverOnly: false
);
).ToHtmlString();
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == false), null), Times.Once);
}

Expand All @@ -185,7 +185,7 @@ public void ReactWithServerOnlyTrueShouldCallRenderHtmlWithTrue()
htmlTag: "span",
clientOnly: false,
serverOnly: true
);
).ToHtmlString();
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == false), It.Is<bool>(z => z == true), null), Times.Once);
}
}
Expand Down