Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Rendering ASP.NET MVC Razor Views outside of MVC revisited


:P
On this page:

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

Posted in ASP.NET  MVC  

The Voices of Reason


 

Vince
July 16, 2013

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

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

Rick Strahl
July 16, 2013

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

@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.

Michael McGuire
July 16, 2013

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

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.

Rick Strahl
July 17, 2013

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

@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 :-).

Vince
July 17, 2013

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

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

Jaesi (@jaesib)
July 30, 2013

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

I'm so glad this is possible, thank you!

Jonathan
August 27, 2014

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

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();

Rodrigo Pires
September 23, 2014

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

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!

Rick Strahl
September 23, 2014

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

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.

Vignesh
December 08, 2014

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

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

Rick Strahl
December 10, 2014

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

@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).

Mike
August 25, 2015

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

Hi Rick,

Thanks for your excellent work!

Do you have any idea how this may be implemented in a scenario where dependency injection is used on some of the controllers and the constructors needs to be feeded with some parameters?

Cheers,
Mike

Rick Strahl
August 25, 2015

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

@Mike - if you have existing controllers and controller contexts that you want to use you can use standard DI in ASP.NET to get the controllers injected as usual. If you're creating a new context completely you'll have to add that functionality in the CreateController  by using reflection and ServiceProvider explicitly to inject the constructors since it doesn't go through the standard ASP.NET mechanism of creating the controllers.

Mike
August 25, 2015

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

Hi Rick and thanks for your quick reply. I figured it was something like that so now I know I'm on the right way.

I really enjoy reading your blog so please keep up the good work and good luck in the future!

All the best,
/Mike

alireza
August 21, 2016

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

Hi Dear Rick,

Thanks for your excellent work!

I HAVE A QUESTION PLEASE HELP ME.

HOW TO USE CreateController<T> by sending controller string name instead controller type name
like this:
CreateController<HomeController> to CreateController("HomeController")

Germán
March 19, 2019

# re: Rendering ASP.NET MVC Razor Views outside of MVC revisited

Hello Rick,

Do you think that what you've posted is a solution for this? https://stackoverflow.com/questions/11996205/asp-net-mvc-exception-rendering-view-in-new-thread

Quoting: "The error I'm getting is: 'Value does not fall within the expected range', when the viewResult.View.Render method is called.

I'm guessing this has to do with the fact that the controller context is no longer valid in the new thread, but I'm not sure."


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