Skip to content

Commit

Permalink
Merge pull request NancyFx#669 from grumpydev/RazorErrors
Browse files Browse the repository at this point in the history
Razor errors
  • Loading branch information
thecodejunkie committed Jun 28, 2012
2 parents 0bee361 + a77cd16 commit dfd06ae
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 26 deletions.
6 changes: 6 additions & 0 deletions src/Nancy.Demo.Hosting.Aspnet/MainModule.cs
Expand Up @@ -51,6 +51,12 @@ public MainModule(IRouteCacheProvider routeCacheProvider)
return View["razor.cshtml", model];
};

Get["/razorError"] = x =>
{
var model = new RatPack { FirstName = "Frank" };
return View["razor-error.cshtml", model];
};

Get["/razor-simple"] = x =>
{
var model = new RatPack { FirstName = "Frank" };
Expand Down
Expand Up @@ -106,6 +106,8 @@
<Content Include="Content\face.png" />
<Content Include="Content\main.css" />
<Content Include="Content\scripts.js" />
<Content Include="Views\razor-error.cshtml" />
<Content Include="Views\razor-layout-error.cshtml" />
<None Include="Views\FileUpload.sshtml">
<SubType>Designer</SubType>
</None>
Expand Down
14 changes: 14 additions & 0 deletions src/Nancy.Demo.Hosting.Aspnet/Views/razor-error.cshtml
@@ -0,0 +1,14 @@
@{
Layout = "razor-layout-error.cshtml";
}
@section Header
{
<!-- This comment should appear in the header -->
}
<h1>Hello @Model.FirstName</h1>
<p>This is a sample Razor view!</p>
@section Footer
{
<p>This is footer content!</p>
<img src='@Url.Content("~/content/face.png")' alt="Face"/>
}
12 changes: 12 additions & 0 deletions src/Nancy.Demo.Hosting.Aspnet/Views/razor-layout-error.cshtml
@@ -0,0 +1,12 @@
<html>
<head>
<title>Razor View Engine Demo - @Model.FirstName</title>
@RenderSection("Header")
</head>
<body>
<div id="body">@RenderBody()</div>
<div id="Error">@ThisDoesntExist()</div>
<div id="footer">@RenderSection("Footer")</div>
<div id="optional">@RenderSection("Optional", false)</div>
</body>
</html>
3 changes: 3 additions & 0 deletions src/Nancy.ViewEngines.Razor/Nancy.ViewEngines.Razor.csproj
Expand Up @@ -120,6 +120,9 @@
<Name>Nancy</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\CompilationError.html" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
45 changes: 34 additions & 11 deletions src/Nancy.ViewEngines.Razor/NancyRazorErrorView.cs
@@ -1,7 +1,26 @@
namespace Nancy.ViewEngines.Razor
{
using System;
using System.IO;

/// <summary>
/// Razor view used when compilation of the view fails.
/// </summary>
public class NancyRazorErrorView : NancyRazorViewBase
{
private static string template;

/// <summary>
/// Gets or sets the template for rendinger errors.
/// The token "[DETAILS]" will be replaced by the HTML for
/// the actual error.
/// </summary>
public static string Template
{
get { return template ?? (template = LoadResource(@"CompilationError.html")); }
set { template = value; }
}

/// <summary>
/// Initializes a new instance of the <see cref="NancyRazorErrorView"/> class.
/// </summary>
Expand All @@ -16,22 +35,26 @@ public class NancyRazorErrorView : NancyRazorViewBase
public string Message { get; private set; }

/// <summary>
/// Writes the literal.
/// Executes this instance.
/// </summary>
/// <param name="value">The value.</param>
public override void WriteLiteral(object value)
public override void Execute()
{
base.WriteLiteral(Message);
base.WriteLiteral(Template.Replace("[DETAILS]", StaticConfiguration.DisableErrorTraces ? String.Empty : this.Message));
}

/// <summary>
/// Executes this instance.
/// </summary>
public override void Execute()
private static string LoadResource(string filename)
{
base.WriteLiteral(this.Message);
base.WriteLiteral("<hr />");
base.WriteLiteral(this.Code);
var resourceStream = typeof(NancyRazorErrorView).Assembly.GetManifestResourceStream(String.Format("Nancy.ViewEngines.Razor.Resources.{0}", filename));

if (resourceStream == null)
{
return string.Empty;
}

using (var reader = new StreamReader(resourceStream))
{
return reader.ReadToEnd();
}
}
}
}
74 changes: 59 additions & 15 deletions src/Nancy.ViewEngines.Razor/RazorViewEngine.cs
Expand Up @@ -10,6 +10,7 @@
using System.Text;
using System.Web.Razor;
using System.Web.Razor.Parser.SyntaxTree;
using System.Web.Razor.Text;

using Nancy.Bootstrapper;
using Nancy.Responses;
Expand Down Expand Up @@ -125,22 +126,20 @@ private RazorTemplateEngine GetRazorTemplateEngine(RazorEngineHost engineHost)
return new RazorTemplateEngine(engineHost);
}

private Func<NancyRazorViewBase> GetCompiledViewFactory(string extension, TextReader reader, Assembly referencingAssembly, Type passedModelType)
private Func<NancyRazorViewBase> GetCompiledViewFactory(string extension, TextReader reader, Assembly referencingAssembly, Type passedModelType, ViewLocationResult viewLocationResult)
{
var renderer = this.viewRenderers
.Where(x => x.Extension.Equals(extension, StringComparison.OrdinalIgnoreCase))
.First();
var renderer = this.viewRenderers.First(x => x.Extension.Equals(extension, StringComparison.OrdinalIgnoreCase));

var engine = this.GetRazorTemplateEngine(renderer.Host);

var razorResult = engine.GenerateCode(reader);

var viewFactory = this.GenerateRazorViewFactory(renderer.Provider, razorResult, referencingAssembly, renderer.Assemblies, passedModelType);
var razorResult = engine.GenerateCode(reader, sourceFileName:"placeholder");

var viewFactory = this.GenerateRazorViewFactory(renderer.Provider, razorResult, referencingAssembly, renderer.Assemblies, passedModelType, viewLocationResult);

return viewFactory;
}

private Func<NancyRazorViewBase> GenerateRazorViewFactory(CodeDomProvider codeProvider, GeneratorResults razorResult, Assembly referencingAssembly, IEnumerable<string> rendererSpecificAssemblies, Type passedModelType)
private Func<NancyRazorViewBase> GenerateRazorViewFactory(CodeDomProvider codeProvider, GeneratorResults razorResult, Assembly referencingAssembly, IEnumerable<string> rendererSpecificAssemblies, Type passedModelType, ViewLocationResult viewLocationResult)
{
var outputAssemblyName = Path.Combine(Path.GetTempPath(), String.Format("Temp_{0}.dll", Guid.NewGuid().ToString("N")));

Expand Down Expand Up @@ -183,13 +182,20 @@ private Func<NancyRazorViewBase> GenerateRazorViewFactory(CodeDomProvider codePr

if (results.Errors.HasErrors)
{
var err = results.Errors
.OfType<CompilerError>()
.Where(ce => !ce.IsWarning)
.Select(error => String.Format("Error Compiling Template: ({0}, {1}) {2})", error.Line, error.Column, error.ErrorText))
.Aggregate((s1, s2) => s1 + "<br/>" + s2);
var fullTemplateName = viewLocationResult.Location + "/" + viewLocationResult.Name + "." + viewLocationResult.Extension;
var templateLines = GetViewBodyLines(viewLocationResult);
var errors = results.Errors.OfType<CompilerError>().Where(ce => !ce.IsWarning).ToArray();
var errorMessages = BuildErrorMessages(errors);

MarkErrorLines(errors, templateLines);

return () => new NancyRazorErrorView(err);
var errorDetails = string.Format(
"Error compiling template: <strong>{0}</strong><br/><br/>Errors:<br/>{1}<br/><br/>Details:<br/>{2}",
fullTemplateName,
errorMessages,
templateLines.Aggregate((s1, s2) => s1 + "<br/>" + s2));

return () => new NancyRazorErrorView(errorDetails);
}

var assembly = Assembly.LoadFrom(outputAssemblyName);
Expand All @@ -215,6 +221,44 @@ private Func<NancyRazorViewBase> GenerateRazorViewFactory(CodeDomProvider codePr
return () => (NancyRazorViewBase)Activator.CreateInstance(type);
}

private static string BuildErrorMessages(IEnumerable<CompilerError> errors)
{
return errors.Select(error => String.Format(
"[{0}] Line: {1} Column: {2} - {3} (<a class='LineLink' href='#{1}'>show</a>)",
error.ErrorNumber,
error.Line,
error.Column,
error.ErrorText)).Aggregate((s1, s2) => s1 + "<br/>" + s2);
}

private static void MarkErrorLines(IEnumerable<CompilerError> errors, IList<string> templateLines)
{
foreach (var compilerError in errors)
{
var lineIndex = compilerError.Line - 1;
if (lineIndex <= templateLines.Count - 1)
{
templateLines[lineIndex] = string.Format("<span class='error'><a name='{0}' />{1}</span>", compilerError.Line, templateLines[lineIndex]);
}
}
}

private static string[] GetViewBodyLines(ViewLocationResult viewLocationResult)
{
var templateLines = new List<string>();
using (var templateReader = viewLocationResult.Contents.Invoke())
{
var currentLine = templateReader.ReadLine();
while (currentLine != null)
{
templateLines.Add(Helpers.HttpUtility.HtmlEncode(currentLine));

currentLine = templateReader.ReadLine();
}
}
return templateLines.ToArray();
}

private static Type FindModelType(Block block, Type passedModelType)
{
var modelFinder = new ModelFinder();
Expand Down Expand Up @@ -291,7 +335,7 @@ private NancyRazorViewBase GetOrCompileView(ViewLocationResult viewLocationResul
{
var viewFactory = renderContext.ViewCache.GetOrAdd(
viewLocationResult,
x => this.GetCompiledViewFactory(x.Extension, x.Contents.Invoke(), referencingAssembly, passedModelType));
x => this.GetCompiledViewFactory(x.Extension, x.Contents.Invoke(), referencingAssembly, passedModelType, viewLocationResult));

var view = viewFactory.Invoke();

Expand Down
149 changes: 149 additions & 0 deletions src/Nancy.ViewEngines.Razor/Resources/CompilationError.html

Large diffs are not rendered by default.

0 comments on commit dfd06ae

Please sign in to comment.