Intro
I was asked to maintain and extend a WebForms application. My first reaction was migrate to MVC, but Dave Paquette explains very well why this is usually a bad idea. Migrating is expensive, takes much time and creates a lot of risk. WebForms was launched in 2001 and is now slowly dying. Sure, it will be supported by Microsoft for many years to come. So what to do with the WebForms application? Adding new features with a dying technology feels wrong. After some googling I found that you can mix WebForms and MVC. Well that's a good a start. With a bit of luck I can build the new stuff with MVC. The material I found on the web, was quite old and did not work, but they gave a good direction. I started coding and the result was even simpler than I could have imaged. In this blog I share my findings on how you can mix WebForms and MVC Razor Views in one web application.
The Problem
From Visual Studio 2013 and later you can mix WebForms and MVC Razor views in one application. The application has two render engines and the routing takes care for selecting the correct render engine.
public class RazorDemoController : Controller
{
public ActionResult Index()
{
return View();
}
...
Here starts the problem. The razor view has no Master Page knowledge. The Master Page is the WebForms way for creating a site layout. In MVC the layout is set by a Razor Layout Page. These two types don't mix and the result is a "standalone" page.
The Solution
I start with the solution and explain the steps how to get there.
The actual work is done by the RazorController base class and makes coding fairly simple, render a RazorView() instead of a normal View().
public class RazorDemoController : RazorController
{
public ActionResult Index()
{
return RazorView();
}
How it works
Fooling the application to render a Razor view in a Web Form is the solution.
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="RazorContent" ContentPlaceHolderID="MainContent" runat="server">
<% Html.RenderPartial((String)ViewBag.ViewName); %>
</asp:Content>
The RazorView.aspx is the Web Form and has a content placeholder for the Razor view. Please note I tweaked Inherits= from "System.Web.UI.Page" to "System.Web.Mvc.ViewPage". This little tweak selects the MVC render engine in Html.RenderPartial instead of the default WebForms engine and does the trick. The RazorView.aspx is located in the Shared Views area and is available for all Razor views.
Passing ViewName
The Html.RenderPartial needs the Razor viewname to render. The RazorView.aspx is a shared page and has no knowledge which view to render and we need some way to pass the viewname. The RazorView.aspx descends from Mvc.ViewPage and has the ViewBag property. The ViewBag is accessible from the controller so we have found a way for passing the view name to Html.RenderPartial in the RazorView.aspx page.
RazorController
The RazorController encapsulates all the required code:
public class RazorController : Controller
{
public ActionResult RazorView(String viewName, Object model)
{
ViewBag.ViewName = viewName;
return View("RazorView", model);
}
public ActionResult RazorView(Object model)
{
return RazorView(GetCurrentViewName(), model);
}
public ActionResult RazorView(String viewName)
{
return RazorView(viewName, null);
}
public ActionResult RazorView()
{
return RazorView(GetCurrentViewName(), null);
}
private String GetCurrentViewName()
{
var result = $"{Url.RequestContext.RouteData.Values["action"]}";
return result;
}
}
The RazorView() method has several overloads. If the view name is omitted the function GetCurrentViewName() takes the current action name as the viewname.
RazorDemoController inherits from the RazorController and as you can see the RazorView methods are now easy to use.
public class RazorDemoController : RazorController
{
public ActionResult Index()
{
return RazorView();
}
public ActionResult RenderOtherView()
{
return RazorView("Hello");
}
public ActionResult Customer(Int32 id)
{
var model = new Customer()
{
Id = id,
Name = "Company ABC",
City = "The One that never sleeps"
};
return RazorView(model);
}
[HttpGet]
public ActionResult SomeAjaxCall(Int32 id)
{
var model = new Customer()
{
Id = id,
Name = "Company ABC",
City = "The One that never sleeps"
};
return View("Customer", model);
}
}
The associated RazorView calls
<div>
<h3>I am really a MVC Razor view in a WebForms world</h3>
</div>
<div>
<h3>Hi there...</h3>
</div>
@model Models.Customer
<div>
<h3>Customer</h3>
<p>Id : <strong>@Model.Id</strong>
<p>Name : <strong>@Model.Name</strong>
<p>City : <strong>@Model.City</strong>
</div>
Customer.cshtml renders to:
Visual Studio Project Setup
"Old" WebForms applications are not configured for both WebForms and MVC. Migrating the solution to the latest Visual Studio will not solve this problem. You can add extra MVC references to the existing project or move all your forms to a new project. What works best for you is hard to tell. If you create a new WebForms project make sure you set the MVC reference.
- Step 1 Setup dual WebForms MVC project
Choose Web Forms and add the MVC reference
- Step 2
A new project is created with all the required references for the dual render engine. You can use it as an example for upgrading the existing solution or as the new solution.
jQuery and Ajax
Another option is jQuery with Ajax. The concept is easy, create a div element in the Web Form page and get the contents with an Ajax call to the MVC controller and illustrated in the next example. The Web Form AjaxDemo.aspx invokes action SomeAjaxCall on controller RazorDemo and loads the content in the div razorViewContent.
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="AjaxDemo.aspx.cs"
Inherits="WebForm.WebForms.AjaxDemo" %>
<asp:Content ID="content" ContentPlaceHolderID="MainContent" runat="server">
<h3>Ajax demo</h3>
<%----%>
<div id="razorViewContent">
</div>
<script type="text/javascript">
$(function () {
$.ajax({
type: "Get",
url: "/RazorDemo/SomeAjaxCall",
data: 'id=55',
success: function (document) {
$('#razorViewContent').html(document);
}
});
});
</script>
</asp:Content>
Controller implementation:
[HttpGet]
public ActionResult SomeAjaxCall(Int32 id)
{
var model = new Customer()
{
Id = id,
Name = "Company ABC",
City = "The One that never sleeps"
};
return View("Customer", model);
}
The Ajax approach has one drawback, it takes one extra file web form file for the same result, although I can image situations where the Ajax principle is really convenient.
Dot Net Core Options
If you want to mix MVC and WebForms on Dot Net Core, you have no luck. There is no WebForms support on Dot Net Core, so mixing is out of the question.
Conclusion
WebForms and MVC Razor views perfectly mix together and provides possibilities to extend existing WebForms application with new MVC techniques. Mixing can be done by rendering a Razor view as a Web Form page or use jQuery and Ajax and render a div element with MVC content. Both options use the original site layout. Which method is best is up to you.
Version History
1.0.0 | 2017 July | Initial version |
1.0.1 | 2017 august | Minor text changes. Source code cleaned |
Further Reading and thanks to
So you inherited an ASP.NET Web Forms application
Dino Esposito Mixing WebForms and MVC
Scott Hanselman Mixing Razor Views and WebForms Master Pages with ASP.NET MVC 3
Matthaw Using Razor Pages with WebForm Master Pages