public class Program { public static void Main() { new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureServices(svcs => svcs.AddMvc()) .Configure(app => app .UseDeveloperExceptionPage() .UseMvc()) .Build() .Run(); } } public class HomeController : Controller { [HttpGet("https://www.jb51.net/")] public IActionResult Index() { return View(); } }
根据约定,Action方法Index呈现出来的视图文件对应的路径应该是“~/views/home/index.cshtml”,我们为此在这个路径下创建这个视图文件。为了能够在动态编译过程中出现编译异常,我们在这个视图文件中编写了如下三行代码,Foobar是一个尚未被创建的类型。
@{ var value = new Foobar(); }
当我们利用浏览器访问HomeController的Action方法Index的时候,应用会动态编译目标视图,由于视图文件中使用了一个不曾不定义的类型,动态编译会失败,响应的错误信息会以如图7所示的形式出现在浏览器上。可以看出错误页面显示的内容和结构与前面演示的实例是完全不一样的,我们不仅可以从这个错误页面中得到导致编译失败的视图文件的路径(“Views/Home/Index.cshtml”),还可以直接看到导致编译失败的那一行代码。不仅如此,这个错误页面还直接将参与编译的源代码(不是定义在.cshtml文件中的原始代码,而是经过转换处理生成的C#代码)。毫无疑问,这个如此详尽的错误页面对于相信开发人员的纠错针对是非常有价值的。
一般来说,动态编译的整个过程由两个步骤组成,它先是将源代码(类似于.cshtml这样的模板文件)转换成针对某种.NET语言(比如C#)的代码,然后进一步地编译成IL代码。动态编译过程中抛出的异常类型一般会实现ICompilationException接口。如下面的代码片段所示,该接口值具有一个唯一的属性CompilationFailures,它返回一个元素类型为CompilationFailure的集合。编译失败的相关信息被封装在一个CompilationFailure对象之中,我们可以利用它得到源文件的路径(SourceFilePath)和内容(SourceFileContent),以及源代码转换后交付编译的内容。如果在内容转换过程就已经发生错误,那么SourceFileContent属性可能返回Null。
public interface ICompilationException { IEnumerable<CompilationFailure> CompilationFailures { get; } } public class CompilationFailure { public string SourceFileContent { get; } public string SourceFilePath { get; } public string CompiledContent { get; } public IEnumerable<DiagnosticMessage> Messages { get; } … }
CompilationFailure类型还具有一个名为Messages的只读属性,它返回一个元素类型为DiagnosticMessage的集合,一个DiagnosticMessage对象承载着一些描述编译错误的诊断信息。我们不仅可以借助DiagnosticMessage对象的相关属性得到描述编译错误的消息(Message和FormattedMessage),还可以得到发生编译错误所在源文件的路径(SourceFilePath)以及范围,StartLine、StartColumn、EndLine和EndColumn属性分别表示导致编译错误的源代码在源文件中开始和结束的行与列(行数和列数分别从1和0开始计数)。
public class DiagnosticMessage { public string SourceFilePath { get; } public int StartLine { get; } public int StartColumn { get; } public int EndLine { get; } public int EndColumn { get; } public string Message { get; } public string FormattedMessage { get; } … }
从上图可以看出,错误页面会直接将导致编译失败的相关源代码显示出来。具体来说,它不仅仅会将直接导致失败的源代码实现出来,还会同时显示前后相邻的源代码。至于相邻源代码应该显示多少行,实际上是通过DeveloperExceptionPageOptions的SourceCodeLineCount属性控制的。
public class Program { public static void Main() { new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureServices(svcs => svcs.AddMvc()) .Configure(app => app .UseDeveloperExceptionPage(new DeveloperExceptionPageOptions { SourceCodeLineCount = 3 }) .UseMvc()) .Build() .Run(); } }
对于前面演示的这个实例来说,如果我们希望前后相邻的三行代码被显示在错误页面上,我们可以采用如上的方式为注册的DeveloperExceptionPageMiddleware中间件指定一个DeveloperExceptionPageOptions对象,并将它的SourceCodeLineCount属性设置为3。与此同时,我们将视图文件(index.cshtml)改写成如下的形式,即在导致编译失败的那一行代码前后分别添加了4行代码。
1: 2: 3: 4: 5:@{ var value = new Foobar();} 6: 7: 8: 9: