Cleggy
Cleggy

Reputation: 725

Using the Razor View Engine with a custom templating framework

We have a custom templating framework used by our web applications with implementations which are written in a variety of languages, and I'm investigating whether its possible to use this in an ASP.NET MVC application using the Razor View engine. We already have an implementation that uses the Web Forms View engine, but I'm keen to be able to use Razor as well.

The template is defined in a html file which contains macro tags that are interpreted and replaced at runtime with the applicable html. Below is a highly simplified version of this file:-

[doctype]
<html>
  <head>
    [HeadScript]
    [HeadSectionText]
  </head>
  <body id="[if Home][Home][else]body[end if]">
    [form]
      [Content]
    [end form] 
    [EndBodyScript]
  </body>
</html>

As you can see, there are opportunities to inject html into various sections of the document, with some of this being handled by a simple conditional logic implementation. In order to use this with Razor, I'd need to be able to process all the macro tags in the template, including inserting the HTML from the Razor view being rendered, and use the output of this operation as the HTML to be ultimately sent to the client.

I'm pretty green when it comes to MVC development, but I know I can create a custom ViewEngine and IView implementation. Perhaps I can use this to achieve what I want, with a custom engine using the RazorView class to render the actual content to be inserted into our template. Does this sound like a feasible solution? And if so, does anyone have any tips to get me started?

Currently I have a kludgy solution where I use the ASP.NET WebForms engine and render partial Razor views, but longterm I'd prefer to have a solution that can take the WebForms engine out of the equation entirely.

Any shoves in the right direction greatly appreciated.

Upvotes: 2

Views: 1953

Answers (1)

Lukasz Lysik
Lukasz Lysik

Reputation: 10620

I've managed to figure something out. Although my problem is not exactly the same you have, but it can certainly help you. But first things first.

The problem

I have a number of controls (in my case they are Partial Views). For example:

Views/Shared/EditName.cshtml
Views/Shared/EditAddress.cshtml
Views/Shared/EditEmail.cshtml
Views/Shared/EditFavoriteColour.cshtml

I want to define groups of these controls (in database or web.config), for example:

SimpleEditGroup:
    EditName
ExtendedEditGroup:
    EditName
    EditEmail
FullEditGroup:
    EditName
    EditAddress
    EditEmail
    EditFavoriteColour

Then I want to render a view based on a particular group. I was thinking the following syntax would be sufficient.

@Html.Partial("ControlGroup:ExtendedEditGroup", Model)

Draft of the solution

I wanted to create my own render engine which will do the following:

  1. If the name of the view begins with "ControlGroup:" then use previously defined groups to render the page. Each of the controls should be rendered by RazorViewEngine.
  2. If the name of the view doesn't begin with "ControlGroup:" just let the RazorViewEngine do its work.

Research

I've found following resources:

  1. Article on CodeProject - Custom ViewEngine in ASP.NET MVC3
  2. Article based on StackOverflow question - Creating a Widget system in ASP.NET MVC 3
  3. I recommend you the book Pro ASP.NET MVC 3 Framework. It will give you the idea how many things you can customize in ASP.NET MVC 3
  4. Of course source code of ASP.NET MVC 3 RTM was very helpful.

Final solution

MyViewEngine:

public class MyViewEngine : RazorViewEngine
{
  IDictionary<string, List<string>> groups = new Dictionary<string, List<string>>();

  public MyViewEngine()
  {
    // Temporary data source
    groups["SimpleEditGroup"] = new List<string>() { "EditName"};
    groups["ExtendedEditGroup"] = new List<string>() { "EditName", "EditEmail"};
  }

  public override ViewEngineResult FindView(ControllerContext controllerContext,
           string viewName, string masterName, bool useCache)
  {
    if (viewName.StartsWith("ControlGroup"))
    {
      var groupName = viewName.Split(':')[1];
      var controls = new List<ViewEngineResult>();

      foreach (var controlName in groups[groupName])
      {
        // Find each control using Razor magic
        var control = base.FindPartialView(controllerContext, controlName, useCache);
        if (control.View != null)
        {
          controls.Add(control);
        }
      }

      if(controls.Count > 0)
        return new ViewEngineResult(new MyView(controls), this);
    }

    return base.FindView(controllerContext, viewName, masterName, useCache);
  }
}

MyView.cs:

public class MyView : IView
{
  IList<ViewEngineResult> controls;

  public MyView(IList<ViewEngineResult> _controls)
  {
    controls = _controls;
  }

  public void Render(ViewContext viewContext, TextWriter textWriter)
  {
    // For simplicity I used table for layout
    textWriter.Write("<table border='1'>");

    foreach (var ctrl in controls)
    {
      textWriter.Write("<tr><td>");

      // Render control using Razor
      ctrl.View.Render(viewContext, textWriter);

      textWriter.Write("</td></tr>");
    }

    textWriter.Write("</table>");
  }
}

Don't forget to register your new engine in Global.asax ViewEngines.Engines.Add(new MyViewEngine());

Happy ending

I can use this in two ways: embedded in a view using Render.Partial or just returning PartialView from my controller.

Let me know if it helps you. And also you can share your solution.

Upvotes: 1

Related Questions