Asp.Net MVC 3 versus Asp.Net Web Forms Performance.
I recently worked with a customer who was migrating their site from Asp.Net Web Forms to Asp.Net MVC 3.
They saw some differences in page execution time and came to us asking for more information.
In this case the MVC code was slower than the equivalent Web Forms page.
Data Class
The customer started with the following Data class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcTest
{
public class DataGenerator
{
public List<DummyData> GetDummyData()
{
List<DummyData> data = new List<DummyData>();
for (int i = 0; i < 100000; i++)
{
DummyData d = new DummyData() { D1 = i.ToString() + "1234567890", D2 = (i * 10).ToString() + "123456789" };
data.Add(d);
}
return data;
}
}
public class DummyData
{
public string D1 { get; set; }
public string D2 { get; set; }
}
}
Asp.Net Web Forms code.
The Web Forms app just loads up the class
public partial class WebFormsPage : System.Web.UI.Page
{
public List<DummyData> data;
protected void Page_Load(object sender, EventArgs e)
{
DataGenerator dg = new DataGenerator();
data = dg.GetDummyData();
}
}
and then loops through it.
<%foreach (var item in data)
{ %>
<tr>
<td>
<%=item.D1 %>
</td>
<td>
<%=item.D2 %>
</td>
</tr>
<% } %>
Asp.Net MVC 3 Code.
The new version of the site had some code like this.
The MVC Controller passes the same class in as a model binding.
public ActionResult MvcPage()
{
DataGenerator dg = new DataGenerator();
return View(dg.GetDummyData());
}
Then it uses the Razor syntax to loop through.
@model List<MvcTest.DummyData>
<table>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.D1)
</td>
<td>
@Html.DisplayFor(modelItem => item.D2)
</td>
</tr>
}
</table>
Analysis
We gathered dump files of the MVC code from IIS and saw stacks like the following.
000000000674d3f8 000000007714135a [NDirectMethodFrameStandalone: 000000000674d3f8] System.RuntimeTypeHandle.GetInstantiation(System.RuntimeTypeHandle, System.Runtime.CompilerServices.ObjectHandleOnStack, Boolean)
000000000674d3c0 000007fee2477db8 DomainNeutralILStubClass.IL_STUB_PInvoke(System.RuntimeTypeHandle, System.Runtime.CompilerServices.ObjectHandleOnStack, Boolean)
000000000674d490 000007fee23cd205 System.RuntimeType.GetGenericArguments()
000000000674d500 000007fedf37d667 System.Dynamic.Utils.TypeUtils.CanCache(System.Type)
000000000674d540 000007fedf37ef32 System.Linq.Expressions.Expression.ValidateLambdaArgs(System.Type, System.Linq.Expressions.Expression ByRef, System.Collections.ObjectModel.ReadOnlyCollection`1<System.Linq.Expressions.ParameterExpression>)
000000000674d5d0 000007fedf39763a System.Linq.Expressions.Expression.Lambda[[System.__Canon, mscorlib]](System.Linq.Expressions.Expression, System.String, Boolean, System.Collections.Generic.IEnumerable`1<System.Linq.Expressions.ParameterExpression>)
000000000674d630 000007fedf397cf0 System.Linq.Expressions.Expression.Lambda[[System.__Canon, mscorlib]](System.Linq.Expressions.Expression, Boolean, System.Collections.Generic.IEnumerable`1<System.Linq.Expressions.ParameterExpression>)
000000000674d680 000007ff001e23ee ASP._Page_Views_Home_MvcPage_cshtml.Execute()
000000000674d810 000007feeba93c18 System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
Conclusion
The model binding in MVC 3 is using reflection and the Lamda expression because of the call to HTML.DisplayFor.
This is different from the Web Forms code which is just looping through the class.
So it’s hard to compare these as equivalent function calls because they are compiled differently.
This shows that when considering a new approach to your code you need to understand what it is doing underneath the covers.
The end result for this customer was then when they built out a test environment they saw equivalent levels of performance between MVC and Web Forms and in some cases MVC was faster.
I also think that Asp.Net MVC helps with test driven development so this might lead to identify performance issues early on.