Tidbits on software development, technology, and other geeky stuff

Share Razor Partial View between WebForms and MVC

ASP.NET MVC is a breath of fresh air for anyone with a background in ASP.NET WebForms. It’s cleaner, supports the Razor view engine, is much(!) easier to test, doesn’t have the nasty viewstate baggage and generally just feels better. It’s pretty great that you can use it in older webapps as well by Integrating ASP.NET MVC into an existing ASP.NET Webforms application. That’s pretty cool but when you do this, you’ll inevitably have view content that you need to share between WebForms and MVC pages. A perfect example of this is a navigation bar or page footer in MasterPages / MVC Layouts. It’s tempting to assume these two ASP.NET paradigms don’t play together and to just have a Razor version and a separate WebForms (ASPX) version that are synced up manually.

However, they can play together and you can share view content between them if you use a Razor partial view and some bridge code to shim out a wrapper around the WebForms/ASPX HttpContext. The technique was posted on Stack Overflow here and I have been using it successfully in some projects. I consider it a pretty big deal that you can do this as it makes migrating to ASP.NET MVC much more feasible in a legacy web application. I’ve gisted an example to show how’s it’s done:

00_Partial.cshtml

@model PartialViewModel
<h1>Hello World</h1>
<p>
    I was rendered from a <strong>@Model.Source</strong>!
</p>

01_RazorPartialBridge.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

//Reference: http://stackoverflow.com/a/1074061/626911
public class RazorPartialBridge
{
    public class WebFormShimController : Controller { }
    public static void RenderPartial(string partialName, object model)
    {
        //get a wrapper for the legacy WebForm context
        var httpCtx = new HttpContextWrapper(System.Web.HttpContext.Current);

        //create a mock route that points to the empty controller
        var rt = new RouteData();
        rt.Values.Add("controller", "WebFormShimController");

        //create a controller context for the route and http context
        var ctx = new ControllerContext(
            new RequestContext(httpCtx, rt), new WebFormShimController());

        //find the partial view using the viewengine
        var view = ViewEngines.Engines.FindPartialView(ctx, partialName).View;

        //create a view context and assign the model
        var vctx = new ViewContext(ctx, view,
            new ViewDataDictionary { Model = model },
            new TempDataDictionary(), httpCtx.Response.Output);

        //render the partial view
        view.Render(vctx, System.Web.HttpContext.Current.Response.Output);
    }
}

02_Demo.aspx

<%@ Page Title="Demo" Language="C#"
    AutoEventWireup="true" Inherits="Demo" Codebehind="Demo.aspx.cs" %>
<html>
<head></head>
<body>
    <% RazorPartialBridge.RenderPartial("_Partial", new PartialViewModel() { Source = "ASPX Page" }) %>
</body>
</html>

03_Demo.cshtml

<html>
<head></head>
<body>
     @{ Html.RenderPartial("_Partial", new PartialViewModel() { Source = "Razor View" }); }
</body>
</html>

Discuss on Twitter