diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs
index 5a58d7fc50..871e958d62 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationFailedException.cs
@@ -4,38 +4,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.AspNet.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
+ ///
+ /// An exception thrown when accessing the result of a failed compilation.
+ ///
public class CompilationFailedException : Exception
{
- public CompilationFailedException(IEnumerable messages, string generatedCode)
+ ///
+ /// Instantiates a new instance of .
+ ///
+ /// The path of the Razor source file that was compiled.
+ /// The contents of the Razor source file.
+ /// The generated C# content that was compiled.
+ /// A sequence of encountered
+ /// during compilation.
+ public CompilationFailedException(
+ [NotNull] string filePath,
+ [NotNull] string fileContent,
+ [NotNull] string compiledContent,
+ [NotNull] IEnumerable messages)
: base(FormatMessage(messages))
{
+ FilePath = filePath;
+ FileContent = fileContent;
+ CompiledContent = compiledContent;
Messages = messages.ToList();
- GeneratedCode = generatedCode;
}
- public string GeneratedCode { get; private set; }
+ ///
+ /// Gets the path of the Razor source file that produced the compilation failure.
+ ///
+ public string FilePath { get; private set; }
+ ///
+ /// Gets a sequence of instances encountered during compilation.
+ ///
public IEnumerable Messages { get; private set; }
- public string CompilationSource
- {
- get { return GeneratedCode; }
- }
+ ///
+ /// Gets the content of the Razor source file.
+ ///
+ public string FileContent { get; private set; }
+ ///
+ /// Gets the generated C# content that was compiled.
+ ///
+ public string CompiledContent { get; private set; }
+
+ ///
public override string Message
{
get
{
- return "Compilation Failed:" + FormatMessage(Messages);
+ return Resources.FormatCompilationFailed(FilePath) +
+ Environment.NewLine +
+ FormatMessage(Messages);
}
}
private static string FormatMessage(IEnumerable messages)
{
- return String.Join(Environment.NewLine, messages);
+ return string.Join(Environment.NewLine, messages);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs
index 352a80bfd4..a99e80d4c9 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationMessage.cs
@@ -3,15 +3,30 @@
namespace Microsoft.AspNet.Mvc.Razor
{
+ ///
+ /// Represents a message encountered during compilation.
+ ///
public class CompilationMessage
{
+ ///
+ /// Initializes a with the specified message.
+ ///
+ /// A message produced from compilation.
public CompilationMessage(string message)
{
Message = message;
}
+ ///
+ /// Gets a message produced from compilation.
+ ///
public string Message { get; private set; }
+ ///
+ /// Returns a representation of this instance of .
+ ///
+ /// A representing this instance.
+ /// Returns same value as .
public override string ToString()
{
return Message;
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs
index 400cf64e18..7fbcac81b2 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs
@@ -3,46 +3,145 @@
using System;
using System.Collections.Generic;
-using System.Linq;
+using System.IO;
+using Microsoft.AspNet.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
+ ///
+ /// Represents the result of compilation.
+ ///
public class CompilationResult
{
- private readonly Type _type;
+ private Type _type;
- private CompilationResult(string generatedCode, Type type, IEnumerable messages)
+ private CompilationResult()
{
- _type = type;
- GeneratedCode = generatedCode;
- Messages = messages.ToList();
}
- public IEnumerable Messages { get; private set; }
-
- public string GeneratedCode { get; private set; }
+ ///
+ /// Gets the path of the Razor file that was compiled.
+ ///
+ public string FilePath
+ {
+ get
+ {
+ if (File != null)
+ {
+ return File.PhysicalPath;
+ }
+ return null;
+ }
+ }
+ ///
+ /// Gets a sequence of instances encountered during compilation.
+ ///
+ public IEnumerable Messages { get; private set; }
+
+ ///
+ /// Gets additional information from compilation.
+ ///
+ ///
+ /// In the event of a compilation failure, values from this dictionary are copied to the
+ /// property of the thrown.
+ ///
+ public IDictionary AdditionalInfo
+ {
+ get; private set;
+ }
+
+ ///
+ /// Gets the generated C# content that was compiled.
+ ///
+ public string CompiledContent { get; private set; }
+
+ ///
+ /// Gets the type produced as a result of compilation.
+ ///
+ /// An error occured during compilation.
public Type CompiledType
{
get
{
if (_type == null)
{
- throw new CompilationFailedException(Messages, GeneratedCode);
+ throw CreateCompilationFailedException();
}
return _type;
}
}
- public static CompilationResult Failed(string generatedCode, IEnumerable messages)
+ private IFileInfo File { get; set; }
+
+ ///
+ /// Creates a that represents a failure in compilation.
+ ///
+ /// The for the Razor file that was compiled.
+ /// The generated C# content to be compiled.
+ /// The sequence of failure messages encountered during compilation.
+ /// Additional info about the compilation.
+ /// A CompilationResult instance representing a failure.
+ public static CompilationResult Failed([NotNull] IFileInfo file,
+ [NotNull] string compilationContent,
+ [NotNull] IEnumerable messages,
+ IDictionary additionalInfo)
{
- return new CompilationResult(generatedCode, type: null, messages: messages);
+ return new CompilationResult
+ {
+ File = file,
+ CompiledContent = compilationContent,
+ Messages = messages,
+ AdditionalInfo = additionalInfo
+ };
}
- public static CompilationResult Successful(string generatedCode, Type type)
+ ///
+ /// Creates a that represents a success in compilation.
+ ///
+ /// The compiled type.
+ /// A CompilationResult instance representing a success.
+ public static CompilationResult Successful([NotNull] Type type)
{
- return new CompilationResult(generatedCode, type, Enumerable.Empty());
+ return new CompilationResult
+ {
+ _type = type
+ };
+ }
+
+ private CompilationFailedException CreateCompilationFailedException()
+ {
+ var fileContent = ReadContent(File);
+ var exception = new CompilationFailedException(FilePath, fileContent, CompiledContent, Messages);
+ if (AdditionalInfo != null)
+ {
+ foreach (var item in AdditionalInfo)
+ {
+ exception.Data.Add(item.Key, item.Value);
+ }
+ }
+
+ return exception;
+ }
+
+ private static string ReadContent(IFileInfo file)
+ {
+ try
+ {
+ using (var stream = file.CreateReadStream())
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+ }
+ catch (IOException)
+ {
+ // Don't throw if reading the file fails.
+ return string.Empty;
+ }
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
index bdc1884c0e..619b815235 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
@@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor
return result;
}
- return CompilationResult.Successful(generatedCode: null, type: compiledType);
+ return CompilationResult.Successful(compiledType);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs
index 90618f40ee..730ca831a5 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs
@@ -1,12 +1,23 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Threading.Tasks;
+using Microsoft.AspNet.FileSystems;
namespace Microsoft.AspNet.Mvc.Razor
{
+ ///
+ /// Provides methods for compilation of a Razor page.
+ ///
public interface ICompilationService
{
- CompilationResult Compile(string content);
+ ///
+ /// Compiles content and returns the result of compilation.
+ ///
+ /// The for the Razor file that was compiled.
+ /// The generated C# content to be compiled.
+ ///
+ /// A representing the result of compilation.
+ ///
+ CompilationResult Compile(IFileInfo fileInfo, string compilationContent);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
index dc8edffc5d..e176f9afd3 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs
@@ -7,15 +7,22 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text;
+using Microsoft.AspNet.FileSystems;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.Text;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
+ ///
+ /// A type that uses Roslyn to compile C# content.
+ ///
public class RoslynCompilationService : ICompilationService
{
+ public static readonly string CompilationResultDiagnosticsKey = "Diagnostics";
private static readonly ConcurrentDictionary _metadataFileCache =
new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
@@ -23,6 +30,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
private readonly IApplicationEnvironment _environment;
private readonly IAssemblyLoaderEngine _loader;
+ ///
+ /// Initalizes a new instance of the class.
+ ///
+ /// The environment for the executing application.
+ /// The loader used to load compiled assemblies.
+ /// The library manager that provides export and reference information.
public RoslynCompilationService(IApplicationEnvironment environment,
IAssemblyLoaderEngine loaderEngine,
ILibraryManager libraryManager)
@@ -32,9 +45,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
_libraryManager = libraryManager;
}
- public CompilationResult Compile(string content)
+ ///
+ public CompilationResult Compile(IFileInfo fileInfo, string compilationContent)
{
- var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(content) };
+ var sourceText = SourceText.From(compilationContent, Encoding.UTF8);
+ var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(sourceText, path: fileInfo.PhysicalPath) };
var targetFramework = _environment.TargetFramework;
var references = GetApplicationReferences();
@@ -70,7 +85,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
.Select(d => GetCompilationMessage(formatter, d))
.ToList();
- return CompilationResult.Failed(content, messages);
+ var additionalInfo = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ { CompilationResultDiagnosticsKey, result.Diagnostics }
+ };
+ return CompilationResult.Failed(fileInfo, compilationContent, messages, additionalInfo);
}
Assembly assembly;
@@ -89,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var type = assembly.GetExportedTypes()
.First();
- return CompilationResult.Successful(string.Empty, type);
+ return CompilationResult.Successful(type);
}
}
}
@@ -135,7 +154,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
});
}
- private CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic)
+ private static CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic)
{
return new CompilationMessage(formatter.Format(diagnostic));
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
index 21ff13315a..6380f8cc82 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
@@ -26,6 +26,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return GetString("ArgumentCannotBeNullOrEmpty");
}
+ ///
+ /// Compilation for '{0}' failed:
+ ///
+ internal static string CompilationFailed
+ {
+ get { return GetString("CompilationFailed"); }
+ }
+
+ ///
+ /// Compilation for '{0}' failed:
+ ///
+ internal static string FormatCompilationFailed(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("CompilationFailed"), p0);
+ }
+
///
/// The layout view '{0}' could not be located.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs
index 85231041c1..20945a83e8 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorCompilationService.cs
@@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly string _appRoot;
public RazorCompilationService(IApplicationEnvironment environment,
- ICompilationService compilationService,
+ ICompilationService compilationService,
IMvcRazorHost razorHost)
{
_environment = environment;
@@ -49,10 +49,10 @@ namespace Microsoft.AspNet.Mvc.Razor
if (!results.Success)
{
var messages = results.ParserErrors.Select(e => new CompilationMessage(e.Message));
- throw new CompilationFailedException(messages, results.GeneratedCode);
+ return CompilationResult.Failed(file, results.GeneratedCode, messages, additionalInfo: null);
}
- return _baseCompilationService.Compile(results.GeneratedCode);
+ return _baseCompilationService.Compile(file, results.GeneratedCode);
}
private static string EnsureTrailingSlash([NotNull]string path)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
index 767a9ac8df..bae45587d0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
@@ -120,6 +120,9 @@
The value cannot be null or empty.
+
+ Compilation for '{0}' failed:
+
The layout view '{0}' could not be located.
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs
new file mode 100644
index 0000000000..94a75ca5be
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Microsoft.AspNet.FileSystems;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Razor.Test
+{
+ public class CompilationResultTest
+ {
+ [Fact]
+ public void FailedResult_ThrowsWhenAccessingCompiledType()
+ {
+ // Arrange
+ var expected =
+@"Compilation for 'myfile' failed:
+hello
+world";
+ var originalContent = "Original file content";
+ var fileInfo = new Mock();
+ fileInfo.SetupGet(f => f.PhysicalPath)
+ .Returns("myfile");
+ var contentBytes = Encoding.UTF8.GetBytes(originalContent);
+ fileInfo.Setup(f => f.CreateReadStream())
+ .Returns(new MemoryStream(contentBytes));
+ var messages = new[]
+ {
+ new CompilationMessage("hello"),
+ new CompilationMessage("world")
+ };
+ var additionalInfo = new Dictionary
+ {
+ { "key", "value" }
+ };
+ var result = CompilationResult.Failed(fileInfo.Object,
+ "hello world
",
+ messages,
+ additionalInfo);
+
+ // Act and Assert
+ var ex = Assert.Throws(() => result.CompiledType);
+ Assert.Equal(expected, ex.Message);
+ Assert.Equal("value", ex.Data["key"]);
+ Assert.Equal(originalContent, ex.FileContent);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj b/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj
index f6d31ead62..9d7e6cc0c2 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj
@@ -21,6 +21,7 @@
+
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorCompilationServiceTest.cs
index c64bb3e077..fc43e1e07f 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorCompilationServiceTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorCompilationServiceTest.cs
@@ -27,14 +27,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
host.Setup(h => h.GenerateCode(@"views\index\home.cshtml", It.IsAny()))
.Returns(new GeneratorResults(new Block(new BlockBuilder { Type = BlockType.Comment }), new RazorError[0], new CodeBuilderResult("", new LineMapping[0])))
.Verifiable();
- var compiler = new Mock();
- compiler.Setup(c => c.Compile(It.IsAny()))
- .Returns(CompilationResult.Successful("", typeof(RazorCompilationServiceTest)));
- var razorService = new RazorCompilationService(env.Object, compiler.Object, host.Object);
var fileInfo = new Mock();
fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null);
+ var compiler = new Mock();
+ compiler.Setup(c => c.Compile(fileInfo.Object, It.IsAny()))
+ .Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));
+ var razorService = new RazorCompilationService(env.Object, compiler.Object, host.Object);
// Act
razorService.CompileCore(fileInfo.Object);