[Fixes #157] ErrorPage middleware doesn't show source code lines of Razor files
This commit is contained in:
parent
6e72a3935d
commit
8d583a4718
|
|
@ -11,6 +11,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Diagnostics.Views;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
|
@ -28,6 +29,7 @@ namespace Microsoft.AspNet.Diagnostics
|
|||
private readonly ErrorPageOptions _options;
|
||||
private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ErrorPageMiddleware"/> class
|
||||
|
|
@ -35,11 +37,15 @@ namespace Microsoft.AspNet.Diagnostics
|
|||
/// <param name="next"></param>
|
||||
/// <param name="options"></param>
|
||||
public ErrorPageMiddleware(
|
||||
[NotNull] RequestDelegate next, [NotNull] ErrorPageOptions options, ILoggerFactory loggerFactory)
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] ErrorPageOptions options,
|
||||
ILoggerFactory loggerFactory,
|
||||
IApplicationEnvironment appEnvironment)
|
||||
{
|
||||
_next = next;
|
||||
_options = options;
|
||||
_logger = loggerFactory.CreateLogger<ErrorPageMiddleware>();
|
||||
_fileProvider = options.FileProvider ?? new PhysicalFileProvider(appEnvironment.ApplicationBasePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -183,31 +189,102 @@ namespace Microsoft.AspNet.Diagnostics
|
|||
|
||||
int lineNumber = line.ToInt32();
|
||||
|
||||
return string.IsNullOrEmpty(file)
|
||||
? LoadFrame(string.IsNullOrEmpty(function) ? line.ToString() : function, string.Empty, 0)
|
||||
: LoadFrame(function, file, lineNumber);
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return GetStackFrame(
|
||||
// Handle stack trace lines like
|
||||
// "--- End of stack trace from previous location where exception from thrown ---"
|
||||
string.IsNullOrEmpty(function) ? line.ToString() : function,
|
||||
file: string.Empty,
|
||||
lineNumber: 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetStackFrame(function, file, lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private StackFrame LoadFrame(string function, string file, int lineNumber)
|
||||
// make it internal to enable unit testing
|
||||
internal StackFrame GetStackFrame(string function, string file, int lineNumber)
|
||||
{
|
||||
var frame = new StackFrame { Function = function, File = file, Line = lineNumber };
|
||||
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return frame;
|
||||
}
|
||||
|
||||
IEnumerable<string> lines = null;
|
||||
if (File.Exists(file))
|
||||
{
|
||||
var code = File.ReadLines(file);
|
||||
ReadFrameContent(frame, code, lineNumber, lineNumber);
|
||||
lines = File.ReadLines(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle relative paths and embedded files
|
||||
var fileInfo = _fileProvider.GetFileInfo(file);
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
// ReadLines doesn't accept a stream. Use ReadLines as its more efficient
|
||||
// relative to reading lines via stream reader
|
||||
if (!string.IsNullOrEmpty(fileInfo.PhysicalPath))
|
||||
{
|
||||
lines = File.ReadLines(fileInfo.PhysicalPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
lines = ReadLines(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lines != null)
|
||||
{
|
||||
ReadFrameContent(frame, lines, lineNumber, lineNumber);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
private void ReadFrameContent(StackFrame frame,
|
||||
IEnumerable<string> code,
|
||||
int startLineNumber,
|
||||
int endLineNumber)
|
||||
// make it internal to enable unit testing
|
||||
internal void ReadFrameContent(
|
||||
StackFrame frame,
|
||||
IEnumerable<string> allLines,
|
||||
int errorStartLineNumberInFile,
|
||||
int errorEndLineNumberInFile)
|
||||
{
|
||||
frame.PreContextLine = Math.Max(startLineNumber - _options.SourceCodeLineCount, 1);
|
||||
frame.PreContextCode = code.Skip(frame.PreContextLine - 1).Take(startLineNumber - frame.PreContextLine).ToArray();
|
||||
frame.ContextCode = code.Skip(startLineNumber - 1).Take(1 + Math.Max(0, endLineNumber - startLineNumber));
|
||||
frame.PostContextCode = code.Skip(startLineNumber).Take(_options.SourceCodeLineCount).ToArray();
|
||||
// Get the line boundaries in the file to be read and read all these lines at once into an array.
|
||||
var preErrorLineNumberInFile = Math.Max(errorStartLineNumberInFile - _options.SourceCodeLineCount, 1);
|
||||
var postErrorLineNumberInFile = errorEndLineNumberInFile + _options.SourceCodeLineCount;
|
||||
var codeBlock = allLines
|
||||
.Skip(preErrorLineNumberInFile - 1)
|
||||
.Take(postErrorLineNumberInFile - preErrorLineNumberInFile + 1)
|
||||
.ToArray();
|
||||
|
||||
var numOfErrorLines = (errorEndLineNumberInFile - errorStartLineNumberInFile) + 1;
|
||||
var errorStartLineNumberInArray = errorStartLineNumberInFile - preErrorLineNumberInFile;
|
||||
|
||||
frame.PreContextLine = preErrorLineNumberInFile;
|
||||
frame.PreContextCode = codeBlock.Take(errorStartLineNumberInArray).ToArray();
|
||||
frame.ContextCode = codeBlock
|
||||
.Skip(errorStartLineNumberInArray)
|
||||
.Take(numOfErrorLines)
|
||||
.ToArray();
|
||||
frame.PostContextCode = codeBlock
|
||||
.Skip(errorStartLineNumberInArray + numOfErrorLines)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ReadLines(IFileInfo fileInfo)
|
||||
{
|
||||
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Chunk
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNet.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -23,5 +25,13 @@ namespace Microsoft.AspNet.Diagnostics
|
|||
/// source code referenced by the exception stack trace is present on the server.
|
||||
/// </summary>
|
||||
public int SourceCodeLineCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides files containing source code used to display contextual information of an exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <c>null</c> <see cref="ErrorPageMiddleware" /> will use a <see cref="PhysicalFileProvider"/>.
|
||||
/// </remarks>
|
||||
public IFileProvider FileProvider { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,8 +143,8 @@ using Views
|
|||
#line hidden
|
||||
|
||||
WriteLiteral(" <li class=\"frame\"");
|
||||
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 1231), Tuple.Create("\"", 1251),
|
||||
Tuple.Create(Tuple.Create("", 1242), Tuple.Create<System.Object, System.Int32>(tabIndex, 1242), false));
|
||||
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 1246), Tuple.Create("\"", 1266),
|
||||
Tuple.Create(Tuple.Create("", 1257), Tuple.Create<System.Object, System.Int32>(tabIndex, 1257), false));
|
||||
WriteLiteral(">\r\n");
|
||||
#line 42 "CompilationErrorPage.cshtml"
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ using Views
|
|||
#line hidden
|
||||
|
||||
#line 48 "CompilationErrorPage.cshtml"
|
||||
if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
|
||||
if (frame.Line != 0 && frame.ContextCode.Any())
|
||||
{
|
||||
|
||||
#line default
|
||||
|
|
@ -207,15 +207,15 @@ using Views
|
|||
#line hidden
|
||||
|
||||
#line 51 "CompilationErrorPage.cshtml"
|
||||
if (frame.PreContextCode != null)
|
||||
if (frame.PreContextCode.Any())
|
||||
{
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
WriteLiteral(" <ol");
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 1777), Tuple.Create("\"", 1806),
|
||||
Tuple.Create(Tuple.Create("", 1785), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 1785), false));
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 1790), Tuple.Create("\"", 1819),
|
||||
Tuple.Create(Tuple.Create("", 1798), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 1798), false));
|
||||
WriteLiteral(" class=\"collapsible\">\r\n");
|
||||
#line 54 "CompilationErrorPage.cshtml"
|
||||
|
||||
|
|
@ -251,8 +251,8 @@ using Views
|
|||
#line hidden
|
||||
|
||||
WriteLiteral(" <ol");
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 2187), Tuple.Create("\"", 2206),
|
||||
Tuple.Create(Tuple.Create("", 2195), Tuple.Create<System.Object, System.Int32>(frame.Line, 2195), false));
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 2200), Tuple.Create("\"", 2219),
|
||||
Tuple.Create(Tuple.Create("", 2208), Tuple.Create<System.Object, System.Int32>(frame.Line, 2208), false));
|
||||
WriteLiteral(" class=\"highlight\">\r\n");
|
||||
#line 61 "CompilationErrorPage.cshtml"
|
||||
|
||||
|
|
@ -288,15 +288,15 @@ using Views
|
|||
#line hidden
|
||||
|
||||
#line 66 "CompilationErrorPage.cshtml"
|
||||
if (frame.PostContextCode != null)
|
||||
if (frame.PostContextCode.Any())
|
||||
{
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
WriteLiteral(" <ol");
|
||||
WriteAttribute("start", Tuple.Create(" start=\'", 2635), Tuple.Create("\'", 2660),
|
||||
Tuple.Create(Tuple.Create("", 2643), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 2643), false));
|
||||
WriteAttribute("start", Tuple.Create(" start=\'", 2646), Tuple.Create("\'", 2671),
|
||||
Tuple.Create(Tuple.Create("", 2654), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 2654), false));
|
||||
WriteLiteral(" class=\"collapsible\">\r\n");
|
||||
#line 69 "CompilationErrorPage.cshtml"
|
||||
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@
|
|||
<h3>@frame.ErrorDetails</h3>
|
||||
}
|
||||
|
||||
@if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
|
||||
@if (frame.Line != 0 && frame.ContextCode.Any())
|
||||
{
|
||||
<div class="source">
|
||||
@if (frame.PreContextCode != null)
|
||||
@if (frame.PreContextCode.Any())
|
||||
{
|
||||
<ol start="@frame.PreContextLine" class="collapsible">
|
||||
@foreach (var line in frame.PreContextCode)
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<li><span>@line</span></li>
|
||||
}
|
||||
</ol>
|
||||
@if (frame.PostContextCode != null)
|
||||
@if (frame.PostContextCode.Any())
|
||||
{
|
||||
<ol start='@(frame.Line + 1)' class="collapsible">
|
||||
@foreach (var line in frame.PostContextCode)
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ using Views
|
|||
#line hidden
|
||||
|
||||
WriteLiteral("\r\n<!DOCTYPE html>\r\n<html");
|
||||
WriteAttribute("lang", Tuple.Create(" lang=\"", 518), Tuple.Create("\"", 579),
|
||||
Tuple.Create(Tuple.Create("", 525), Tuple.Create<System.Object, System.Int32>(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, 525), false));
|
||||
WriteAttribute("lang", Tuple.Create(" lang=\"", 533), Tuple.Create("\"", 594),
|
||||
Tuple.Create(Tuple.Create("", 540), Tuple.Create<System.Object, System.Int32>(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, 540), false));
|
||||
WriteLiteral(" xmlns=\"http://www.w3.org/1999/xhtml\">\r\n <head>\r\n <meta charset=\"utf-8\"" +
|
||||
" />\r\n <title>");
|
||||
#line 26 "ErrorPage.cshtml"
|
||||
|
|
@ -147,8 +147,8 @@ using Views
|
|||
#line default
|
||||
#line hidden
|
||||
WriteLiteral(" in <code");
|
||||
WriteAttribute("title", Tuple.Create(" title=\"", 1895), Tuple.Create("\"", 1919),
|
||||
Tuple.Create(Tuple.Create("", 1903), Tuple.Create<System.Object, System.Int32>(firstFrame.File, 1903), false));
|
||||
WriteAttribute("title", Tuple.Create(" title=\"", 1910), Tuple.Create("\"", 1934),
|
||||
Tuple.Create(Tuple.Create("", 1918), Tuple.Create<System.Object, System.Int32>(firstFrame.File, 1918), false));
|
||||
WriteLiteral(">");
|
||||
#line 50 "ErrorPage.cshtml"
|
||||
Write(System.IO.Path.GetFileName(firstFrame.File));
|
||||
|
|
@ -281,8 +281,8 @@ using Views
|
|||
#line hidden
|
||||
|
||||
WriteLiteral(" <li class=\"frame\"");
|
||||
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 3327), Tuple.Create("\"", 3347),
|
||||
Tuple.Create(Tuple.Create("", 3338), Tuple.Create<System.Object, System.Int32>(tabIndex, 3338), false));
|
||||
WriteAttribute("tabindex", Tuple.Create(" tabindex=\"", 3342), Tuple.Create("\"", 3362),
|
||||
Tuple.Create(Tuple.Create("", 3353), Tuple.Create<System.Object, System.Int32>(tabIndex, 3353), false));
|
||||
WriteLiteral(">\r\n");
|
||||
#line 87 "ErrorPage.cshtml"
|
||||
|
||||
|
|
@ -332,8 +332,8 @@ using Views
|
|||
#line default
|
||||
#line hidden
|
||||
WriteLiteral(" in <code");
|
||||
WriteAttribute("title", Tuple.Create(" title=\"", 3742), Tuple.Create("\"", 3761),
|
||||
Tuple.Create(Tuple.Create("", 3750), Tuple.Create<System.Object, System.Int32>(frame.File, 3750), false));
|
||||
WriteAttribute("title", Tuple.Create(" title=\"", 3757), Tuple.Create("\"", 3776),
|
||||
Tuple.Create(Tuple.Create("", 3765), Tuple.Create<System.Object, System.Int32>(frame.File, 3765), false));
|
||||
WriteLiteral(">");
|
||||
#line 94 "ErrorPage.cshtml"
|
||||
Write(System.IO.Path.GetFileName(frame.File));
|
||||
|
|
@ -355,7 +355,7 @@ using Views
|
|||
#line hidden
|
||||
|
||||
#line 97 "ErrorPage.cshtml"
|
||||
if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
|
||||
if (frame.Line != 0 && frame.ContextCode.Any())
|
||||
{
|
||||
|
||||
#line default
|
||||
|
|
@ -369,15 +369,15 @@ using Views
|
|||
#line hidden
|
||||
|
||||
#line 100 "ErrorPage.cshtml"
|
||||
if (frame.PreContextCode != null)
|
||||
if (frame.PreContextCode.Any())
|
||||
{
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
WriteLiteral(" <ol");
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 4194), Tuple.Create("\"", 4223),
|
||||
Tuple.Create(Tuple.Create("", 4202), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 4202), false));
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 4207), Tuple.Create("\"", 4236),
|
||||
Tuple.Create(Tuple.Create("", 4215), Tuple.Create<System.Object, System.Int32>(frame.PreContextLine, 4215), false));
|
||||
WriteLiteral(" class=\"collapsible\">\r\n");
|
||||
#line 103 "ErrorPage.cshtml"
|
||||
|
||||
|
|
@ -413,8 +413,8 @@ using Views
|
|||
#line hidden
|
||||
|
||||
WriteLiteral("\r\n <ol");
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 4663), Tuple.Create("\"", 4682),
|
||||
Tuple.Create(Tuple.Create("", 4671), Tuple.Create<System.Object, System.Int32>(frame.Line, 4671), false));
|
||||
WriteAttribute("start", Tuple.Create(" start=\"", 4676), Tuple.Create("\"", 4695),
|
||||
Tuple.Create(Tuple.Create("", 4684), Tuple.Create<System.Object, System.Int32>(frame.Line, 4684), false));
|
||||
WriteLiteral(" class=\"highlight\">\r\n");
|
||||
#line 111 "ErrorPage.cshtml"
|
||||
|
||||
|
|
@ -450,15 +450,15 @@ using Views
|
|||
#line hidden
|
||||
|
||||
#line 117 "ErrorPage.cshtml"
|
||||
if (frame.PostContextCode != null)
|
||||
if (frame.PostContextCode.Any())
|
||||
{
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
WriteLiteral(" <ol");
|
||||
WriteAttribute("start", Tuple.Create(" start=\'", 5177), Tuple.Create("\'", 5202),
|
||||
Tuple.Create(Tuple.Create("", 5185), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 5185), false));
|
||||
WriteAttribute("start", Tuple.Create(" start=\'", 5188), Tuple.Create("\'", 5213),
|
||||
Tuple.Create(Tuple.Create("", 5196), Tuple.Create<System.Object, System.Int32>(frame.Line + 1, 5196), false));
|
||||
WriteLiteral(" class=\"collapsible\">\r\n");
|
||||
#line 120 "ErrorPage.cshtml"
|
||||
|
||||
|
|
|
|||
|
|
@ -94,10 +94,10 @@
|
|||
<h3>@frame.Function in <code title="@frame.File">@System.IO.Path.GetFileName(frame.File)</code></h3>
|
||||
}
|
||||
|
||||
@if (frame.Line != 0 && frame.ContextCode !=null && frame.ContextCode.Any())
|
||||
@if (frame.Line != 0 && frame.ContextCode.Any())
|
||||
{
|
||||
<div class="source">
|
||||
@if (frame.PreContextCode != null)
|
||||
@if (frame.PreContextCode.Any())
|
||||
{
|
||||
<ol start="@frame.PreContextLine" class="collapsible">
|
||||
@foreach (var line in frame.PreContextCode)
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
}
|
||||
</ol>
|
||||
|
||||
@if (frame.PostContextCode != null)
|
||||
@if (frame.PostContextCode.Any())
|
||||
{
|
||||
<ol start='@(frame.Line + 1)' class="collapsible">
|
||||
@foreach (var line in frame.PostContextCode)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Diagnostics.Views
|
||||
{
|
||||
|
|
@ -32,19 +33,19 @@ namespace Microsoft.AspNet.Diagnostics.Views
|
|||
public int PreContextLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Lines of code before the actual error line(s).
|
||||
/// </summary>
|
||||
public IEnumerable<string> PreContextCode { get; set; }
|
||||
public IEnumerable<string> PreContextCode { get; set; } = Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Line(s) of code responsible for the error.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ContextCode { get; set; }
|
||||
public IEnumerable<string> ContextCode { get; set; } = Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Lines of code after the actual error line(s).
|
||||
/// </summary>
|
||||
public IEnumerable<string> PostContextCode { get; set; }
|
||||
public IEnumerable<string> PostContextCode { get; set; } = Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Specific error details for this stack frame.
|
||||
|
|
|
|||
|
|
@ -7,13 +7,17 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Diagnostics.Abstractions": "1.0.0-*",
|
||||
"Microsoft.AspNet.FileProviders.Physical": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.WebUtilities": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
|
||||
"Microsoft.Framework.NotNullAttribute.Sources": {
|
||||
"type": "build",
|
||||
"version": "1.0.0-*"
|
||||
},
|
||||
"Microsoft.Framework.OptionsModel": "1.0.0-*",
|
||||
"Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" },
|
||||
"Microsoft.Framework.WebEncoders.Core": "1.0.0-*",
|
||||
"Microsoft.Framework.Runtime.Compilation.Abstractions": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*"
|
||||
"Microsoft.Framework.WebEncoders.Core": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": {},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,451 @@
|
|||
// 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.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Diagnostics.Views;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.Framework.Caching;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.Runtime;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Diagnostics
|
||||
{
|
||||
public class ErrorPageMiddlewareTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("TestFiles/SourceFile.txt")]
|
||||
[InlineData(@"TestFiles\SourceFile.txt")]
|
||||
public void UsesDefaultFileProvider_IfNotProvidedOnOptions(string relativePath)
|
||||
{
|
||||
// Arrange & Act
|
||||
var middleware = GetErrorPageMiddleware(fileProvider: null);
|
||||
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
|
||||
|
||||
// Assert
|
||||
// Lines 4-16 (inclusive) is the code block
|
||||
Assert.Equal(4, stackFrame.PreContextLine);
|
||||
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
||||
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
||||
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
||||
}
|
||||
|
||||
public static TheoryData<string> AbsolutePathsData
|
||||
{
|
||||
get
|
||||
{
|
||||
var rootPath = Directory.GetCurrentDirectory();
|
||||
return new TheoryData<string>()
|
||||
{
|
||||
Path.Combine(rootPath, "TestFiles/SourceFile.txt"),
|
||||
Path.Combine(rootPath, @"TestFiles\SourceFile.txt")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AbsolutePathsData))]
|
||||
public void DisplaysSourceCodeLines_ForAbsolutePaths(string absoluteFilePath)
|
||||
{
|
||||
// Arrange
|
||||
var rootPath = Directory.GetCurrentDirectory();
|
||||
// PhysicalFileProvider handles only relative paths but we fall back to work with absolute paths too
|
||||
var provider = new PhysicalFileProvider(rootPath);
|
||||
|
||||
// Act
|
||||
var middleware = GetErrorPageMiddleware(provider);
|
||||
var stackFrame = middleware.GetStackFrame("func1", absoluteFilePath, lineNumber: 10);
|
||||
|
||||
// Assert
|
||||
// Lines 4-16 (inclusive) is the code block
|
||||
Assert.Equal(4, stackFrame.PreContextLine);
|
||||
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
||||
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
||||
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("TestFiles/SourceFile.txt")]
|
||||
[InlineData(@"TestFiles\SourceFile.txt")]
|
||||
public void DisplaysSourceCodeLines_ForRelativePaths(string relativePath)
|
||||
{
|
||||
// Arrange
|
||||
var rootPath = Directory.GetCurrentDirectory();
|
||||
var provider = new PhysicalFileProvider(rootPath);
|
||||
|
||||
// Act
|
||||
var middleware = GetErrorPageMiddleware(provider);
|
||||
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
|
||||
|
||||
// Assert
|
||||
// Lines 4-16 (inclusive) is the code block
|
||||
Assert.Equal(4, stackFrame.PreContextLine);
|
||||
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
||||
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
||||
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("TestFiles/EmbeddedSourceFile.txt")]
|
||||
//[InlineData(@"TestFiles\EmbeddedSourceFile.txt")]
|
||||
public void DisplaysSourceCodeLines_ForRelativeEmbeddedPaths(string relativePath)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EmbeddedFileProvider(
|
||||
GetType().GetTypeInfo().Assembly,
|
||||
baseNamespace: $"{typeof(ErrorPageMiddlewareTest).GetTypeInfo().Assembly.GetName().Name}.Resources");
|
||||
|
||||
// Act
|
||||
var middleware = GetErrorPageMiddleware(provider);
|
||||
var stackFrame = middleware.GetStackFrame("func1", relativePath, lineNumber: 10);
|
||||
|
||||
// Assert
|
||||
// Lines 4-16 (inclusive) is the code block
|
||||
Assert.Equal(4, stackFrame.PreContextLine);
|
||||
Assert.Equal(GetCodeLines(4, 9), stackFrame.PreContextCode);
|
||||
Assert.Equal(GetCodeLines(10, 10), stackFrame.ContextCode);
|
||||
Assert.Equal(GetCodeLines(11, 16), stackFrame.PostContextCode);
|
||||
}
|
||||
|
||||
public static TheoryData<ErrorData> DisplaysSourceCodeLines_PreAndPostErrorLineData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<ErrorData>()
|
||||
{
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 30),
|
||||
ErrorStartLine = 10,
|
||||
ErrorEndLine = 10,
|
||||
ExpectedPreContextLine = 4,
|
||||
ExpectedPreErrorCode = GetCodeLines(4, 9),
|
||||
ExpectedErrorCode = GetCodeLines(10, 10),
|
||||
ExpectedPostErrorCode = GetCodeLines(11, 16)
|
||||
},
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 30),
|
||||
ErrorStartLine = 10,
|
||||
ErrorEndLine = 13, // multi-line error
|
||||
ExpectedPreContextLine = 4,
|
||||
ExpectedPreErrorCode = GetCodeLines(4, 9),
|
||||
ExpectedErrorCode = GetCodeLines(10, 13),
|
||||
ExpectedPostErrorCode = GetCodeLines(14, 19)
|
||||
},
|
||||
|
||||
// PreErrorCode less than source code line count
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 10),
|
||||
ErrorStartLine = 1,
|
||||
ErrorEndLine = 1,
|
||||
ExpectedPreContextLine = 1,
|
||||
ExpectedPreErrorCode = Enumerable.Empty<string>(),
|
||||
ExpectedErrorCode = GetCodeLines(1, 1),
|
||||
ExpectedPostErrorCode = GetCodeLines(2, 7)
|
||||
},
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 10),
|
||||
ErrorStartLine = 3,
|
||||
ErrorEndLine = 5,
|
||||
ExpectedPreContextLine = 1,
|
||||
ExpectedPreErrorCode = GetCodeLines(1, 2),
|
||||
ExpectedErrorCode = GetCodeLines(3, 5),
|
||||
ExpectedPostErrorCode = GetCodeLines(6, 10)
|
||||
},
|
||||
|
||||
// PostErrorCode less than source code line count
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 10),
|
||||
ErrorStartLine = 10,
|
||||
ErrorEndLine = 10,
|
||||
ExpectedPreContextLine = 4,
|
||||
ExpectedPreErrorCode = GetCodeLines(4, 9),
|
||||
ExpectedErrorCode = GetCodeLines(10, 10),
|
||||
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
||||
},
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 10),
|
||||
ErrorStartLine = 7,
|
||||
ErrorEndLine = 10,
|
||||
ExpectedPreContextLine = 1,
|
||||
ExpectedPreErrorCode = GetCodeLines(1, 6),
|
||||
ExpectedErrorCode = GetCodeLines(7, 10),
|
||||
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
||||
},
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 10),
|
||||
ErrorStartLine = 5,
|
||||
ErrorEndLine = 8,
|
||||
ExpectedPreContextLine = 1,
|
||||
ExpectedPreErrorCode = GetCodeLines(1, 4),
|
||||
ExpectedErrorCode = GetCodeLines(5, 8),
|
||||
ExpectedPostErrorCode = GetCodeLines(9, 10)
|
||||
},
|
||||
|
||||
// Pre and Post error code less than source code line count
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 4),
|
||||
ErrorStartLine = 2,
|
||||
ErrorEndLine = 3,
|
||||
ExpectedPreContextLine = 1,
|
||||
ExpectedPreErrorCode = GetCodeLines(1, 1),
|
||||
ExpectedErrorCode = GetCodeLines(2, 3),
|
||||
ExpectedPostErrorCode = GetCodeLines(4, 4)
|
||||
},
|
||||
new ErrorData()
|
||||
{
|
||||
AllLines = GetCodeLines(1, 4),
|
||||
ErrorStartLine = 1,
|
||||
ErrorEndLine = 4,
|
||||
ExpectedPreContextLine = 1,
|
||||
ExpectedPreErrorCode = Enumerable.Empty<string>(),
|
||||
ExpectedErrorCode = GetCodeLines(1, 4),
|
||||
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
||||
},
|
||||
|
||||
// change source code line count
|
||||
new ErrorData()
|
||||
{
|
||||
SourceCodeLineCount = 1,
|
||||
AllLines = GetCodeLines(1, 1),
|
||||
ErrorStartLine = 1,
|
||||
ErrorEndLine = 1,
|
||||
ExpectedPreContextLine = 1,
|
||||
ExpectedPreErrorCode = Enumerable.Empty<string>(),
|
||||
ExpectedErrorCode = GetCodeLines(1, 1),
|
||||
ExpectedPostErrorCode = Enumerable.Empty<string>()
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DisplaysSourceCodeLines_PreAndPostErrorLineData))]
|
||||
public void DisplaysSourceCodeLines_PreAndPostErrorLine(ErrorData errorData)
|
||||
{
|
||||
// Arrange
|
||||
var middleware = GetErrorPageMiddleware();
|
||||
var stackFrame = new StackFrame();
|
||||
|
||||
// Act
|
||||
middleware.ReadFrameContent(
|
||||
stackFrame, errorData.AllLines, errorData.ErrorStartLine, errorData.ErrorEndLine);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(errorData.ExpectedPreContextLine, stackFrame.PreContextLine);
|
||||
Assert.Equal(errorData.ExpectedPreErrorCode, stackFrame.PreContextCode);
|
||||
Assert.Equal(errorData.ExpectedErrorCode, stackFrame.ContextCode);
|
||||
Assert.Equal(errorData.ExpectedPostErrorCode, stackFrame.PostContextCode);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetCodeLines(int fromLine, int toLine)
|
||||
{
|
||||
var start = fromLine;
|
||||
var count = toLine - fromLine + 1;
|
||||
return Enumerable.Range(start, count).Select(i => string.Format("Line{0}", i));
|
||||
}
|
||||
|
||||
private ErrorPageMiddleware GetErrorPageMiddleware(
|
||||
IFileProvider fileProvider = null, int sourceCodeLineCount = 6)
|
||||
{
|
||||
var errorPageOptions = new ErrorPageOptions();
|
||||
errorPageOptions.SourceCodeLineCount = sourceCodeLineCount;
|
||||
|
||||
if (fileProvider != null)
|
||||
{
|
||||
errorPageOptions.FileProvider = fileProvider;
|
||||
}
|
||||
|
||||
var middleware = new ErrorPageMiddleware(
|
||||
(httpContext) => { return Task.FromResult(0); },
|
||||
errorPageOptions,
|
||||
new LoggerFactory(),
|
||||
new TestApplicationEnvironment());
|
||||
|
||||
return middleware;
|
||||
}
|
||||
|
||||
private class TestApplicationEnvironment : IApplicationEnvironment
|
||||
{
|
||||
public string ApplicationBasePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Directory.GetCurrentDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
public string ApplicationName
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public string ApplicationVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public string Configuration
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public FrameworkName RuntimeFramework
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public string Version
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public object GetData(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetData(string name, object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestFileProvider : IFileProvider
|
||||
{
|
||||
private readonly IEnumerable<string> _sourceCodeLines;
|
||||
|
||||
public TestFileProvider(IEnumerable<string> sourceCodeLines)
|
||||
{
|
||||
_sourceCodeLines = sourceCodeLines;
|
||||
}
|
||||
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
{
|
||||
return new TestFileInfo(_sourceCodeLines);
|
||||
}
|
||||
|
||||
public IExpirationTrigger Watch(string filter)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestFileInfo : IFileInfo
|
||||
{
|
||||
private readonly MemoryStream _stream;
|
||||
|
||||
public TestFileInfo(IEnumerable<string> sourceCodeLines)
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
using (var writer = new StreamWriter(_stream, Encoding.UTF8, 1024, leaveOpen: true))
|
||||
{
|
||||
foreach (var line in sourceCodeLines)
|
||||
{
|
||||
writer.WriteLine(line);
|
||||
}
|
||||
}
|
||||
_stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
public bool Exists
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset LastModified
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public string PhysicalPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream CreateReadStream()
|
||||
{
|
||||
return _stream;
|
||||
}
|
||||
}
|
||||
|
||||
public class ErrorData
|
||||
{
|
||||
public int SourceCodeLineCount { get; set; } = 6;
|
||||
public IEnumerable<string> AllLines { get; set; }
|
||||
public int ErrorStartLine { get; set; }
|
||||
public int ErrorEndLine { get; set; }
|
||||
public int ExpectedPreContextLine { get; set; }
|
||||
public IEnumerable<string> ExpectedPreErrorCode { get; set; }
|
||||
public IEnumerable<string> ExpectedErrorCode { get; set; }
|
||||
public IEnumerable<string> ExpectedPostErrorCode { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
Line1
|
||||
Line2
|
||||
Line3
|
||||
Line4
|
||||
Line5
|
||||
Line6
|
||||
Line7
|
||||
Line8
|
||||
Line9
|
||||
Line10
|
||||
Line11
|
||||
Line12
|
||||
Line13
|
||||
Line14
|
||||
Line15
|
||||
Line16
|
||||
Line17
|
||||
Line18
|
||||
Line19
|
||||
Line20
|
||||
Line21
|
||||
Line22
|
||||
Line23
|
||||
Line24
|
||||
Line25
|
||||
Line26
|
||||
Line27
|
||||
Line28
|
||||
Line29
|
||||
Line30
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
Line1
|
||||
Line2
|
||||
Line3
|
||||
Line4
|
||||
Line5
|
||||
Line6
|
||||
Line7
|
||||
Line8
|
||||
Line9
|
||||
Line10
|
||||
Line11
|
||||
Line12
|
||||
Line13
|
||||
Line14
|
||||
Line15
|
||||
Line16
|
||||
Line17
|
||||
Line18
|
||||
Line19
|
||||
Line20
|
||||
Line21
|
||||
Line22
|
||||
Line23
|
||||
Line24
|
||||
Line25
|
||||
Line26
|
||||
Line27
|
||||
Line28
|
||||
Line29
|
||||
Line30
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Diagnostics.Elm": "1.0.0-*",
|
||||
"Microsoft.AspNet.FileProviders.Embedded": "1.0.0-*",
|
||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||
"xunit.runner.aspnet": "2.0.0-aspnet-*"
|
||||
|
|
@ -18,7 +19,10 @@
|
|||
"dnxcore50": {
|
||||
}
|
||||
},
|
||||
|
||||
"commands": {
|
||||
"test": "xunit.runner.aspnet"
|
||||
}
|
||||
},
|
||||
|
||||
"resource": [ "Resources/**" ]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue