Skip to content

Commit 46b45b3

Browse files
Support component-level exception handlers
1 parent ea071c8 commit 46b45b3

File tree

8 files changed

+61
-42
lines changed

8 files changed

+61
-42
lines changed

src/React.AspNet/HtmlHelperExtensions.cs

+7-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10+
using System;
1011
using React.Exceptions;
1112
using React.TinyIoC;
1213

@@ -64,7 +65,8 @@ public static IHtmlString React<T>(
6465
string containerId = null,
6566
bool clientOnly = false,
6667
bool serverOnly = false,
67-
string containerClass = null
68+
string containerClass = null,
69+
Action<Exception, string, string> exceptionHandler = null
6870
)
6971
{
7072
try
@@ -78,7 +80,7 @@ public static IHtmlString React<T>(
7880
{
7981
reactComponent.ContainerClass = containerClass;
8082
}
81-
var result = reactComponent.RenderHtml(clientOnly, serverOnly);
83+
var result = reactComponent.RenderHtml(clientOnly, serverOnly, exceptionHandler);
8284
return new HtmlString(result);
8385
}
8486
finally
@@ -108,7 +110,8 @@ public static IHtmlString ReactWithInit<T>(
108110
string htmlTag = null,
109111
string containerId = null,
110112
bool clientOnly = false,
111-
string containerClass = null
113+
string containerClass = null,
114+
Action<Exception, string, string> exceptionHandler = null
112115
)
113116
{
114117
try
@@ -122,7 +125,7 @@ public static IHtmlString ReactWithInit<T>(
122125
{
123126
reactComponent.ContainerClass = containerClass;
124127
}
125-
var html = reactComponent.RenderHtml(clientOnly);
128+
var html = reactComponent.RenderHtml(clientOnly, exceptionHandler: exceptionHandler);
126129

127130
#if LEGACYASPNET
128131
var script = new TagBuilder("script")

src/React.Core/IReactComponent.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
/*
1+
/*
22
* Copyright (c) 2014-Present, Facebook, Inc.
33
* All rights reserved.
44
*
55
* This source code is licensed under the BSD-style license found in the
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
8-
*/
8+
*/
9+
10+
using System;
911

1012
namespace React
11-
{
13+
{
1214
/// <summary>
1315
/// Represents a React JavaScript component.
1416
/// </summary>
@@ -44,9 +46,10 @@ public interface IReactComponent
4446
/// return the rendered HTML.
4547
/// </summary>
4648
/// <param name="renderContainerOnly">Only renders component container. Used for client-side only rendering.</param>
47-
/// <param name="renderServerOnly">Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.</param>
49+
/// <param name="renderServerOnly">Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.</param>
50+
/// <param name="exceptionHandler"></param>
4851
/// <returns>HTML</returns>
49-
string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false);
52+
string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null);
5053

5154
/// <summary>
5255
/// Renders the JavaScript required to initialise this component client-side. This will

src/React.Core/IReactSiteConfiguration.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,14 @@ public interface IReactSiteConfiguration
184184
/// An exception handler which will be called if a render exception is thrown.
185185
/// If unset, unhandled exceptions will be thrown for all component renders.
186186
/// </summary>
187-
Action<Exception> ExceptionHandler { get; set; }
187+
Action<Exception, string, string> ExceptionHandler { get; set; }
188188

189189
/// <summary>
190190
/// Sets an exception handler which will be called if a render exception is thrown.
191191
/// If unset, unhandled exceptions will be thrown for all component renders.
192192
/// </summary>
193193
/// <param name="handler"></param>
194194
/// <returns></returns>
195-
IReactSiteConfiguration SetExceptionHandler(Action<Exception> handler);
195+
IReactSiteConfiguration SetExceptionHandler(Action<Exception, string, string> handler);
196196
}
197197
}

src/React.Core/ReactComponent.cs

+11-13
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* This source code is licensed under the BSD-style license found in the
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
8-
*/
9-
8+
*/
9+
1010
using System;
1111
using System.Linq;
1212
using System.Text.RegularExpressions;
@@ -15,7 +15,7 @@
1515
using React.Exceptions;
1616

1717
namespace React
18-
{
18+
{
1919
/// <summary>
2020
/// Represents a React JavaScript component.
2121
/// </summary>
@@ -106,9 +106,10 @@ public ReactComponent(IReactEnvironment environment, IReactSiteConfiguration con
106106
/// return the rendered HTML.
107107
/// </summary>
108108
/// <param name="renderContainerOnly">Only renders component container. Used for client-side only rendering.</param>
109-
/// <param name="renderServerOnly">Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.</param>
109+
/// <param name="renderServerOnly">Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.</param>
110+
/// <param name="exceptionHandler"></param>
110111
/// <returns>HTML</returns>
111-
public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false)
112+
public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null)
112113
{
113114
if (!_configuration.UseServerSideRendering)
114115
{
@@ -132,15 +133,12 @@ public virtual string RenderHtml(bool renderContainerOnly = false, bool renderSe
132133
}
133134
catch (JsRuntimeException ex)
134135
{
135-
if (_configuration.ExceptionHandler == null) {
136-
throw new ReactServerRenderingException(string.Format(
137-
"Error while rendering \"{0}\" to \"{2}\": {1}",
138-
ComponentName,
139-
ex.Message,
140-
ContainerId
141-
));
136+
if (exceptionHandler == null)
137+
{
138+
exceptionHandler = _configuration.ExceptionHandler;
142139
}
143-
_configuration.ExceptionHandler(ex);
140+
141+
exceptionHandler(ex, ComponentName, ContainerId);
144142
}
145143
}
146144

src/React.Core/ReactSiteConfiguration.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Collections.Generic;
1212
using System.Linq;
1313
using Newtonsoft.Json;
14+
using React.Exceptions;
1415

1516
namespace React
1617
{
@@ -45,6 +46,13 @@ public ReactSiteConfiguration()
4546
};
4647
UseDebugReact = false;
4748
UseServerSideRendering = true;
49+
ExceptionHandler = (Exception ex, string ComponentName, string ContainerId) =>
50+
throw new ReactServerRenderingException(string.Format(
51+
"Error while rendering \"{0}\" to \"{2}\": {1}",
52+
ComponentName,
53+
ex.Message,
54+
ContainerId
55+
));
4856
}
4957

5058
/// <summary>
@@ -306,14 +314,14 @@ public IReactSiteConfiguration DisableServerSideRendering()
306314
/// Handle an exception caught during server-render of a component.
307315
/// If unset, unhandled exceptions will be thrown for all component renders.
308316
/// </summary>
309-
public Action<Exception> ExceptionHandler { get; set; }
317+
public Action<Exception, string, string> ExceptionHandler { get; set; }
310318

311319
/// <summary>
312320
///
313321
/// </summary>
314322
/// <param name="handler"></param>
315323
/// <returns></returns>
316-
public IReactSiteConfiguration SetExceptionHandler(Action<Exception> handler)
324+
public IReactSiteConfiguration SetExceptionHandler(Action<Exception, string, string> handler)
317325
{
318326
ExceptionHandler = handler;
319327
return this;

src/React.Sample.CoreMvc/Startup.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* This source code is licensed under the BSD-style license found in the
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
8-
*/
9-
8+
*/
9+
1010
using System;
1111
using Microsoft.AspNetCore.Builder;
1212
using Microsoft.AspNetCore.Hosting;
@@ -71,7 +71,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
7171
config
7272
.SetReuseJavaScriptEngines(true)
7373
.AddScript("~/js/Sample.jsx")
74-
.SetExceptionHandler(ex =>
74+
.SetExceptionHandler((ex, name, id) =>
7575
{
7676
Logger.LogError("React component exception thrown!" + ex.ToString());
7777
})

tests/React.Tests/Core/ReactComponentTest.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* This source code is licensed under the BSD-style license found in the
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
8-
*/
9-
8+
*/
9+
1010
using System;
1111
using JavaScriptEngineSwitcher.Core;
1212
using Moq;
@@ -204,6 +204,7 @@ public void ExceptionThrownIsHandled()
204204

205205
var config = new Mock<IReactSiteConfiguration>();
206206
config.Setup(x => x.UseServerSideRendering).Returns(true);
207+
config.Setup(x => x.ExceptionHandler).Returns(() => throw new ReactServerRenderingException("test"));
207208

208209
var component = new ReactComponent(environment.Object, config.Object, "Foo", "container")
209210
{
@@ -223,9 +224,15 @@ public void ExceptionThrownIsHandled()
223224

224225
Assert.True(exceptionCaught);
225226

227+
// Custom handler passed into render call
228+
bool customHandlerInvoked = false;
229+
Action<Exception, string, string> customHandler = (ex, name, id) => customHandlerInvoked = true;
230+
component.RenderHtml(exceptionHandler: customHandler);
231+
Assert.True(customHandlerInvoked);
232+
226233
// Custom exception handler set
227234
Exception caughtException = null;
228-
config.Setup(x => x.ExceptionHandler).Returns(ex => caughtException = ex);
235+
config.Setup(x => x.ExceptionHandler).Returns((ex, name, id) => caughtException = ex);
229236

230237
var result = component.RenderHtml();
231238
Assert.Equal(@"<div id=""container""></div>", result);

tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
* This source code is licensed under the BSD-style license found in the
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
8-
*/
9-
8+
*/
9+
1010
using Moq;
11-
using Xunit;
1211
using React.Web.Mvc;
12+
using Xunit;
1313

1414
namespace React.Tests.Mvc
1515
{
@@ -31,7 +31,7 @@ private Mock<IReactEnvironment> ConfigureMockEnvironment()
3131
public void ReactWithInitShouldReturnHtmlAndScript()
3232
{
3333
var component = new Mock<IReactComponent>();
34-
component.Setup(x => x.RenderHtml(false, false)).Returns("HTML");
34+
component.Setup(x => x.RenderHtml(false, false, null)).Returns("HTML");
3535
component.Setup(x => x.RenderJavaScript()).Returns("JS");
3636
var environment = ConfigureMockEnvironment();
3737
environment.Setup(x => x.CreateComponent(
@@ -57,7 +57,7 @@ public void ReactWithInitShouldReturnHtmlAndScript()
5757
public void EngineIsReturnedToPoolAfterRender()
5858
{
5959
var component = new Mock<IReactComponent>();
60-
component.Setup(x => x.RenderHtml(true, true)).Returns("HTML");
60+
component.Setup(x => x.RenderHtml(true, true, null)).Returns("HTML");
6161
var environment = ConfigureMockEnvironment();
6262
environment.Setup(x => x.CreateComponent(
6363
"ComponentName",
@@ -75,15 +75,15 @@ public void EngineIsReturnedToPoolAfterRender()
7575
clientOnly: true,
7676
serverOnly: true
7777
);
78-
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == true)), Times.Once);
78+
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == true), null), Times.Once);
7979
environment.Verify(x => x.ReturnEngineToPool(), Times.Once);
8080
}
8181

8282
[Fact]
8383
public void ReactWithClientOnlyTrueShouldCallRenderHtmlWithTrue()
8484
{
8585
var component = new Mock<IReactComponent>();
86-
component.Setup(x => x.RenderHtml(true, true)).Returns("HTML");
86+
component.Setup(x => x.RenderHtml(true, true, null)).Returns("HTML");
8787
var environment = ConfigureMockEnvironment();
8888
environment.Setup(x => x.CreateComponent(
8989
"ComponentName",
@@ -100,13 +100,13 @@ public void ReactWithClientOnlyTrueShouldCallRenderHtmlWithTrue()
100100
clientOnly: true,
101101
serverOnly: true
102102
);
103-
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == true)), Times.Once);
103+
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == true), null), Times.Once);
104104
}
105105

106106
[Fact]
107107
public void ReactWithServerOnlyTrueShouldCallRenderHtmlWithTrue() {
108108
var component = new Mock<IReactComponent>();
109-
component.Setup(x => x.RenderHtml(true, true)).Returns("HTML");
109+
component.Setup(x => x.RenderHtml(true, true, null)).Returns("HTML");
110110
var environment = ConfigureMockEnvironment();
111111
environment.Setup(x => x.CreateComponent(
112112
"ComponentName",
@@ -123,7 +123,7 @@ public void ReactWithServerOnlyTrueShouldCallRenderHtmlWithTrue() {
123123
clientOnly: true,
124124
serverOnly: true
125125
);
126-
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == true)), Times.Once);
126+
component.Verify(x => x.RenderHtml(It.Is<bool>(y => y == true), It.Is<bool>(z => z == true), null), Times.Once);
127127
}
128128
}
129129
}

0 commit comments

Comments
 (0)