ASP.NET MVC and the Razor Engine that is used to render ASP.NET MVC views combine to make an excellent text and HTML template-rendering engine. Together, they make it easy to merge text, model data, and code to produce rich HTML output. Typically, Razor rendering in ASP.NET MVC is reserved purely for view rendering and generation of HTML output as part of an MVC request. But, as I describe in this article, it's also possible to render MVC Razor views directly to string in order to capture the output and to render those views outside of the direct context of an MVC Web request.
I find that, as part of the Web applications I build, it's common to require text merging features that live outside of the scope of ASP.NET MVC requests. For example, quite frequently I need to create text-merged strings for email confirmations of orders, new account and password recovery notifications, or for capturing some merged output and storing it on disk or in a database. All of these require capturing of text content that is often best handled via templates rather than static text embedded into the application's source code. Razor Views are ideal for this.
Frequently, I also need to render text output in ASP.NET application code that lives outside of the context of an ASP.NET MVC request. For example, the ASP.NET Application_Error
event lives outside of an ASP.NET MVC request cycle, but could benefit greatly from a template Razor View to render the error page output. Likewise, ASP.NET Web API requests that for some reason or other need to display HTML output could also greatly benefit from Razor View rendering to generate HTML output. In short, there are quite a few places in typical ASP.NET applications that live outside of the scope of an ASP.NET MVC request and yet could benefit greatly from the ASP.NET MVC Razor View Engine.
In this article, I demonstrate how you can render ASP.NET MVC Razor Views in some unconventional ways, like capturing Razor View output to a string for use in mail-merging or storage, and for generating output from Razor Views outside of the context of an ASP.NET MVC request either for output rendering or for text-capture purposes.
I'll focus on using Razor views inside of ASP.NET applications, because that's a common scenario that allows you to use all of the goodness that is within the ASP.NET MVC Razor implementation. In a future article I'll discuss how to explicitly host the Razor engine outside of ASP.NET altogether, which is a bit more complex and doesn't allow access to the ASP.NET MVC Razor implementation.
It's not widely known, but with a little trickery you can get ASP.NET MVC Razor Views to render outside of MVC.
Understanding Razor Inside ASP.NET MVC
The Razor Engine is not directly part of ASP.NET MVC. Rather Razor is a fully freestanding and self-contained HTML template rendering engine that can live entirely outside of the MVC and ASP.NET frameworks. Razor on its own can be hosted in a desktop application, console app or service, for example. Microsoft provides two distinct custom implementations of the Razor Engine in:
- ASP.NET MVC
- ASP.NET Web Pages
In addition, you can create your own pluggable implementation of Razor to host in your own applications. The two provided implementations are marginally different in behavior; they are optimized for their respective environments. With Razor, you can create a custom Razor Engine that can be used in any .NET environment. In this article, I focus on the ASP.NET MVC Razor implementation specifically.
The ASP.NET MVC Razor implementation is closely tied to ASP.NET and MVC and the View template (WebViewPage class) includes a few MVC-specific features as well as back references to the controller. In other words MVC Razor is designed with MVC in mind.
Not surprisingly, it's easiest to render MVC Razor views out of MVC applications. Let's start by taking a look at how you can capture the output of a view to a string from within an MVC Controller application.
Rendering an MVC Razor View to String
When running inside of an MVC request, it's very simple to render an MVC view and capture its output. You can essentially simulate the process that MVC runs through when it renders a view without firing the View()
method of an ActionMethod
. The code in Listing 1 shows a static helper method that can be used to render a view by the path name to a string generically.
Listing 1: Rendering a Razor View to String from within a Controller
static string RenderViewToString(ControllerContext context,
string viewPath, object model = null, bool partial = false) {
// first find the ViewEngine for this view
ViewEngineResult viewEngineResult = null;
if (partial)
viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
else
viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);
if (viewEngineResult == null)
throw new FileNotFoundException("View cannot be found.");
// get the view and attach the model to view data
var view = viewEngineResult.View;
context.Controller.ViewData.Model = model;
string result = null;
using (var sw = new StringWriter())
{
var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw);
view.Render(ctx, sw);
result = sw.ToString();
}
return result;
}
To call this method from within a controller method, you can use code like the following:
public ActionResult RenderViewToString()
{
var model = new EmailConfirmationModel()
{
FirstName = "John",
LastName = "Doe",
InvoiceNo = "DoeBoy1"
};
string html = RenderViewToString(ControllerContext, "~/views/samples/ConfirmationEmail.cshtml", model,true);
// do something with text like send an email with the text
// For demo just push it to a View and display it
ViewBag.RenderedHtml = html;
return View();
}
The RenderViewToString()
method works by receiving a controller context and virtual view path (i.e., ~/views/item/page.cshtml
) and optional model data that are passed to the view. You can also pass a flag whether to render a partial view, which is quite common for self-contained template pages that shouldn't render a _Layout page.
The routine works by using the FindView()
method on the ViewEngine
to retrieve the View from ASP.NET's virtual path provider. You pass in a virtual path (i.e., ~/virtualPath
) to point at the template to render.
Once the view is created, the model is assigned to the ControllerContext that was passed in, or if the controller already had a model assigned to its ViewData.Model
property, that model is used. The context is crucial for various aspects of Razor view rendering including the model assignment and as a base to create the ViewContext. The context is the key that ties together the Razor View and controller, model, and the ASP.NET Context.
That's a key point-in order to use ASP.NET MVC Razor views, one way or another you need to have a ControllerContext available. The context is necessary in order to assign the model and to create the ViewContext instance that can render the view to a TextWriter
instance. In the code snippet above, I have a ControllerContext readily available, because the request is running inside of the context of an MVC Controller action method.
This code is simple and easy to understand and it works very well within the confines of an MVC application in Controller actions. If you need to send email confirmations or perform logging or data tasks as part of an MVC page request, this simple mechanism is all you need.
ASP.NET MVC's Razor implementation is unfortunately closely coupled to ASP.NET, making it difficult to use MVC Razor outside of ASP.NET.
Getting a ControllerContext Outside of MVC
What happens when no ControllerContext is available, like inside of static code or inside of an HttpHandler or HttpModule or even a Web API call?
As you can see in RenderViewToString()
in Listing 1, a ControllerContext is required in order to render a view. There's really no way around it, as the context provides the background info for a number of key components that are exposed on the view, such as the HttpContext intrinsic objects, the routing data, UrlHelper and more. So it seems that the view is closely coupled to the controller…
But surprisingly, it turns out that the controller coupling is relatively loose-it's possible to create a ControllerContext generically off any Controller instance and pass that to a totally unrelated View. You can then use that generated context and pass it to RenderViewToString()
. Using this trick, it's possible to render a View outside of the MVC framework as long as an HttpContext instance is available.
The method in Listing 2 demonstrates how to create an arbitrary controller instance with an attached ControllerContext, which can then be used to call RenderView()
.
Listing 2: Creating a generic Controller Instance and Context
public static T CreateController<T>(RouteData routeData = null) where T : Controller, new()
{
// create a disconnected controller instance
T controller = new T();
// get context wrapper from HttpContext if available
HttpContextBase wrapper;
if (System.Web.HttpContext.Current != null)
wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
else
throw new InvalidOperationException("Can't create Controller Context if no active HttpContext instance is available.");
if (routeData == null)
routeData = new RouteData();
// add the controller routing if not existing
if (!routeData.Values.ContainsKey("controller") && !routeData.Values.ContainsKey("Controller"))
routeData.Values.Add("controller", controller.GetType().Name.ToLower().Replace("controller", ""));
controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
return controller;
}
You can now call this method to create a controller generically from anywhere within an ASP.NET application as long as an HttpContext is available. Listing 3 shows an example of using an MVC view to render error information from an HttpModule which lives outside of the MVC runtime.
Listing 3: Render an MVC from an Http Module outside of MVC
public class ErrorModule : ApplicationErrorModule
{
protected override void OnDisplayError(WebErrorHandler errorHandler, ErrorViewModel model)
{
var response = HttpContext.Current.Response;
// Create an arbitrary controller instance
var controller = ViewRenderer.CreateController<GenericController>();
string html = ViewRenderer.RenderPartialView("~/views/shared/Error.cshtml", model, controller.ControllerContext);
HttpContext.Current.Server.ClearError();
response.TrySkipIisCustomErrors = true;
response.ClearContent();
response.StatusCode = 500;
response.Write(html);
}
}
// *any* controller class will do for the template
public class GenericController : Controller
{ }
This example's code runs in an HttpModule outside of the scope of MVC but can still render an MVC view. It's an error handling module that captures any unhandled ASP.NET errors (think Application_Error
), and routes them to the OnDisplayError()
method to handle the display error information, for which I use the RenderViewToString()
method to render the error response page.
The rendering code uses the CreateController()
method to create an instance of a controller and passes its ControllerContext to RenderViewToString()
. Note that any controller instance will work, as you can see by the empty GenericController
class I created as part of the sample. In my own MVC helper libraries, the ViewRenderer
class creates a known simple EmptyController
instance that lives in the Helper
assembly and uses that if no ControllerContext
instance is passed explicitly.
As long as you are inside of ASP.NET, even things like the Request
object or UrlHelpers work because the controller context can access these off of the HttpContext
that is used in conjunction with the controller context when it is created.
The ViewRenderer Class
The ViewRenderer class used in Listing 2 and Listing 3 is an expansion of the methods shown earlier. The class provides RenderViewToString()
and RenderPartialViewToString()
methods, the CreateController()
method shown in Listing 2 and both instance and static methods to handle view rendering easily. You can find the source code to ViewRenderer class in the sample that accompanies this article or in the GitHub repository of the West Wind Toolkit (http://tinyurl.com/lpz877z). You can also can find the ViewRenderer class as part of the Westwind.Web.Mvc NuGet (http://tinyurl.com/morbw6e).
It is possible to use Razor outside of ASP.NET but it's not the same MVC Razor implementation and a lot more work is required to set it up.
Another Use Case: Web API
Another use case for HTML rendered from Razor is ASP.NET Web Api. Although APIs return data, when building hyper-media systems or mixed-mode APIs, it's not uncommon to have APIs also generate HTML output for some requests. Because Web API can run under ASP.NET and has access to HttpContext.Current
, you can use the ViewRenderer from an API controller method or even from a custom MediaFormatter.
To demonstrate this, Listing 4 shows a simple API controller method that checks for HTML clients and returns either an HTML response or the raw data when JSON or XML is requested.
Listing 4: A Web API controller that renders a Razor View
public class CustomerController : ApiController
{
[HttpGet]
public HttpResponseMessage Customer(int id = 0)
{
var customer = new Customer()
{
Id = id,
Name = "Jimmy Roe",
Address = "123 Nowhere Lane\r\nAnytown, USA",
Email = "jroe@doeboy.com"
};
string accept = Request.Headers.GetValues("Accept").FirstOrDefault();
if (!string.IsNullOrEmpty(accept) && accept.ToLower().Contains("text/html"))
{
var html = ViewRenderer.RenderView("~/views/samples/customerapi.cshtml", customer);
var message=new HttpResponseMessage(HttpStatusCode.OK);
message.Content = new StringContent(html, Encoding.UTF8, "text/html");
return message;
}
return Request.CreateResponse<Customer>(HttpStatusCode.OK, customer);
}
}
The example is simple of course: any switching logic should be delegated to a custom media-type formatter or some other central routine that can automatically figure out which view to render. Other requests may also always return an HTML response, such as those that return merged-text data such as confirmations or other dynamic documents. There are some interesting possibilities in this scenario to allow building complete systems that encompass both data and text or HTML content through a single API implementation.
Slicing and Dicing with Razor
Razor is a powerful templating implementation that's very useful to generate text and HTML output for all sorts of purposes. As you've seen in this article, you can use ASP.NET MVC Razor views from just about anywhere in your ASP.NET applications fairly easily with the Helper class provided here.
Whether it's text generation for confirmation emails, or generation of output for non-MVC requests like in custom handlers or modules, or whether you may need an occasional HTML response in a Web API application, as long as the ASP.NET runtime and HttpContext.Current
is available, using the techniques described in this article allows you to use MVC Razor views in your ASP.NET Web applications.
For me, this has been a vital feature that I use in almost every Web application I build these days, as it helps me avoid hard-coding text into applications. Razor makes it easy to manage templates for documents with the tools I'm already using in my application, which is as it should be.