Anjani Singh

Decoupling MVC Razor Engine for Parsing Razor Page

When creating razor page using MVC, MVC default Razor View engine is invoked to render the page.I have been through a situation when we want to render view using MVC razor engine but the view is not located under the conventional directory structure of the view. In fact not even in the project and in some cases not even as cshtml view. For instance, The text for view can be located in database or simple text file.

If we write-

 public class HomeController : Controller

{
public ActionResult Index()
{
return View();
}
}

The location of the view is expected to be in Home folder under the View Folder with view name as Index.cshtml.

Suppose, we have different name and location for view we can specify the view location as

 public class HomeController : Controller
    {
    public ActionResult Index()
        {
            return View("~/Views/Account/test.cshtml");
        }
    }

where test.cshtml view is located under Account folder with name “test” instead of “Index”

There can be two types of different scenarios. First, when the cshtml view is not located in project and has to be located from elsewhere, like from filesystem or network or any location which is accessible from the application server. Second, when View is stored simply as text in database or some other kind of files. In both the cases we will be using default razor engine to parse and generate html output. This blog shows how to call Razor Engine separately instead of directly being invoked by return PartialView() or return View(). We will cover the first scenario.i.e, when cshtml view is located at different location. I am writing another blog for second scenario.i.e when the cshtml view is stored as string in database or simple text file. (will share it soon). Only, additional step is to add a virtual path provider which will create a virtual cshtml file while the common part is invoking default razor engine separately.

In this example, I created a view which will use ajax call to render the page content stored at different location

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<button onclick="GetDynamicHtml()">Get View HTML </button>

<span>Views will be rendered in colored div below</span>
<div id="htmlContainer" style="height:400px;width:400px;background-color:aquamarine">


</div>

<script>

    function GetDynamicHtml() {

        $.ajax({
            url: '@Url.Action("GetPageHtml", "Home")',
            success: function (data) {

                $('#htmlContainer').html(data.HTML);
            },

        });
    }

</script>

created a view

The action method is defined as below.This action Invokes the Render Method in RazorViewHelper class which contains implementation for generating HTML from RazorView.

public JsonResult GetPageHtml()
        {
          
                string  guid = Guid.NewGuid().ToString();
                string tempFilePath = System.AppDomain.CurrentDomain.BaseDirectory + "\\Views"+"\\temp\\"+ guid+".cshtml";            
                string pathcshtml = "E:\\Index.cshtml";
                System.IO.File.Copy(pathcshtml, tempFilePath); //  Creates new temporary file in View Folder.Remember to Delete at regular interval
                string virtualFilePath = tempFilePath.Replace(System.AppDomain.CurrentDomain.BaseDirectory, "~");
                // razor engine uses relative path instead of physical path, hence converted to virtual path
                var viewHelper = new RazorViewHelper();
                string generatedHtml = viewHelper.Render(null, ControllerContext, virtualFilePath);
                return Json(new { HTML = generatedHtml }, JsonRequestBehavior.AllowGet);        
        }

The path to external file containing view is stored in variable pathcshtml. The Content of E:\Index.cshtml is shown in image below.

external file containing view is stored

The generated output after “Get View HTML” button click

generated output

Now coming back to action method shown above, we are copying the cshtml file from file system(E:\Index.cshtml) to views directory and renaming with unique name generated by GUID. It is necessary to put the temporary file under Views Folder otherwise the web configuration in the View folder would not be applicable and razor syntax or any such code would not be recognized for which assemblies are declared in web config of Views.

Notice that you have to pass controller context to the Render method in order to make it work. There is workaround if we want to use Razor engine outside of MVC environment. The first method is to use custom razor engine instead of default engine provided with MVC, other methods is described here.

Code for Render Method in RazorViewHelper class is shown below.

   public string Render(object ViewModel, ControllerContext controller, string path)
        {

           
                var sb = new StringWriter();
                ViewDataDictionary ViewData = new ViewDataDictionary();
                var tempData = new TempDataDictionary();
                ViewData.Model = ViewModel;
                var razor = new RazorView(controller, path, null, false, null);
                var viewContext = new ViewContext(controller, razor, ViewData, tempData, sb);
                razor.Render(viewContext, sb);
                return sb.ToString();

           
        }

This code is doing compiling of Razor View and calling of Render Method of default view Engine. The same thing which is done by return View() method call.Internally, it fetches the view, compiles it and then generates the Html string.Please note that, Once the virtual path of temporary file has been created,we can simply render view by calling return View(virtualcshtmlPath) but separating the render logic and getting hold of generated HTML as string instead of directly being written to response stream can be required in many scenarios. One of the scenario may be an Email Services which parse Razor template, generates text based on Model passed and finally sends to the reciever.

You can see null is being passed to Render Method of RazorViewHelper as ViewModel. We can pass our model for binding with View.

Lets change the action method to as shown below. Notice the Employee object being passed.

 public JsonResult GetPageHtml()
        {
         
                string  guid = Guid.NewGuid().ToString();
                string tempFilePath = System.AppDomain.CurrentDomain.BaseDirectory + "\\Views"+"\\temp"+ guid+".cshtml";            
                string pathcshtml = "E:\\Index.cshtml";
                System.IO.File.Copy(pathcshtml, tempFilePath); //  Creates new temporary file in View Folder. Delete at regular interval
                string virtualFilePath = tempFilePath.Replace(System.AppDomain.CurrentDomain.BaseDirectory, "~");
                // razor engine uses relative path instead of physical path, hence converted to virtual path
                var viewHelper = new RazorViewHelper();
                Employee employee = new Employee();
                employee.Name = "Anjani";
                employee.Id = 211;
                string generatedHtml = viewHelper.Render(employee, ControllerContext, virtualFilePath);
                return Json(new { HTML = generatedHtml }, JsonRequestBehavior.AllowGet);        
        }

The View has been changed to

The View has been changed

and the result rendered is:

 result rendered

Conclusion

There are various custom razor engine available for parsing razor pages mostly used for using outside MVC environment but all Html helpers methods and support for methods like TextBoxFor using Lambda expression may not be there. Advantage of using Default razor engine also includes support for DevExpress controls. So, its good to use default razor engine wherever possible. The HTML generation logic can be separated as shown in the post. My Next post (coming soon) will show the case when the cshtml view are stored in DB or is in different format.

 

Related Articles

#Tech

NHibernate, Linq and Lazy Collections

For the past few months we have been busy building a services framework for one of our clients so that they can build all of their enterprise web services without ever having to worry about the cross cutting concerns and... Read more
#Tech

Page Redirects using Spring.NET

Who is responsible for page redirects in ASPNET MVP – The View or the Presenter None of the above, it is you :) On a serious note, it is the View as one shouldn’t pollute the Presenter with the page navigation... Read more