Performance bottleneck Url.Action - can I workaround it? - asp.net-mvc-3

I have an application that I recently upgraded from ASP.NET MVC1 to ASP.NET MVC4 rc1.
It uses the Webforms viewengine.
It has performance issues whenever Url.Action(action,controller) is used.
I can reproduce the issue in ASP.NET MVC3.
I need 3ms to render views that have 10 instances of the Url.Action helper in it in ASP.NET MVC1 and 40ms to render the same in ASP.NET MVC3.
I already found some ways to make it render faster:
I moved the default route to the top
I removed Url.Action and used static links
This does not feel right: the application is pretty large and I need the goodness of a decent working routing in it. I am also not confident that I found all performance bottlenecks. Routing is a central part of MVC: if there is something performing badly it will pop up in different parts of the application.
I have the impression that MVC3 introduced some routing features (like regex constraints) that even if I dont use them lead to a badly performing application.
Is there something I can do like turning of features of routing or using a different set of URL-helpers?
This code reproduces the issue:
Index action
public ActionResult Index()
{
return View();
}
index.aspx
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head >
<title></title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="page">
<%= Url.Action("Action1", "Controller1") %>
<%= Url.Action("Action2", "Controller2") %>
<%= Url.Action("Action3", "Controller3") %>
<%= Url.Action("Action4", "Controller4") %>
<%= Url.Action("Action5", "Controller5") %>
<%= Url.Action("Action6", "Controller6") %>
<%= Url.Action("Action7", "Controller7") %>
<%= Url.Action("Action8", "Controller8") %>
<%= Url.Action("Action9", "Controller9") %>
<%= Url.Action("Action10", "Controller10") %>
</div>
</body>
</html>
Route registration
This looks strange: but I just want to simulate my not very complicated routing. This is not the 600 routes of SO!
public static void RegisterRoutesSlow(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{language}/Content/{*pathInfo}");
routes.IgnoreRoute("images/{*pathinfo}");
routes.IgnoreRoute("scripts/{*pathinfo}");
routes.IgnoreRoute("content/{*pathinfo}");
routes.IgnoreRoute("{file}.gif");
routes.IgnoreRoute("{file}.jpg");
routes.IgnoreRoute("{file}.js");
routes.IgnoreRoute("{file}.css");
routes.IgnoreRoute("{file}.png");
routes.IgnoreRoute("{file}.pdf");
routes.IgnoreRoute("{file}.htm");
routes.IgnoreRoute("{file}.html");
routes.IgnoreRoute("{file}.swf");
routes.IgnoreRoute("{file}.txt");
routes.IgnoreRoute("{file}.xml");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
for (int i = 0; i <= 10; i++)
{
routes.MapRoute(
// Route name
"RouteName" + i.ToString(),
// URL with parameters
"{language}/{controller}/{action}/{para1}",
// Parameter defaults
new
{
action = "Index",
language = "de",
para1 = 0
},
//Parameter constraints
new { language = "de|en", controller = "SomeNameOfAnActualController" + i.ToString() }
);
}
routes.MapRoute(
"DefaulRoute", // Route name
"{controller}/{action}", // URL with parameters
new
{
controller = "Home",
action = "Index",
}
);
routes.MapRoute("404-PageNotFound", "{*url}", new { controller = "Error", action = "PageNotFound", language = "de" });
}
EDIT
The sample code was compiled against MVC2 now. In VS2010 MVC2 can be compiled against .NET 3.5 or 4.0.
The performance with 3.5 is good and 4.0 is bad.
I guess this means that the poorly performing part is not in a MVC assembly but in a framework assembly (like System.Web.Routing.dll). The question is still the same: Can I do something about it? An accepted answer would also be: No, the code is slow because from version 3.5 to 4.0 MS changed XXX
EDIT-2
I decompiled the part of System.Web.Routing.dll that takes to long. It uses a compiled regular expression. There is a code path (constraint2.Match ) that returns without executing the regex, but I did not check yet if it internally uses a different expensive operation.
protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
object obj2;
IRouteConstraint constraint2 = constraint as IRouteConstraint;
if (constraint2 != null)
{
return constraint2.Match(httpContext, this, parameterName, values, routeDirection);
}
string str = constraint as string;
if (str == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[] { parameterName, this.Url }));
}
values.TryGetValue(parameterName, out obj2);
string input = Convert.ToString(obj2, CultureInfo.InvariantCulture);
string pattern = "^(" + str + ")$";
return Regex.IsMatch(input, pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase);
}

There are solved problem similar to yours: First call to Url.Action on a page is slow
there are conclusion about routing constraints with regexp constraints that is very slow.

Each view is compiled and cached when it is used the first time. However, since the aspx Views were not designed specifically for Mvc each Url.Action is not compilede once for all in the final link but it is re-computed at each execution. Razor compiler has better optimization.
The only solution is computing the various linkks with Url.Action and storing them into some application level property, so it is computed just at first execution. You can put them either in the Application dictionary, or in static properties of a class.

I am not sure to the cause of what you are seeing, but it may not only be an MVC 1 vs MVC 4, IIS setup in later versions can impact the speed of view rendering. I came across a slide deck a few months ago that I thought was pretty interesting, relating to performance improvement tips in MVC 3 apps.
http://www.slideshare.net/ardalis/improving-aspnet-mvc-application-performance
Specifically, take a look at slide 28, which states:
Uninstall IIS UrlRewrite Module
If no applications on the server are using it
No effect in MVC apps before v3
Enhances speed of URL generation
I take this to mean that the UrlRewrite module will negatively impact MVC 3, but not MVC 2 or 1, which could be a source of the slowdown that you are seeing. There are some other improvements as well, but I don't believe that any of them 'directly' relate to what you are seeing.

Related

How to use knockout.js in MVC 3 Razor?

i am newbie knockout.js. Also i ama upper intermadiate in asp.net mvc 3. i really want to learn how to use knockout js in mvc 3 razor? but below code is not working also return to me empty total price. There is no error. Help please thanks...
Model:
public class GiftModel
{
public string Title { get; set; }
public double Price { get; set; }
}
View:
#using System.Web.Script.Serialization;
#model IEnumerable<knockout1.Models.GiftModel>
#{
ViewBag.Title = "Index";
}
<script src="/Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script type="text/javascript">
var initialData = #(new JavaScriptSerializer().Serialize(Model));
var viewModel = {
gifts : ko.observableArray(initialData)
};
ko.applyBindings(viewModel);
</script>
<h2>Index</h2>
<p>You have asked for <span data-bind="text: gifts().length"> </span> gift(s)</p>
Controller:
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Index()
{
var initialState = new[] {
new GiftModel { Title = "Tall Hat", Price = 49.95 },
new GiftModel { Title = "Long Cloak", Price = 78.25 }
};
return View(initialState);
}
}
I guess you are following this tutorial.
You have a couple of errors. First replace:
var initialData = #(new JavaScriptSerializer().Serialize(Model));
with:
var initialData = #Html.Raw(Json.Encode(Model));
This ensures that your model is properly JSON encoded. In the original article Steven Sanderson is using the WebForms view engine but you seem to be using the Razor view engine. So make sure that you adapt your syntax accordingly (don't forget that the # razor function automatically html encodes the output contrary to the <%= WebForms syntax).
And the second problem with your code is that you attempted to bind your knockout model before your DOM is ready (i.e. you have placed the ko.applyBindings(viewModel); call before the actual span containing the bindings). So either wrap your call in a $(document).ready or place your scripts at the end of the document, just before closing your </body> tag (recommended).
Also I would recommend you using url helpers to reference your scripts, don't just hardcode those urls, your application will break as soon as you publish in IIS:
#model IEnumerable<GiftModel>
<h2>Index</h2>
<p>You have asked for <span data-bind="text: gifts().length"> </span> gift(s)</p>
<script src="#Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
<script type="text/javascript">
var initialData = #Html.Raw(Json.Encode(Model));
var viewModel = {
gifts : ko.observableArray(initialData)
};
ko.applyBindings(viewModel);
</script>
So as you can see the 2 problems you were having have strictly nothing to do with knockoutjs. So what I would recommend you if you want to learn some framework is to learn it independently. Don't mix up technologies or you will get mixed up.
So go ahead over the knockoutjs site and start the tutorials working on static HTML pages. Forget about ASP.NET MVC for the moment. Forget about Entity Framework. Just learn the framework starting from a static HTML page. This way you will better understand how it works.

MVC Separation of Concerns

so I was going on creating my MVC 3 web app when it dawned on me that I might be putting entirely too much logic in my Controller, that it needs to be in the Model instead. The problem with this is, that in this particular instance I'm dealing with a file.
The SQL database table stores the path of the file, and the file itself is saved in a directory. So in the database, the file path is stored as an nvarchar, and in the model, the file is a string, everything's consistent to that point. The issue comes when it's time to upload the file, at that point I'm dealing with a System.IO.File.
So the question is, how do you provide System.IO.File logic inside the model for the file when in the back-end it is actually a string?
I had finished the functional version of the Controller and had some logic already in it, and was about to add more when I realized that I was working against the system. What I mean is that in order to have server-side validation, the logic needs to be in the Model in order for the input validation to behave and work according to proper MVC rules, obviously optionally using client-side validation in conjunction.
Currently...
Here is my View:
#model ProDevPortMVC3.Profile
#{
ViewBag.Title = "Profile Photo Upload";
}
<h2>Photo Upload</h2>
<img alt="Profile Image" src="#Html.DisplayFor(model => model.ProfilePhotoPath)" />
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("UploadPhoto", "Profile", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<br />
<input type="file" name="File1" />
#Html.ValidationMessageFor(model => model.ProfilePhotoPath)
<input type="submit" value="Upload" />
}
Here is my Controller (just the relevant action method):
[HttpPost]
public ActionResult UploadPhoto(int id, FormCollection form)
{
Profile profile = db.Profiles.Find(id);
var file = Request.Files[0];
if (file != null && file.ContentLength > 0)
{
try
{
string newFile = Path.GetFileName(file.FileName);
file.SaveAs(Server.MapPath("/Content/users/" + User.Identity.Name + "/" + newFile));
profile.ProfilePhotoPath = "/Content/users/" + User.Identity.Name + "/" + newFile;
UpdateModel(profile);
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
return View();
}
And here is my Model (just the part relevant to the file):
public string ProfilePhotoPath { get; set; }
So I guess, what are your guys' solutions in these particular situations?
Description
Assuming i understand your question. I have read your question a few times. ;) If i don't understand, please comment my answer in order to get a better answer (i will update)
I think that you want is.. How to Model Validation for your particular case.
You can add Model Validation errors using the ModelState.AddModelError("Key", "Message) method.
ModelState.AddModelError Adds a model error to the errors collection for the model-state dictionary.
Sample
ModelState.AddModelError("ProfilePhotoName", "YourMessage");
This will affect ModelState.IsValid
So you can do whatever you want (your logic) and can make your Model invalid.
More Information
MSDN - ModelStateDictionary.AddModelError Method
There are any number of answers to this question. I'll take a crack at it knowing the risk going in due to varying opinion. In my personal experience with MVC3 I like to use flatter, simpler Models. If there is validation that can be done easily in a few lines of code that doesn't require external dependencies then I'll do those in the Model. I don't feel like your System.IO logic is validation, per se. Validation that could go in the Model, in my mind, is whether the filename is zero length or not. The logic to save is something you can put in your controller. Better yet, you could inject that logic using the Inversion of Controller pattern and specifically a Dependency Injection solution.

MVC 3: Add usercontrol to Razor view

I have a DLL that contain a user control inside, in the Web Form view i can easily use it by using
<%# Register Assembly = "..." Namespace = "..." TagPrefix = "..." %>
But how to do it in Razor view?
You can't add server side controls to Razor views. In general it is very bad practice to do so anyways in an ASP.NET MVC application. Due to the heritage of WebForms view engine you could violate this rule but in Razor things have been made clearer.
This being said you could still do some pornography in Razor and include a WebForms partial which will contain the user control (totally not recommended, don't even know why I am mentioning it but anyway):
#Html.Partial("_Foo")
where in _Foo.ascx you could include server side controls:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<%# Register Assembly="SomeAssembly" Namespace="SomeNs" TagName="foo" %>
<foo:SomeControl runat="server" ID="fooControl" />
Also, not recommended, but you can render a control in code, like in an HTML Helper:
public static string GenerateHtmlFromYourControl(this HtmlHelper helper, string id)
{
var yourControl = new YourControl();
yourControl.ID = id;
var htmlWriter = new HtmlTextWriter(new StringWriter());
yourControl.RenderControl(htmlWriter);
return htmlWriter.InnerWriter.ToString();
}
and then you can reference it from your view:
Html.GenerateHtmlFromYourControl("YourControlId")
Just make sure you set up/reference your namespaces correctly to do this.
Caveat
FYI, I'm pretty sure there are some severe limitations regarding the Page Lifecycle here...

Using Page.User.Identity.Name in MVC3

In MVC2 I have used Page.User.Identity.Name using the <%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
How can I use the same in MVC3?
You can always do something like:
#Html.ViewContext.HttpContext.User.Identity.Name
but don't.
Normally a view shouldn't try to fetch such information. It is there to display whatever information is passed by the controller. It should be strongly typed to a model class which is passed by a controller action.
So in the controller action rendering this view:
[Authorize]
public ActionResult Index()
{
var model = new MyViewModel
{
Username = User.Identity.Name
}
return View(model);
}
Now inside the view feel free to use this information:
#Model.Username
MVC 2
<%: this.Page.User.Identity.Name %>
MVC 3
#this.User.Identity.Name
I had the same problem. I used this tutorial to solve this issue.
In short, in your view, put this code:
#Context.User.Identity.Name
Just make sure that the project is set to authenticate with windows.

MVC Transfer Data Between Views

I just started to learn MVC and am trying to understand how it works.
I don't want to send users to different views for all edit, insert and list operations.
In my sample application a View contains a list of items and below the list there is a form (for inserting new items) with action "{Controller}/Create" but there is no Create View.
When a user inserts a new item it posts to the Create action with httpverb post and creates the item and returns back to the List action with RedirectToAction method.
But I can not show any message(error, information etc) to the user in this style because I can not pass data between Create action and List action. How can I do that?
You need to use Post Redirect Get PRG pattern.
Please read this Use PRG Pattern for Data Modification section in this blog post by Kazi Manzur Rashid.
http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx
This approach uses TempData to maintain ModelState data between redirects.
[HttpPost, ValidateAntiForgeryToken, ExportModelStateToTempData]
public ActionResult Create(FormCollection form)
{
Product p = new Product();
if (TryUpdateModel<IProductModel>(p))
{
productRepository.CreateProduct( p );
}
else
{
// add additional validation messages as needed
ModelState.AddModelError("_generic", "Error Msg");
}
return RedirectToAction("Index");
}
And here is your Index action method.
[ImportModelStateFromTempData]
public ActionResult Index()
{
IList<Product> products = productRepository.GetAll();
return View("Index", products);
}
And here is your Index view.
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<Product>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Products</h2>
<% foreach (var p in Model) { %>
<div><%= Html.Encode( p.ProductName ) %></div>
<% } %>
<%= Html.ValidationSummary("Please correct the errors", new { id = "valSumCreateForm" }) %>
<% using (Html.BeginForm("Create", "Product")) { %>
Product Name: <%= Html.TextBox("ProductName") %>
<%= Html.AntiForgeryToken() %>
<% ViewContext.FormContext.ValidationSummaryId = "valSumCreateForm"; %>
<% } %>
</asp:Content>
The ImportModelStateFromTempData
and ExportModelStateToTempData
attributes helps transfer model
state errors between redirects. This
<% ViewContext.FormContext.ValidationSummaryId = "valSumCreateForm"; %> associates the MVC Form with its corresponding Validation Summary.
You can check another answer by me on this here as well.
ViewModel with SelectList binding in ASP.NET MVC2
Let me know if you have any question.
-Soe
Most MVC frameworks have the ability to temporarily store a small bit of data just through the next request, for just this purpose. In ASP.NET MVC its called TempData, in Rails it's called :flash, etc.
This article explains how to use TempData:
One of the more annoying things to
deal with in Web programming is errors
on a form. More specifically, you want
to display error messages, but you
want to keep the previously entered
data. We've all had the experience of
making a mistake on a form that has 35
fields, only to be presented with a
bunch of error messages and a new,
blank form. The MVC Framework offers TempData as a place to store the previously entered information so that the form can be repopulated. This is
something that ViewState actually made
very easy in Web Forms, since saving
the contents of controls was pretty
much automatic. ... TempData is a dictionary,
much like the untyped ViewData.
However, the contents of TempData only
live for a single request and then
they're deleted.

Resources