Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

Rendering ASP.NET MVC Razor Views outside of MVC revisited


Last year I posted a detailed article on how to render Razor Views to string both inside of ASP.NET MVC and outside of it. In that article I showed several different approaches to capture the rendering output. The first and easiest is to use an existing MVC Controller Context to render a view by simply passing the controller context which is fairly trivial and I demonstrated a simple ViewRenderer class that simplified the process down to a couple lines of code.

However, if no Controller Context is available the process is not quite as straight forward and I referenced an old, much more complex example that uses my RazorHosting library, which is a custom self-contained implementation of the Razor templating engine that can be hosted completely outside of ASP.NET. While it works inside of ASP.NET, it’s an awkward solution when running inside of ASP.NET, because it requires a bit of setup to run efficiently.

Well, it turns out that I missed something in the original article, namely that it is possible to create a ControllerContext, if you have a controller instance, even if MVC didn’t create that instance.

Creating a Controller Instance outside of MVC

The trick to make this work is to create an MVC Controller instance – any Controller instance – and then configure a ControllerContext through that instance. As long as an HttpContext.Current is available it’s possible to create a fully functional controller context as Razor can get all the necessary context information from the HttpContextWrapper().

The key to make this work is the following method:

/// <summary>
/// Creates an instance of an MVC controller from scratch 
/// when no existing ControllerContext is present       
/// </summary>
/// <typeparam name="T">Type of the controller to create</typeparam>
/// <returns>Controller Context for T</returns>
/// <exception cref="InvalidOperationException">thrown if HttpContext not available</exception>
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 = null;
    if (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;
}

This method creates an instance of a Controller class from an existing HttpContext which means this code should work from anywhere within ASP.NET to create a controller instance that’s ready to be rendered. This means you can use this from within an Application_Error handler as I needed to or even from within a WebAPI controller as long as it’s running inside of ASP.NET (ie. not self-hosted). Nice.

So using the ViewRenderer class from the previous article I can now very easily render an MVC view outside of the context of MVC. Here’s what I ended up in my Application’s custom error HttpModule:

protected override void OnDisplayError(WebErrorHandler errorHandler, ErrorViewModel model)
{
    var Response = HttpContext.Current.Response;
    Response.ContentType = "text/html";
    Response.StatusCode = errorHandler.OriginalHttpStatusCode;

    var context = ViewRenderer.CreateController<ErrorController>().ControllerContext;
    var renderer = new ViewRenderer(context);
    string html = renderer.RenderView("~/Views/Shared/GenericError.cshtml", model);

    Response.Write(html);           
}

That’s pretty sweet, because it’s now possible to use ViewRenderer just about anywhere in any ASP.NET application, not only inside of controller code.

This also allows the constructor for the ViewRenderer from the last article to work without a controller context parameter, using a generic view as a base for the controller context when not passed:

public ViewRenderer(ControllerContext controllerContext = null)
{
    // Create a known controller from HttpContext if no context is passed
    if (controllerContext == null)
    {
        if (HttpContext.Current != null)
            controllerContext = CreateController<ErrorController>().ControllerContext;
        else
            throw new InvalidOperationException(
                "ViewRenderer must run in the context of an ASP.NET " +
                "Application and requires HttpContext.Current to be present.");
    }
    Context = controllerContext;
}

In this case I use the ErrorController class which is a generic controller instance that exists in the same assembly as my ViewRenderer class and that works just fine since ‘generically’ rendered views tend to not rely on anything from the controller other than the model which is explicitly passed.

While these days most of my apps use MVC I do still have a number of generic pieces in most of these applications where Razor comes in handy. This includes modules like the above, which when they error often need to display error output. In other cases I need to generate string template output for emailing or logging data to disk. Being able to render simply render an arbitrary View to and pass in a model makes this super nice and easy at least within the context of an ASP.NET application!

You can check out the updated ViewRenderer class below to render your ‘generic views’ from anywhere within your ASP.NET applications. Hope some of you find this useful.

Resources

Make Donation
Posted in ASP.NET  MVC  


Feedback for this Post

 
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Vince July 16, 2013 @ 4:09am
Rick,

I'm, like yourself, a big fan of Razor views and being able to easily construct them in MVC is one of the joys of using this framework. However, I think for non-MVC .NET applications T4 preprocessor templates are a better (albeit not as easy to use) option as they are more versatile and lend themselves to a streamlined implementation footprint. I always feel a little 'dirty' using the System.Web assembly in a Winforms applications.

T4 templates are practical and useful no matter what you application type or what system tier your utilising them in. The added advantage is that they can be used to not only generate HTML but produce any number of other formats such as code, XML, etc.

Might be something you could write about sometime.
I'm always interested in reading your thoughts on development topics.

Cheers
Vince
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Rick Strahl July 16, 2013 @ 2:57pm
@Vince - T4 is nice, but it's tied to Visual Studio/MsBuild - you can't really embed that into an application (at least not that I know of). Razor you can definitely embed and it doesn't actually require System.Web - only System.Web.Razor.

The solution I'm describing in this post has nothing to do with hosting outside of IIS/ASP.Net - it isn't meant to be used outside of ASP.NET, but rather in those parts of ASP.NET that live outside of the scope of MVC: Handlers, Modules, static methods, ASP.NET Web API etc.

T4 is optimized for code generation. Razor is optimized for HTML generation. Both are very specific to their domain focus and both have some issues outside of that focus. Personally I think that T4 stinks mainly because Microsoft seems to have purposefully hobbled it and the tooling that goes with it. It might have been a good solution, but it falls short for anything but code generation within VS/MsBuild.
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Michael McGuire July 16, 2013 @ 4:50pm
I'm going to take an opportunity to thank you for your Razor Hosting library. I'm using it right now! The other Razor library on NuGet with more downloads was way too hobbled and undocumented to work for what I needed to do.
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Rick Strahl July 17, 2013 @ 2:42am
@Michael - thanks! I've been meaning to do another blog post on this as there have been significant improvements to the Razor library, since the original blog post and since Razor 2.0 was released. Just can't seem to find the time, but hearing that more people are using it sure is an incentive :-).
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Vince July 17, 2013 @ 4:49pm
Rick,

You understanding of T4 templates is erroneous. T4 preprocessor templates can be embedded into an application and used to generate views (html, xml, csharp, etc) at run time.

Some very basic information can be found here:
http://msdn.microsoft.com/en-us/library/ee844259.aspx

There is no difference between the capability of T4 templates and Razor Views, although I agree the tooling support for T4 templates is limited in VS.

Again, might be worth taking another look at this topic.

Cheers
Vince
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Jaesi (@jaesib) July 30, 2013 @ 2:26pm
I'm so glad this is possible, thank you!
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Jonathan August 27, 2014 @ 11:18am
Hi Rick, Thanks for the code. I noticed if you give an incorrect path to a view, RenderViewToStringInternal at this line:

viewEngineResult = ViewEngines.Engines.FindView(Context, viewPath, null);


Will still return a ViewEngineResult but the View property will be null and throw an exception here:

                var ctx = new ViewContext(Context, view,
                                          Context.Controller.ViewData,
                                          Context.Controller.TempData,
                                          sw);


So this line is not reached:

            if (viewEngineResult == null)
                throw new FileNotFoundException();
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Rodrigo Pires September 23, 2014 @ 11:11am
Hello Rick,

What if I'm running this code outside an ASP.NET app (meaning no HttpContext available), is it still possible to render the view?

Thank you!
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Rick Strahl September 23, 2014 @ 1:11pm
Rodrigo - no this particular code does not work outside of ASP.NET as mentioned in the article. If you need to run completely outside of ASP.NET you can use the Razor Hosting Library:

https://github.com/RickStrahl/Westwind.RazorHosting

Takes a little more work to set this up, but you can do most Razor operations easily.
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Vignesh December 08, 2014 @ 1:16pm
Rick,

Is it possible to divide a single view into a number of sections and render a given section instead of the entire view ? This will be useful in cases where there are a number of tiny but related html stuff that don't necessarily warrant a dedicated view.

For ex : renderer.RenderSection(View,SectionA, model);
renderer.RenderSection(View,SectionB, model);
etc
# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited
by Rick Strahl December 10, 2014 @ 1:29pm
@Vignesh - No, you have to render the top level view. But it sounds to me that you can do what you want if you do the proper segmentation of your views using partial views rather than sections (or else embed the sections into partial views).
 


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2014