diff --git a/src/React.AspNet/HtmlHelperExtensions.cs b/src/React.AspNet/HtmlHelperExtensions.cs
index cb6b168c9..b136ca42f 100644
--- a/src/React.AspNet/HtmlHelperExtensions.cs
+++ b/src/React.AspNet/HtmlHelperExtensions.cs
@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
+using System;
using React.Exceptions;
using React.TinyIoC;
@@ -55,6 +56,7 @@ private static IReactEnvironment Environment
/// Skip rendering server-side and only output client-side initialisation code. Defaults to false
/// Skip rendering React specific data-attributes during server side rendering. Defaults to false
/// HTML class(es) to set on the container tag
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
/// The component's HTML
public static IHtmlString React(
this IHtmlHelper htmlHelper,
@@ -64,7 +66,8 @@ public static IHtmlString React(
string containerId = null,
bool clientOnly = false,
bool serverOnly = false,
- string containerClass = null
+ string containerClass = null,
+ Action exceptionHandler = null
)
{
try
@@ -78,7 +81,7 @@ public static IHtmlString React(
{
reactComponent.ContainerClass = containerClass;
}
- var result = reactComponent.RenderHtml(clientOnly, serverOnly);
+ var result = reactComponent.RenderHtml(clientOnly, serverOnly, exceptionHandler);
return new HtmlString(result);
}
finally
@@ -100,6 +103,7 @@ public static IHtmlString React(
/// ID to use for the container HTML tag. Defaults to an auto-generated ID
/// Skip rendering server-side and only output client-side initialisation code. Defaults to false
/// HTML class(es) to set on the container tag
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
/// The component's HTML
public static IHtmlString ReactWithInit(
this IHtmlHelper htmlHelper,
@@ -108,7 +112,8 @@ public static IHtmlString ReactWithInit(
string htmlTag = null,
string containerId = null,
bool clientOnly = false,
- string containerClass = null
+ string containerClass = null,
+ Action exceptionHandler = null
)
{
try
@@ -122,7 +127,7 @@ public static IHtmlString ReactWithInit(
{
reactComponent.ContainerClass = containerClass;
}
- var html = reactComponent.RenderHtml(clientOnly);
+ var html = reactComponent.RenderHtml(clientOnly, exceptionHandler: exceptionHandler);
#if LEGACYASPNET
var script = new TagBuilder("script")
diff --git a/src/React.Core/IReactComponent.cs b/src/React.Core/IReactComponent.cs
index 04016d09a..3cdf7f794 100644
--- a/src/React.Core/IReactComponent.cs
+++ b/src/React.Core/IReactComponent.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright (c) 2014-Present, Facebook, Inc.
* All rights reserved.
*
@@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
+using System;
+
namespace React
{
///
@@ -45,8 +47,9 @@ public interface IReactComponent
///
/// Only renders component container. Used for client-side only rendering.
/// Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
/// HTML
- string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false);
+ string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action exceptionHandler = null);
///
/// Renders the JavaScript required to initialise this component client-side. This will
diff --git a/src/React.Core/IReactSiteConfiguration.cs b/src/React.Core/IReactSiteConfiguration.cs
index bd10fee0f..8c3eed6da 100644
--- a/src/React.Core/IReactSiteConfiguration.cs
+++ b/src/React.Core/IReactSiteConfiguration.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright (c) 2014-Present, Facebook, Inc.
* All rights reserved.
*
@@ -179,5 +179,19 @@ public interface IReactSiteConfiguration
/// Disables server-side rendering. This is useful when debugging your scripts.
///
IReactSiteConfiguration DisableServerSideRendering();
+
+ ///
+ /// An exception handler which will be called if a render exception is thrown.
+ /// If unset, unhandled exceptions will be thrown for all component renders.
+ ///
+ Action ExceptionHandler { get; set; }
+
+ ///
+ /// Sets an exception handler which will be called if a render exception is thrown.
+ /// If unset, unhandled exceptions will be thrown for all component renders.
+ ///
+ ///
+ ///
+ IReactSiteConfiguration SetExceptionHandler(Action handler);
}
}
diff --git a/src/React.Core/ReactComponent.cs b/src/React.Core/ReactComponent.cs
index 43c40628c..e736604f0 100644
--- a/src/React.Core/ReactComponent.cs
+++ b/src/React.Core/ReactComponent.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright (c) 2014-Present, Facebook, Inc.
* All rights reserved.
*
@@ -107,8 +107,9 @@ public ReactComponent(IReactEnvironment environment, IReactSiteConfiguration con
///
/// Only renders component container. Used for client-side only rendering.
/// Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
/// HTML
- public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false)
+ public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action exceptionHandler = null)
{
if (!_configuration.UseServerSideRendering)
{
@@ -120,39 +121,39 @@ public virtual string RenderHtml(bool renderContainerOnly = false, bool renderSe
EnsureComponentExists();
}
- try
+ var html = string.Empty;
+ if (!renderContainerOnly)
{
- var html = string.Empty;
- if (!renderContainerOnly)
+ try
{
var reactRenderCommand = renderServerOnly
? string.Format("ReactDOMServer.renderToStaticMarkup({0})", GetComponentInitialiser())
: string.Format("ReactDOMServer.renderToString({0})", GetComponentInitialiser());
html = _environment.Execute(reactRenderCommand);
}
-
- string attributes = string.Format("id=\"{0}\"", ContainerId);
- if (!string.IsNullOrEmpty(ContainerClass))
+ catch (JsRuntimeException ex)
{
- attributes += string.Format(" class=\"{0}\"", ContainerClass);
- }
+ if (exceptionHandler == null)
+ {
+ exceptionHandler = _configuration.ExceptionHandler;
+ }
- return string.Format(
- "<{2} {0}>{1}{2}>",
- attributes,
- html,
- ContainerTag
- );
+ exceptionHandler(ex, ComponentName, ContainerId);
+ }
}
- catch (JsRuntimeException ex)
+
+ string attributes = string.Format("id=\"{0}\"", ContainerId);
+ if (!string.IsNullOrEmpty(ContainerClass))
{
- throw new ReactServerRenderingException(string.Format(
- "Error while rendering \"{0}\" to \"{2}\": {1}",
- ComponentName,
- ex.Message,
- ContainerId
- ));
+ attributes += string.Format(" class=\"{0}\"", ContainerClass);
}
+
+ return string.Format(
+ "<{2} {0}>{1}{2}>",
+ attributes,
+ html,
+ ContainerTag
+ );
}
///
diff --git a/src/React.Core/ReactSiteConfiguration.cs b/src/React.Core/ReactSiteConfiguration.cs
index bab2e5e23..879a92db9 100644
--- a/src/React.Core/ReactSiteConfiguration.cs
+++ b/src/React.Core/ReactSiteConfiguration.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright (c) 2014-Present, Facebook, Inc.
* All rights reserved.
*
@@ -7,9 +7,11 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
-using Newtonsoft.Json;
+using System;
using System.Collections.Generic;
using System.Linq;
+using Newtonsoft.Json;
+using React.Exceptions;
namespace React
{
@@ -44,6 +46,13 @@ public ReactSiteConfiguration()
};
UseDebugReact = false;
UseServerSideRendering = true;
+ ExceptionHandler = (Exception ex, string ComponentName, string ContainerId) =>
+ throw new ReactServerRenderingException(string.Format(
+ "Error while rendering \"{0}\" to \"{2}\": {1}",
+ ComponentName,
+ ex.Message,
+ ContainerId
+ ));
}
///
@@ -300,5 +309,22 @@ public IReactSiteConfiguration DisableServerSideRendering()
UseServerSideRendering = false;
return this;
}
+
+ ///
+ /// Handle an exception caught during server-render of a component.
+ /// If unset, unhandled exceptions will be thrown for all component renders.
+ ///
+ public Action ExceptionHandler { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public IReactSiteConfiguration SetExceptionHandler(Action handler)
+ {
+ ExceptionHandler = handler;
+ return this;
+ }
}
}
diff --git a/src/React.Sample.CoreMvc/Controllers/HomeController.cs b/src/React.Sample.CoreMvc/Controllers/HomeController.cs
index 8dd83e224..5962890cd 100644
--- a/src/React.Sample.CoreMvc/Controllers/HomeController.cs
+++ b/src/React.Sample.CoreMvc/Controllers/HomeController.cs
@@ -37,6 +37,7 @@ public class IndexViewModel
{
public IEnumerable Comments { get; set; }
public int CommentsPerPage { get; set; }
+ public bool ThrowRenderError { get; set; }
}
}
@@ -78,7 +79,8 @@ public IActionResult Index()
return View(new IndexViewModel
{
Comments = _comments.Take(COMMENTS_PER_PAGE),
- CommentsPerPage = COMMENTS_PER_PAGE
+ CommentsPerPage = COMMENTS_PER_PAGE,
+ ThrowRenderError = Request.Query.ContainsKey("throwRenderError"),
});
}
diff --git a/src/React.Sample.CoreMvc/Startup.cs b/src/React.Sample.CoreMvc/Startup.cs
index 356107449..d4b0da082 100644
--- a/src/React.Sample.CoreMvc/Startup.cs
+++ b/src/React.Sample.CoreMvc/Startup.cs
@@ -5,8 +5,8 @@
* 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 Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -20,15 +20,16 @@ namespace React.Sample.CoreMvc
{
public class Startup
{
- public Startup(IHostingEnvironment env)
+ public Startup(IHostingEnvironment env, ILogger logger)
{
// Setup configuration sources.
var builder = new ConfigurationBuilder().AddEnvironmentVariables();
-
+ Logger = logger;
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
+ public ILogger Logger { get; set; }
// This method gets called by the runtime.
public IServiceProvider ConfigureServices(IServiceCollection services)
@@ -70,6 +71,10 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
config
.SetReuseJavaScriptEngines(true)
.AddScript("~/js/Sample.jsx")
+ .SetExceptionHandler((ex, name, id) =>
+ {
+ Logger.LogError("React component exception thrown!" + ex.ToString());
+ })
.SetUseDebugReact(true);
});
diff --git a/src/React.Sample.CoreMvc/Views/Home/Index.cshtml b/src/React.Sample.CoreMvc/Views/Home/Index.cshtml
index 3f65ab4e7..6f939ad17 100644
--- a/src/React.Sample.CoreMvc/Views/Home/Index.cshtml
+++ b/src/React.Sample.CoreMvc/Views/Home/Index.cshtml
@@ -14,7 +14,7 @@