I saw a comment the other day on a post about asp.net mvc. The post was old enough that additional comments were no longer being accepted, but I thought the points raised were worth a response.

Today we see there are many view engines. What is the implication of that? They offer a lot of convenience, but you can’t step through them with the debugger. Are they even compiled? It could be that under load you will find that the view engine spends a lot of time interpreting the ’smart’ notation they came up with.

Talking of view engines, this is some spark I saw the other day, I’ll let the code speak for itself:

<test if="expression">
...
<else/>
...
</test>

And the answer is… Heck yeah it’s compiled! You can view the generated source at any time and you can absolutely step through it with a debugger. Lucky.

Let’s take that test/else sample see what it takes to view the generated source.

<div>
  <viewdata unitprice="decimal"/>
  <test if="unitprice [[ 7">
    <p>cheap</p>
    <else/>
    <p>expensive</p>
  </test>
</div>

<var entry="Spark.CompiledViewHolder.Current.Lookup(GeneratedViewId)"/>
<code>${H(entry.Compiler.SourceCode)
  .Replace("\r\n", "<br/>")
  .Replace(" ", "&nbsp;")}</code>

CompiledViewHolder.Current is a singleton instance of the dictionary of compiled view types. If you're using a pre-compiled assembly this dictionary will be filled on startup with descriptor-view pairs, but the SourceCode property will not be available.

Here's what you see on that page.


cheap

namespace PartialFiles.Controllers
{

[global::Spark.SparkViewAttribute(
    TargetNamespace="PartialFiles.Controllers",
    Templates = new string[] {
      "home\\hack.spark"
    })]
public class View676e2dd0cd5443cdbf834d364c67a20f : MvcContrib.SparkViewEngine.SparkView
{

    public override System.Guid GeneratedViewId
    { get { return new System.Guid("676e2dd0cd5443cdbf834d364c67a20f"); } }

    decimal unitprice
    {get {return (decimal)ViewData.Eval("unitprice");}}

    public void RenderViewLevel0()
    {
        Output.Write("<div>");
        if (unitprice < 7)
        {
            Output.Write("\r\n    <p>cheap</p>");
        } // if (unitprice < 7)
        else
        {
            Output.Write("\r\n    <p>expensive</p>");
        }
        Output.Write("\r\n</div>\r\n");
        var entry = Spark.CompiledViewHolder.Current.Lookup(GeneratedViewId);
        Output.Write("\r\n<code>");
        Output.Write(H(entry.Compiler.SourceCode)
  .Replace("\r\n", "<br/>")
  .Replace(" ", "&nbsp;"));
        Output.Write("</code>\r\n\r\n");
    }

    public override void RenderView(System.IO.TextWriter writer)
    {
        using (OutputScope(writer)) {RenderViewLevel0();}
    }
}
}

Output is a TextWriter property of the AbstractSparkView base class. Since there are no layouts or content in this example the TextWriter that will be written to is the actual http Response.Output passed to the engine from the Asp.Net Mvc or Castle MonoRail framework. Combined with the fact we’re looking at a pretty small number of calls to .Write and a csharp if / else statements I don’t know how much more efficient this could become.

To step through the code let’s do two things. First we’ll tell Spark to compile the assemblies with debug symbols.

  <configSections>
    <section name="spark"
           type="Spark.Configuration.SparkSectionHandler, Spark" />
  </configSections>
  <spark>
    <compilation debug="true"/>
  </spark>

Then let’s add two lines to the view file to set a breakpoint.

<div>
  <viewdata unitprice="decimal"/>
  #if (System.Diagnostics.Debugger.IsAttached)
  #  System.Diagnostics.Debugger.Break();
  <test if="unitprice [[ 7">
    <p>cheap</p>

Since the cs is generated you can’t set a normal code breakpoint. Whatcha gonna do? Anyway! F5 to run and we’re off!

To make this breakpoint type of stuff simpler to work with, consider creating the following Views\Shared\_Break.spark file.

<!-- breakpoint -->
#if (System.Diagnostics.Debugger.IsAttached)
#    System.Diagnostics.Debugger.Break();

Now you can put <Break/> elements in any view or partial file in your project.