Tracing in ASP.NET MVC Razor Views

Tom Dykstra - MSFT

System.Diagnostics.Trace can be a useful tool for logging and debugging, and sometimes it would be handy to be able to call one of the tracing methods from a Razor view.

For example, when an MVC application runs in production, by default MVC catches most application exceptions for you and routes them to Views/Shared/Error.cshtml. That view doesn’t show error details for security reasons, so you end up with the not-very-helpful message “An error occurred while processing your request.”

image

Running locally you get detailed error info, or you can catch exceptions by running in debug mode, but what to do about errors that happen in production? 

If you can re-create the error in production, you can temporarily tell the site to go ahead and display detailed error information by adding a customErrors element to your Web.config file in the system.web element:

Code Snippet
  1. <system.web>
  2.   <customErrors mode="Off" defaultRedirect="Error" />

Now instead of “An error occurred” you get an actual error message and a stack trace.

image

But another quick-and-dirty alternative that doesn’t require temporarily exposing error details to the public, is to add tracing statements to the view, for example:

Code Snippet
  1. @using System.Diagnostics
  2.  
  3. @model System.Web.Mvc.HandleErrorInfo
  4.  
  5. @{
  6.     ViewBag.Title = "Error";
  7.     var message = string.Format("Error in Controller {0}, Action method {1}. Exception: {2}",
  8.         Model.ControllerName, Model.ActionName, Model.Exception.Message);
  9.     if (Model.Exception.InnerException != null)
  10.     {
  11.         message += "; Inner exception: " + Model.Exception.InnerException.Message;
  12.     }
  13.     Trace.TraceError(message);    
  14. }
  15.  
  16. <hgroup class="title">
  17.     <h1 class="error">Error.</h1>
  18.     <h2 class="error">An error occurred while processing your request.</h2>
  19. </hgroup>

Unfortunately, when you do this you don’t get any trace output, and if you step through the code in the debugger you’ll see it step right over the TraceError method call without executing it.

Trace statements require the TRACE compiler constant, but it’s on by default and you can verify that in the Build tab of the project properties window:

image

The problem is that this setting in the .csproj file only applies to .cs files.  ASP.NET uses a different compile process for .cshtml files (or .aspx files in Web Forms), and the settings for that compile process are in the Web.config file. If you don’t explicitly specify the TRACE constant there, tracing method calls in .cshtml views are ignored.

You have at least two options for dealing with this situation if you want to add tracing in a view: define a TRACE constant for the .cshtml compiler, or use a static helper method.

Below is an example of what you have to add to the application Web.config file for a Visual Studio project that targets .NET 4.5, in order to define a TRACE constant for the .cshtml compiler:

Code Snippet
  1. <system.codedom>
  2.   <compilers>
  3.     <compiler
  4.       language="c#;cs;csharp"
  5.       extension=".cs"
  6.       type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  7.       compilerOptions="/define:TRACE"
  8.       warningLevel="1" />
  9.   </compilers>
  10. </system.codedom>

With this in place the TraceError method call gets executed.

If you’re using a different Visual Studio or ASP.NET version, you can get the Version number you need by looking at your root Web.config file, copying out the codedom element from there, and adding the compilerOptions setting. The root Web.config file for .NET 4 or 4.5 is located in C:WindowsMicrosoft.NETFramework64v4.0.30319Configweb.config:

Code Snippet
  1. <system.codedom>
  2.     <compilers>
  3.         <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  4.             <providerOption name="CompilerVersion" value="v4.0"/>
  5.             <providerOption name="WarnAsError" value="false"/>
  6.         </compiler>
  7.         <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" warningLevel="4" type="Microsoft.VisualBasic.VBCodeProvider, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  8.             <providerOption name="CompilerVersion" value="v4.0"/>
  9.             <providerOption name="OptionInfer" value="true"/>
  10.             <providerOption name="WarnAsError" value="false"/>
  11.         </compiler>
  12.     </compilers>
  13. </system.codedom>

  

If you’d rather use a static trace helper, create a class like the following example:

Code Snippet
  1. using System.Diagnostics;
  2.  
  3. namespace MyExample
  4. {
  5.     public static class TraceHelper
  6.     {
  7.         public static void MyTrace(TraceLevel level, string message)
  8.         {
  9.             switch (level)
  10.             {
  11.                 case TraceLevel.Error:
  12.                     Trace.TraceError(message);
  13.                     break;
  14.                 case TraceLevel.Warning:
  15.                     Trace.TraceWarning(message);
  16.                     break;
  17.                 case TraceLevel.Info:
  18.                     Trace.TraceInformation(message);
  19.                     break;
  20.                 case TraceLevel.Verbose:
  21.                     Trace.WriteLine(message);
  22.                     break;
  23.             }
  24.         }
  25.     }
  26. }

Now you can use virtually the same code in your view without having to add a codeDom element to your Web.config:

Code Snippet
  1. @using MyExample
  2. @using System.Diagnostics
  3.  
  4. @model System.Web.Mvc.HandleErrorInfo
  5.  
  6. @{
  7.     ViewBag.Title = "Error";
  8.     var message = string.Format("Error in Controller {0}, Action method {1}. Exception: {2}",
  9.         Model.ControllerName, Model.ActionName, Model.Exception.Message);
  10.     if (Model.Exception.InnerException != null)
  11.     {
  12.         message += "; Inner exception: " + Model.Exception.InnerException.Message;
  13.     }
  14.     TraceHelper.MyTrace(TraceLevel.Error, message);
  15. }
  16.  
  17. <hgroup class="title">
  18.     <h1 class="error">Error.</h1>
  19.     <h2 class="error">An error occurred while processing your request.</h2>
  20. </hgroup>

There are many ways to get the trace output, and I’ve added some links for info about that at the end of this post.

Adding tracing to views might be an effective way to debug a problem in production, and I’ve used Error.cshtml to show an example of how to do that. If your goal is a more permanent method of logging unhandled errors, a better method is to use an exception filter. To do that you create a class that implements IExceptionFilter, such as the following example:

Code Snippet
  1. using System.Web.Mvc;
  2.  
  3. namespace MyExample
  4. {
  5.     public class ErrorLoggerFilter : IExceptionFilter
  6.    {
  7.         public void OnException (ExceptionContext context)
  8.         {
  9.             var message = string.Format("Error processing URL: {0}. Exception: {1}",
  10.                 context.HttpContext.Request.Url,  context.Exception.Message);
  11.             if (context.Exception.InnerException != null)
  12.             {
  13.                 message += "; Inner exception: " + context.Exception.InnerException.Message;
  14.             }
  15.             context.HttpContext.Trace.Write(message);
  16.             System.Diagnostics.Trace.TraceError(message);    
  17.         }
  18.     }
  19. }

And then register the filter in App_Start/FilterConfig.cs:

Code Snippet
  1. using System.Web;
  2. using System.Web.Mvc;
  3.  
  4. namespace MyExample
  5. {
  6.     public class FilterConfig
  7.     {
  8.         public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  9.         {
  10.             filters.Add(new HandleErrorAttribute());
  11.             filters.Add(new ErrorLoggerFilter());
  12.         }
  13.     }
  14. }

For another look at tracing best practices for web applications, see 47:00-55:36 in this video: Scott Guthrie: Building Real World Cloud Apps with Windows Azure – Part 2.

If you deploy your application to a Windows Azure Web Site, the latest SDK makes it exceptionally easy to get trace output immediately in the Visual Studio Output window while the site is running.  In two weeks the tutorial I’ve written about this will be published on WindowsAzure.com; until then, see the intro in the ScottGu blog post introducing Windows Azure SDK 2.0.

Some other links:

Thanks to Rick Anderson and Yishai Galatzer for help with the code for this blog post.

0 comments

Discussion is closed.

Feedback usabilla icon