Razor. Without the MVC (Part 2)

Previous: Razor. Without the MVC (Part 1).
Now reading: Razor. Without the MVC (Part 2).
Previously we’ve looked into how Microsoft’s new Razor view engine technology can be used without MVC. This was all thanks to Ben at BuildStarted.com, who created the initial version thanks to Andrew Nurse’s code and also solved the issue of anonymous models using DynamicObject. It’s nice to see how flexible the code is.

On my previous post, a reader had left a comment asking an obvious question, “where is my @Html support?”

Before I answer this question I think it’s important to know two things;

Razor does NOT support @Html.
Razor generates a class that extends another. In MVC it just happens to create views based on a base type that supports a HtmlHelper property. That’s where the @Html construct originates.
Right from Ben’s original implementation, we were creating a base class from which we were creating our templates. We refactored it originally, but we were still limited to a singular @Model property. Adding support for anonymous objects meant we then required two base templates, one for strict models and one for dynamic models.

So the big refactor began, and it began by supporting a custom base template type. For instance, I could:

public abstract class MyCustomBaseTemplate : TemplateBase
{
public string MyCustomProperty { get; set; }

public string MyCustomMethod()
{
return “Hello World”;
}
}
With the above custom base template, I can now use those custom members:

Every first program starts with @MyCustomMethod()
Now if we look at this in respect to Ben’s previous work with anonymous models, it would mean we essentially need to create two base templates, one for strict models and one for anonymous models. I wanted to see if we could combine two templates into one. So what I did instead was come up with an alternative solution. It begins by refactoring or code to determine ahead of compilation whether the model is going to be dynamic, and if it is, we add a custom attribute to the class that is generated.

if ((modelType != null) && IsAnonymousType(modelType))
generator.GeneratedClass.CustomAttributes.Add(
new CodeAttributeDeclaration(
new CodeTypeReference(typeof(HasDynamicModelAttribute))));
The attribute itself is just a marker. But what we can then do, is change the base template to check for this during construction, and then we can implement dynamic. This allows us to use the same base template for both static and dynamic models.

public abstract class TemplateBase : TemplateBase, ITemplate
{
#region Fields
private object model;
#endregion

#region Constructor /// <summary> /// Initialises a new instance of <see cref="TemplateBase{T}"/> /// </summary> protected TemplateBase() { var type = GetType(); HasDynamicModel = type.GetCustomAttributes(typeof(HasDynamicModelAttribute), true).Any(); } #endregion #region Methods /// <summary> /// Gets whether this template has a dynamic model. /// </summary> protected bool HasDynamicModel { get; private set; } /// <summary> /// Gets or sets the model. /// </summary> public virtual T Model { get { return (T)model; } set { if (HasDynamicModel) model = new RazorDynamicObject { Model = value }; else model = value; } } #endregion
Code language: PHP (php)

}
Now we have that support, let’s get down to the real meat of it! Let’s create a custom base template that supports html helpers. When you create instances of HtmlHelper you need to wire up a few things, a ViewContext and a IViewDataContainer What I’ve done is create a factory class used to create our helper:

internal class HtmlHelperFactory
{
#region Methods
/// /// Creates a for the specified model. ///
/// The model type.
/// The model to create a helper for.
/// The writer used to output html.
/// An instance of .
public HtmlHelper CreateHtmlHelper(T model, TextWriter writer)
{
var container = new InternalViewDataContainer(model);
var context = new ViewContext(
new ControllerContext(),
new InternalView(),
container.ViewData,
new TempDataDictionary(),
writer);

return new HtmlHelper<T>(context, container); } #endregion
Code language: PHP (php)

}
Then we can create our custom base template:

[RequireNamespaces(“System.Web.Mvc.Html”)]
public abstract class HtmlTemplateBase : TemplateBase
{
#region Fields
private readonly HtmlHelperFactory factory = new HtmlHelperFactory();
#endregion

#region Constructor /// <summary> /// Initialises a new instance of <see cref="HtmlTemplateBase{T}"/>. /// </summary> protected HtmlTemplateBase() { CreateHelper(Model); } #endregion #region Properties /// <summary> /// Gets the <see cref="HtmlHelper{T}"/> for this template. /// </summary> public HtmlHelper<T> Html { get; private set; } /// <summary> /// Gets or sets the model. /// </summary> public override T Model { get { return base.Model; } set { base.Model = value; CreateHelper(value); } } #endregion #region Methods /// <summary> /// Creates the required html helper. /// </summary> private void CreateHelper(T model) { Html = factory.CreateHtmlHelper(model, new StringWriter(StringBuilder)); } #endregion
Code language: PHP (php)

}
Now we have our custom base template, we need to change a few more things. Namely changing our Razor (static) class to support setting a custom base template. The problem with throwing all this code into our static class is that it would mean that our custom base class would be for all templates. Let’s lift this code out into an intermediary class, the TemplateService, and then change our static class to create a default instance of TemplateService that supports the default base type TemplateBase.

public static class Razor
{
#region Fields
private static bool createdService = false;
private static ILanguageProvider languageProvider = null;
private static MarkupParser markupParser = null;
private static TemplateService service;
private static readonly object sync = new object();
private static Type templateBaseType = null;
#endregion

#region Methods /// <summary> /// Ensures the template service has been created or re-created. /// </summary> private static void EnsureTemplateService() { if (!createdService) { lock (sync) { service = new TemplateService(languageProvider, templateBaseType, markupParser); createdService = true; } } } /// <summary> /// Parses the specified template. /// </summary> /// <param name="template">The template to parse.</param> /// <param name="name">[Optional] The name of the template.</param> /// <returns>The parsed template.</returns> public static string Parse(string template, string name = null) { EnsureTemplateService(); return service.Parse(template, name); } /// <summary> /// Parses the specified template. /// </summary> /// <param name="template">The template to parse.</param> /// <param name="model">The model to merge with the template.</param> /// <param name="name">[Optional] The name of the template.</param> /// <returns>The parsed template.</returns> public static string Parse<T>(string template, T model, string name = null) { EnsureTemplateService(); return service.Parse(template, model, name); } /// <summary> /// Sets the language provider. /// </summary> /// <param name="provider">The language provider.</param> public static void SetLanguageProvider(ILanguageProvider provider) { if (provider == null) throw new ArgumentException("provider"); languageProvider = provider; createdService = false; } /// <summary> /// Sets the markup parser. /// </summary> /// <param name="parser">The markup parser to use.</param> public static void SetMarkupParser(MarkupParser parser) { markupParser = parser; createdService = false; } /// <summary> /// Sets the template base type. /// </summary> /// <param name="type">The template base type.</param> public static void SetTemplateBaseType(Type type) { templateBaseType = type; createdService = false; } #endregion
Code language: PHP (php)

}
With that code in place, I can now do:

class Program
{
static void Main(string[] args)
{
string template =
@” @Model.Title

Enter your name: @Html.TextBoxFor(m => m.Name) “;

Razor.SetTemplateBaseType(typeof(HtmlTemplateBase<>)); var model = new PageModel { Title = "Test", Name = "Matt" }; string result = Razor.Parse(template, model); cs.WriteLine(result); cs.ReadKey(); }
Code language: JavaScript (javascript)

}
Which generates:

Test

Enter your name:
There you have it! Now I haven’t tested all possible combinations of html helper methods, but I’ll leave that for someone else.

Note: When Razor is parsing templates, it is essentially a synergistic ballet between a CodeParser and MarkupParser. The CodeParser is responsible for understanding (read: not compiling) code, and the MarkupParser is responsible for understanding the markup. Razor ships with a standard HtmlMarkupParser, but our infrastructure allows us to specify a custom MarkupParser, so in the future if we want to mix up Razor with another language, we can create a MarkupParser to do so.

Perhaps Christoph @ emphess.net might find this useful if the HtmlMarkupParser is causing problems with his RazorLaTex code?

Oh, as an added bonus, I also got declarative html helpers working:

@using System.Collections.Generic
@helper Title(string title) {
@title
}
@helper DisplayItems(IEnumerable items) {
@if(items != null) {
@foreach(string item in items) {
@item
}
}
}
@Title(Model.Title)

Enter your name: @Html.TextBoxFor(m => m.Name)

The updated project is attached. Comments are appreciated!

Download VS2010 Project

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post