diff --git a/src/Microsoft.AspNetCore.Razor/Properties/RazorResources.Designer.cs b/src/Microsoft.AspNetCore.Razor/Properties/RazorResources.Designer.cs
index 50f903abc0..7c92b15f09 100644
--- a/src/Microsoft.AspNetCore.Razor/Properties/RazorResources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Razor/Properties/RazorResources.Designer.cs
@@ -1562,6 +1562,22 @@ namespace Microsoft.AspNetCore.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_IncompleteQuotesAroundDirective"), p0);
}
+ ///
+ /// A fatal exception occurred when trying to parse '{0}':{1}{2}
+ ///
+ internal static string FatalException
+ {
+ get { return GetString("FatalException"); }
+ }
+
+ ///
+ /// A fatal exception occurred when trying to parse '{0}':{1}{2}
+ ///
+ internal static string FormatFatalException(object p0, object p1, object p2)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("FatalException"), p0, p1, p2);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNetCore.Razor/RazorResources.resx b/src/Microsoft.AspNetCore.Razor/RazorResources.resx
index 515a977fa3..06855362d7 100644
--- a/src/Microsoft.AspNetCore.Razor/RazorResources.resx
+++ b/src/Microsoft.AspNetCore.Razor/RazorResources.resx
@@ -428,4 +428,7 @@ Instead, wrap the contents of the block in "{{}}":
Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.
+
+ A fatal exception occurred when trying to parse '{0}':{1}{2}
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Razor/RazorTemplateEngine.cs b/src/Microsoft.AspNetCore.Razor/RazorTemplateEngine.cs
index 7344f07551..dcbe13473c 100644
--- a/src/Microsoft.AspNetCore.Razor/RazorTemplateEngine.cs
+++ b/src/Microsoft.AspNetCore.Razor/RazorTemplateEngine.cs
@@ -2,14 +2,19 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
+using Microsoft.AspNetCore.Razor.Chunks;
using Microsoft.AspNetCore.Razor.Chunks.Generators;
using Microsoft.AspNetCore.Razor.CodeGenerators;
+using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Microsoft.AspNetCore.Razor.Parser;
+using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
using Microsoft.AspNetCore.Razor.Text;
namespace Microsoft.AspNetCore.Razor
@@ -363,26 +368,50 @@ namespace Microsoft.AspNetCore.Razor
throw new ArgumentNullException(nameof(input));
}
- className = (className ?? Host.DefaultClassName) ?? DefaultClassName;
- rootNamespace = (rootNamespace ?? Host.DefaultNamespace) ?? DefaultNamespace;
+ try
+ {
+ className = (className ?? Host.DefaultClassName) ?? DefaultClassName;
+ rootNamespace = (rootNamespace ?? Host.DefaultNamespace) ?? DefaultNamespace;
- // Run the parser
- var parser = CreateParser(sourceFileName);
- Debug.Assert(parser != null);
- var results = parser.Parse(input);
+ // Run the parser
+ var parser = CreateParser(sourceFileName);
+ Debug.Assert(parser != null);
+ var results = parser.Parse(input);
- // Generate code
- var chunkGenerator = CreateChunkGenerator(className, rootNamespace, sourceFileName);
- chunkGenerator.DesignTimeMode = Host.DesignTimeMode;
- chunkGenerator.Visit(results);
+ // Generate code
+ var chunkGenerator = CreateChunkGenerator(className, rootNamespace, sourceFileName);
+ chunkGenerator.DesignTimeMode = Host.DesignTimeMode;
+ chunkGenerator.Visit(results);
- var codeGeneratorContext = new CodeGeneratorContext(chunkGenerator.Context, results.ErrorSink);
- codeGeneratorContext.Checksum = checksum;
- var codeGenerator = CreateCodeGenerator(codeGeneratorContext);
- var codeGeneratorResult = codeGenerator.Generate();
+ var codeGeneratorContext = new CodeGeneratorContext(chunkGenerator.Context, results.ErrorSink);
+ codeGeneratorContext.Checksum = checksum;
+ var codeGenerator = CreateCodeGenerator(codeGeneratorContext);
+ var codeGeneratorResult = codeGenerator.Generate();
- // Collect results and return
- return new GeneratorResults(results, codeGeneratorResult, codeGeneratorContext.ChunkTreeBuilder.Root);
+ // Collect results and return
+ return new GeneratorResults(results, codeGeneratorResult, codeGeneratorContext.ChunkTreeBuilder.Root);
+ }
+ // During runtime we want code generation explosions to flow up into the calling code. At design time
+ // we want to capture these exceptions to prevent IDEs from crashing.
+ catch (Exception ex) when (Host.DesignTimeMode)
+ {
+ var errorSink = new ErrorSink();
+ errorSink.OnError(
+ SourceLocation.Undefined,
+ RazorResources.FormatFatalException(sourceFileName, Environment.NewLine, ex.Message),
+ length: -1);
+ var emptyBlock = new BlockBuilder();
+ emptyBlock.Type = default(BlockType);
+
+ return new GeneratorResults(
+ document: emptyBlock.Build(),
+ tagHelperDescriptors: Enumerable.Empty(),
+ errorSink: errorSink,
+ codeGeneratorResult: new CodeGeneratorResult(
+ code: string.Empty,
+ designTimeLineMappings: new List()),
+ chunkTree: new ChunkTree());
+ }
}
protected internal virtual RazorChunkGenerator CreateChunkGenerator(
diff --git a/test/Microsoft.AspNetCore.Razor.Test/RazorTemplateEngineTest.cs b/test/Microsoft.AspNetCore.Razor.Test/RazorTemplateEngineTest.cs
index 122f84866e..b66d873844 100644
--- a/test/Microsoft.AspNetCore.Razor.Test/RazorTemplateEngineTest.cs
+++ b/test/Microsoft.AspNetCore.Razor.Test/RazorTemplateEngineTest.cs
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
@@ -18,6 +19,53 @@ namespace Microsoft.AspNetCore.Razor
{
public class RazorTemplateEngineTest
{
+ [Fact]
+ public void InvalidRazorEngineHostReturnsParseErrorsAtDesignTime()
+ {
+ // Arrange
+ var host = new InvalidRazorEngineHost(new CSharpRazorCodeLanguage())
+ {
+ DesignTimeMode = true
+ };
+ var razorEngine = new RazorTemplateEngine(host);
+ var input = new StringTextBuffer("Hello @(\"World\")
");
+ var exception = new InvalidOperationException("Hello World");
+ var expectedError = RazorResources.FormatFatalException("test", Environment.NewLine, exception.Message);
+
+ // Act
+ var result = razorEngine.GenerateCode(input, className: null, rootNamespace: null, sourceFileName: "test");
+
+ // Assert
+ Assert.Empty(result.Document.Children);
+ Assert.Empty(result.ChunkTree.Children);
+ Assert.Empty(result.DesignTimeLineMappings);
+ Assert.Empty(result.GeneratedCode);
+
+ var error = Assert.Single(result.ParserErrors);
+ Assert.Equal(expectedError, error.Message, StringComparer.Ordinal);
+ Assert.Equal(SourceLocation.Undefined, error.Location);
+ Assert.Equal(-1, error.Length);
+ }
+
+ [Fact]
+ public void InvalidRazorEngineHostThrowsAtRuntime()
+ {
+ // Arrange
+ var host = new InvalidRazorEngineHost(new CSharpRazorCodeLanguage())
+ {
+ DesignTimeMode = false
+ };
+ var razorEngine = new RazorTemplateEngine(host);
+ var input = new StringTextBuffer("Hello @(\"World\")
");
+
+ // Act
+ var thrownException = Assert.Throws(() =>
+ razorEngine.GenerateCode(input, className: null, rootNamespace: null, sourceFileName: "test"));
+
+ // Assert
+ Assert.Equal("Hello World", thrownException.Message, StringComparer.Ordinal);
+ }
+
[Fact]
public void ConstructorInitializesHost()
{
@@ -339,5 +387,23 @@ namespace Microsoft.AspNetCore.Razor
return null;
}
}
+
+ private class InvalidRazorEngineHost : RazorEngineHost
+ {
+ public InvalidRazorEngineHost(RazorCodeLanguage codeLanguage) : base(codeLanguage)
+ {
+ }
+
+ public override string DefaultClassName
+ {
+ get
+ {
+ throw new InvalidOperationException("Hello World");
+ }
+ set
+ {
+ }
+ }
+ }
}
}