Skip to content

Commit 5d1bd9d

Browse files
gunnimDaniel15
authored andcommitted
React Router Support (#407)
* React.Router initial draft * spaces -> tabs * tests WIP * Unit tests and final cleanup * Fix htmlhelper test and comment out environmentextensionsTest * Fix content include and only include aspnetcore files in netstandard build * new createComponent function added to environment interface * Various changes after feedback from Daniel. * Finished fixing all unit tests
1 parent 2752d9d commit 5d1bd9d

24 files changed

+973
-19
lines changed
File renamed without changes.

build.proj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ of patent rights can be found in the PATENTS file in the same directory.
2727
<PackageAssemblies Include="React.Core" />
2828
<PackageAssemblies Include="React.MSBuild" />
2929
<PackageAssemblies Include="React.Owin" />
30+
<PackageAssemblies Include="React.Router" />
3031
<PackageAssemblies Include="React.Web" />
3132
<PackageAssemblies Include="React.Web.Mvc4" />
3233
<PackageAssemblies Include="System.Web.Optimization.React" />

src/React.AspNet/HtmlHelperExtensions.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,7 @@ private static IReactEnvironment Environment
3939
{
4040
get
4141
{
42-
try
43-
{
44-
return ReactEnvironment.Current;
45-
}
46-
catch (TinyIoCResolutionException ex)
47-
{
48-
throw new ReactNotInitialisedException(
49-
#if LEGACYASPNET
50-
"ReactJS.NET has not been initialised correctly.",
51-
#else
52-
"ReactJS.NET has not been initialised correctly. Please ensure you have " +
53-
"called services.AddReact() and app.UseReact() in your Startup.cs file.",
54-
#endif
55-
ex
56-
);
57-
}
42+
return ReactEnvironment.GetCurrentOrThrow;
5843
}
5944
}
6045

src/React.Core/IReactEnvironment.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ public interface IReactEnvironment
8484
/// <returns>The component</returns>
8585
IReactComponent CreateComponent<T>(string componentName, T props, string containerId = null, bool clientOnly = false);
8686

87+
/// <summary>
88+
/// Adds the provided <see cref="IReactComponent"/> to the list of components to render client side.
89+
/// </summary>
90+
/// <param name="component">Component to add to client side render list</param>
91+
/// <param name="clientOnly">True if server-side rendering will be bypassed. Defaults to false.</param>
92+
/// <returns>The component</returns>
93+
IReactComponent CreateComponent(IReactComponent component, bool clientOnly = false);
94+
8795
/// <summary>
8896
/// Renders the JavaScript required to initialise all components client-side. This will
8997
/// attach event handlers to the server-rendered HTML.

src/React.Core/ReactComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public virtual string RenderHtml(bool renderContainerOnly = false, bool renderSe
142142
attributes,
143143
html,
144144
ContainerTag
145-
);
145+
);
146146
}
147147
catch (JsRuntimeException ex)
148148
{

src/React.Core/ReactEnvironment.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using JSPool;
1919
using Newtonsoft.Json;
2020
using React.Exceptions;
21+
using React.TinyIoC;
2122

2223
namespace React
2324
{
@@ -85,6 +86,34 @@ public static IReactEnvironment Current
8586
get { return AssemblyRegistration.Container.Resolve<IReactEnvironment>(); }
8687
}
8788

89+
/// <summary>
90+
/// Gets the <see cref="IReactEnvironment"/> for the current request. If no environment
91+
/// has been created for the current request, creates a new one.
92+
/// Also provides more specific error information in the event that ReactJS.NET is misconfigured.
93+
/// </summary>
94+
public static IReactEnvironment GetCurrentOrThrow
95+
{
96+
get
97+
{
98+
try
99+
{
100+
return Current;
101+
}
102+
catch (TinyIoCResolutionException ex)
103+
{
104+
throw new ReactNotInitialisedException(
105+
#if NET451
106+
"ReactJS.NET has not been initialised correctly.",
107+
#else
108+
"ReactJS.NET has not been initialised correctly. Please ensure you have " +
109+
"called services.AddReact() and app.UseReact() in your Startup.cs file.",
110+
#endif
111+
ex
112+
);
113+
}
114+
}
115+
}
116+
88117
/// <summary>
89118
/// Initializes a new instance of the <see cref="ReactEnvironment"/> class.
90119
/// </summary>
@@ -274,6 +303,23 @@ public virtual IReactComponent CreateComponent<T>(string componentName, T props,
274303
return component;
275304
}
276305

306+
/// <summary>
307+
/// Adds the provided <see cref="IReactComponent"/> to the list of components to render client side.
308+
/// </summary>
309+
/// <param name="component">Component to add to client side render list</param>
310+
/// <param name="clientOnly">True if server-side rendering will be bypassed. Defaults to false.</param>
311+
/// <returns>The component</returns>
312+
public virtual IReactComponent CreateComponent(IReactComponent component, bool clientOnly = false)
313+
{
314+
if (!clientOnly)
315+
{
316+
EnsureUserScriptsLoaded();
317+
}
318+
319+
_components.Add(component);
320+
return component;
321+
}
322+
277323
/// <summary>
278324
/// Renders the JavaScript required to initialise all components client-side. This will
279325
/// attach event handlers to the server-rendered HTML.

src/React.Core/Resources/shims.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,31 @@ function ReactNET_initReact() {
6767
// :'(
6868
return false;
6969
}
70+
71+
/**
72+
* Polyfill for engines that do not support Object.assign
73+
*/
74+
if (typeof Object.assign !== 'function') {
75+
Object.assign = function (target, varArgs) { // .length of function is 2
76+
'use strict';
77+
if (target == null) { // TypeError if undefined or null
78+
throw new TypeError('Cannot convert undefined or null to object');
79+
}
80+
81+
var to = Object(target);
82+
83+
for (var index = 1; index < arguments.length; index++) {
84+
var nextSource = arguments[index];
85+
86+
if (nextSource != null) { // Skip over if undefined or null
87+
for (var nextKey in nextSource) {
88+
// Avoid bugs when hasOwnProperty is shadowed
89+
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
90+
to[nextKey] = nextSource[nextKey];
91+
}
92+
}
93+
}
94+
}
95+
return to;
96+
};
97+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<configuration>
2+
<system.web.webPages.razor>
3+
<pages>
4+
<namespaces>
5+
<add namespace="React.Router" />
6+
</namespaces>
7+
</pages>
8+
</system.web.webPages.razor>
9+
</configuration>

src/React.Router/ExecutionResult.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
namespace React.Router
11+
{
12+
/// <summary>
13+
/// Contains the context object used during execution in addition to
14+
/// the string result of rendering the React Router component.
15+
/// </summary>
16+
public class ExecutionResult
17+
{
18+
/// <summary>
19+
/// String result of ReactDOMServer render of provided component.
20+
/// </summary>
21+
public string RenderResult { get; set; }
22+
23+
/// <summary>
24+
/// Context object used during JS engine execution.
25+
/// </summary>
26+
public RoutingContext Context { get; set; }
27+
}
28+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 React.Exceptions;
12+
using React.TinyIoC;
13+
14+
#if NET451
15+
using System.Web;
16+
using System.Web.Mvc;
17+
using HttpResponse = System.Web.HttpResponseBase;
18+
using IHtmlHelper = System.Web.Mvc.HtmlHelper;
19+
#else
20+
using Microsoft.AspNetCore.Mvc.Rendering;
21+
using IHtmlString = Microsoft.AspNetCore.Html.IHtmlContent;
22+
using HttpResponse = Microsoft.AspNetCore.Http.HttpResponse;
23+
using Microsoft.AspNetCore.Html;
24+
#endif
25+
26+
namespace React.Router
27+
{
28+
/// <summary>
29+
/// Render a React StaticRouter Component with context.
30+
/// </summary>
31+
public static class HtmlHelperExtensions
32+
{
33+
/// <summary>
34+
/// Gets the React environment
35+
/// </summary>
36+
private static IReactEnvironment Environment
37+
{
38+
get
39+
{
40+
return ReactEnvironment.GetCurrentOrThrow;
41+
}
42+
}
43+
44+
/// <summary>
45+
/// Render a React StaticRouter Component with context object.
46+
/// Can optionally be provided with a custom context handler to handle the various status codes.
47+
/// </summary>
48+
/// <param name="htmlHelper">MVC Razor <see cref="IHtmlHelper"/></param>
49+
/// <param name="componentName">Name of React Static Router component. Expose component globally to ReactJS.NET</param>
50+
/// <param name="props">Props to initialise the component with</param>
51+
/// <param name="path">F.x. from Request.Path. Used by React Static Router to determine context and routing.</param>
52+
/// <param name="contextHandler">Optional custom context handler, can be used instead of providing a Response object</param>
53+
/// <param name="htmlTag">HTML tag to wrap the component in. Defaults to &lt;div&gt;</param>
54+
/// <param name="containerId">ID to use for the container HTML tag. Defaults to an auto-generated ID</param>
55+
/// <param name="clientOnly">Skip rendering server-side and only output client-side initialisation code. Defaults to <c>false</c></param>
56+
/// <param name="serverOnly">Skip rendering React specific data-attributes during server side rendering. Defaults to <c>false</c></param>
57+
/// <param name="containerClass">HTML class(es) to set on the container tag</param>
58+
/// <returns><see cref="IHtmlString"/> containing the rendered markup for provided React Router component</returns>
59+
public static IHtmlString ReactRouterWithContext<T>(
60+
this IHtmlHelper htmlHelper,
61+
string componentName,
62+
T props,
63+
string path = null,
64+
string htmlTag = null,
65+
string containerId = null,
66+
bool clientOnly = false,
67+
bool serverOnly = false,
68+
string containerClass = null,
69+
Action<HttpResponse, RoutingContext> contextHandler = null
70+
)
71+
{
72+
try
73+
{
74+
var response = htmlHelper.ViewContext.HttpContext.Response;
75+
path = path ?? htmlHelper.ViewContext.HttpContext.Request.Path;
76+
77+
var reactComponent
78+
= Environment.CreateRouterComponent(
79+
componentName,
80+
props,
81+
path,
82+
containerId,
83+
clientOnly
84+
);
85+
86+
if (!string.IsNullOrEmpty(htmlTag))
87+
{
88+
reactComponent.ContainerTag = htmlTag;
89+
}
90+
if (!string.IsNullOrEmpty(containerClass))
91+
{
92+
reactComponent.ContainerClass = containerClass;
93+
}
94+
95+
var executionResult = reactComponent.RenderRouterWithContext(clientOnly, serverOnly);
96+
97+
if (executionResult.Context?.status != null)
98+
{
99+
// Use provided contextHandler
100+
if (contextHandler != null)
101+
{
102+
contextHandler(response, executionResult.Context);
103+
}
104+
// Handle routing context internally
105+
else
106+
{
107+
SetServerResponse.ModifyResponse(executionResult.Context, response);
108+
}
109+
}
110+
111+
return new HtmlString(executionResult.RenderResult);
112+
}
113+
finally
114+
{
115+
Environment.ReturnEngineToPool();
116+
}
117+
}
118+
}
119+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using System.Reflection;
2+
using System.Runtime.InteropServices;
3+
4+
[assembly: AssemblyTitle("React.Router")]
5+
[assembly: AssemblyDescription("React Router support for ReactJS.NET")]
6+
[assembly: ComVisible(false)]
7+
[assembly: Guid("277850fc-8765-4042-945f-a50b8f2525a9")]

src/React.Router/React.Router.csproj

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>React Router support for ReactJS.NET.</Description>
5+
<Copyright>Copyright 2014-Present Facebook, Inc</Copyright>
6+
<AssemblyTitle>ReactJS.NET Router</AssemblyTitle>
7+
<Authors>Daniel Lo Nigro, Gunnar Már Óttarsson</Authors>
8+
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
9+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
10+
<AssemblyName>React.Router</AssemblyName>
11+
<AssemblyOriginatorKeyFile>../key.snk</AssemblyOriginatorKeyFile>
12+
<SignAssembly>true</SignAssembly>
13+
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
14+
<PackageId>React.Router</PackageId>
15+
<PackageTags>asp.net;mvc;asp;javascript;js;react;facebook;reactjs;babel;router;react router</PackageTags>
16+
<PackageIconUrl>http://reactjs.net/img/logo_64.png</PackageIconUrl>
17+
<PackageProjectUrl>http://reactjs.net/</PackageProjectUrl>
18+
<PackageLicenseUrl>https://github.com/reactjs/React.NET#licence</PackageLicenseUrl>
19+
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
20+
</PropertyGroup>
21+
22+
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net451|AnyCPU'">
23+
<DefineConstants>TRACE;DEBUG;ASPNETCORE;NET451</DefineConstants>
24+
</PropertyGroup>
25+
26+
<ItemGroup>
27+
<Compile Include="..\SharedAssemblyInfo.cs" />
28+
<Compile Include="..\SharedAssemblyVersionInfo.cs" />
29+
<Content Include="Content\**\*">
30+
<Pack>true</Pack>
31+
<PackagePath>content\</PackagePath>
32+
</Content>
33+
</ItemGroup>
34+
35+
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
36+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="1.0.3" />
37+
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="1.0.3" />
38+
</ItemGroup>
39+
40+
<ItemGroup Condition=" '$(TargetFramework)' == 'net451' ">
41+
<Reference Include="System.Web" />
42+
<Reference Include="Microsoft.CSharp" />
43+
<PackageReference Include="Microsoft.AspNet.Mvc" Version="4.0.20710" />
44+
</ItemGroup>
45+
46+
<ItemGroup>
47+
<ProjectReference Include="..\React.Core\React.Core.csproj" />
48+
</ItemGroup>
49+
50+
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
51+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
52+
</PropertyGroup>
53+
54+
</Project>

0 commit comments

Comments
 (0)