ASP.Net MVC Routing issue - asp.net-mvc

I've got an issue with MVC routing(or at least I think it's w/routing :) )...
Just upgraded to MVC RC1, but I'm not sure that it's related as this is my first attempt at setting a MapRoute and corresponding RouteLink.
here's the route:
routes.MapRoute("Test1",
"Forecast/CurrentLineItems/{propertyID}/{forecastYear}/{forecastMonth}",
new { controller = "Forecast", action = "CurrentLineItems", propertyID = "", forecastYear = "", forecastMonth = "" }
);
here's the RouteLink...in the view it's wrapped in a table cell:
Html.RouteLink(Html.Encode(myProperty.Description),"Test1", new { controller = "Forecast", action = "CurrentLineItems", propertyID = myProperty.PropertyID.ToString(), forecastYear = "2008", forecastMonth = "10" })
here's a snippet from the controller:
namespace AnApplication.Controllers
{
[HandleError]
[Authorize]
public class ForecastController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult CurrentLineItems(string propertyID, string forecastYear, string forecastMonth)
{
//Some code
}
Now for the strange behavior, when I click the link specified by the RouteLink, the app enters the CurrentLineItems method and all the method arguments are correct...
then it enters the CurrentLineItems method again!
with, for instance, these arguments:
propertyID = "scripts"
forecastYear = "jquery-1.2.6.js"
forecastMonth = ""
It then repeats this several times as it seems to run through all the scripts on this view and the Site.Master and then the last one is the .css file for this page!
What is going on!
The Call Stack is of no help as it lists the above-mentioned CurrentLineItems method and then below that is the dreaded [External Code]
When I profile the page/view in FireFox/FireBug all I see are the jQuery calls
Here's the html from the Site.Master re the scripts
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title><%= Html.Encode(ViewData["Title"]) %></title>
<script type="text/javascript" src="../../scripts/jquery-1.2.6.js"></script>
<script type="text/javascript" src="../../scripts/calculations.js"></script>
<script type="text/javascript" src="../../scripts/common.js"></script>
<style media="all" type="text/css">#import "../../Content/all.css";</style>
<!--[if IE]><link rel="stylesheet" type="text/css" href="../../Content/ie.css"media="screen"/><![endif]-->
<!--<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />-->
</head>
here's a snippet from the view re the scripts
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"AutoEventWireup="true" CodeBehind="CurrentLineItems.aspx.cs" Inherits="AnApplication.Views.Forecast.CurrentLineItems" %>
<asp:Content ID="lineItemsContent" ContentPlaceHolderID="MainContent" runat="server">
<!--<script type="text/javascript" src="../../scripts/MicrosoftAjax.debug.js"></script>-->
<script type="text/javascript" src="../../scripts/lineItems.js"></script>
<script type="text/javascript" src="../../Scripts/jquery.formatCurrency.js"></script>
<!--<script type="text/javascript" src="../../scripts/jquery-1.2.6.min.js"></script>-->
Note that this ActionLink works fine(It's basically just a menu item used for testing and the three arguments are set in the code inside the controller...):
<%= Html.ActionLink("Line Items", "CurrentLineItems", "Forecast")%>
Any help in solving this issue is greatly appreciated.
Thanks,
Greg

There were in fact two subtle, yet annoying bugs in the recently release ASP.NET MVC Release Candidate. These were the two bugs:
We changed all our URL-rendering methods to render relative URLs instead of absolute URLs. While we feel this might be the right decision in general, we found that it breaks an awful lot of scenarios. AJAX scenarios were especially affected since the URLs for retrieving data asynchronously are often different from the original URL as seen in the browser's address bar.
Html.RouteLink specifically (and not Html.ActionLink) had a bug in it (so it is not in fact a red herring - at least not necessarily). Html.RouteLink would erroneously take the "current" controller and action and pass those values into the routing system. Only Html.ActionLink is supposed to do that. Html.RouteLink is not supposed to do any processing at all. It's supposed to just take the values you give it and pass it along to the ASP.NET Routing system.
Since these two bugs were both pretty bad, we decided to roll back the change that caused #1 and to fix the issue that caused #2 and release an updated ASP.NET MVC Release Candidate Refresh.
You'll see some posts on ScottGu's blog, Phil Haack's blog, and the ASP.NET MVC Forums detailing the refresh.
Thanks,
Eilon

It could be caused by the usage of relative paths to include the scripts.
When you click on the link the URL of the new page will be something like http://[server]/Forecast/CurrentLineItems/xxx/2009/1
Now check the html code in the browser: If the URLs are in the form of '../../scripts/jquery-1.2.6.js', this will cause the browser to load it from http://[server]/Forecast/CurrentLineItems/scripts/jquery-1.2.6.js - and so your controller gets called again.
If that is the case you could either use absolute paths ('/scripts/jquery-1.2.6.js') or use a path relative to the application root ('~/scripts/jquery-1.2.6.js') and resolve it on the server side with Page.ResolveClientUrl().
Maybe there has been a change from Beta to RC1, so that the URLs in the head, even with runat="server", don't get remapped.

RouteLink versus ActionLink is a red herring here. The only thing that matters is the rendered a href="[something]". You would get exactly the same results if you wrote the a href manually instead of generating it via RouteLink.
So, yes, now we're down to routing. Inside your controller action, inspect RouteData in the debugger, and see which route name was matched. Chances are very likely it's the wrong one, and that is causing other things to misbehave. Either change the order of your reps, or add constraints to prevent the wrong one from being matched.
RouteLink works very well to prevent finding the wrong route when you're generating a URL. But when your application is consuming a URL, you still need to have your routes in order in global.asax.

Related

Cannot Ignore ASHX Route

I have an MVC web app, which inherits and is part of a Webform framework. Part of the webform framework outputs several ASHX handlers. Which, I cannot remove with routes.clear(), because that will remove them all together. I still need to use them.
Here is where my problem comes in, I have, <link href="#Url.Action("Index", "Styles")" rel="stylesheet" type="text/css" /> This points to a controller which generates dynamic CSS Style Attributes.
When the page renders the Url.Action looks like this -
<link href="/MyLayout/Handlers/LoginStyleHandler.ashx?
action=Index&controller=Styles" rel="stylesheet" type="text/css">
As far as trying to ignore this route here is what I have tried. None of these work. What gives? What can I do to keep the ASHX path out of my Url.Action HTML Helper.
routes.IgnoreRoute("{Handlers}.ashx");
routes.IgnoreRoute("{Handlers}/{resource}.ashx/{*pathInfo}");
routes.IgnoreRoute("TMW_LayoutPrototype/Handlers/LoginStyleHandler.ashx");
routes.IgnoreRoute("{*allaspx}", new { allaspx = #".*\.aspx(/.*)?" });
routes.IgnoreRoute("Handlers/LoginStyleHandler.ashx");
routes.IgnoreRoute("{*allashx}", new { allashx = #".*\.ashx(/.*)?" });
Update
Using the Regex tester at http://regexpal.com/ let me know that my Regex pattern #".*\.ashx(/.*)?" is valid. However it does not remove the handler from the MVC side of the web application.
Conclusion
The root of my problem stemmed from the web forms side of the application. Low-and-behold, inside the framework the MVC app inherited from laid a routing engine. So, my routes in MVC were getting placed at the bottom of the stack and the Webform routes were at the top.
The ignore routes did not work, because the routes were being created on the application_start event. Ignoring nor placing my MVC routes at the top of the stack did not work, because then I would lose Session, as well as, forms auth.
The solution was writing a constraint in the Webform routing code to stay out of my MVC application. After that all was fine in the MVC kingdom.
try routes.Ignore instead of routes.IgnoreRoutes
Some of my handlers mapped to sub directories, so I went with the regex which has to be a full match instead of a partial
routes.Ignore("{*legecy}", new { legecy = #".*\.(aspx|ashx|asmx|axd|svc)([/\?].*)?" });

ASP.NET MVC Routing

I have a requirement which lets the user search for properties using friendly urls. I created this route
routes.MapRoute(
"Search", // Route name
"{controller}/{action}/refno/{refno}/status/{status}/proptype/{proptype}/locality/{locality}/pricefrom/{pricefrom}/priceto/{priceto}", // URL with parameters
new { controller = "Property", action = "Search", refno = "0", proptype = "ANY TYPE", status = "ANY STATUS", locality = "ANY LOCALITY", pricefrom = "0", priceto = "NO LIMIT" }
and the request action="/Property/Search/refno/0/status/Converted/proptype/Airspace/locality/Any locality/pricefrom/0/priceto/No limit" actually calls the route correctly.
However there is something strange. When the search form with the results is shown the javascript script tags are being ignored and therefore some dropdowns which are filled through javascript are not being filled. I am of the impression that the routing which I have created actually blocks the script tags which I have in the master page.
Can someone help me.
Your browser is looking for the .js files relative to the URL. Make sure your script tags are referencing your .js files with Url.Content("~/somepath/file.js").
If that isn't the problem, make sure that there is not a javascript error someplace on your page that is blocking the rest from executing.
HI,
First of all thanks for the reply. There are no JS errors for sure sinc the JS file is being called on a user control which always works.
I just ran the website on IIS express to trace the website. It seems that the correct post is being done, but then on the results form the js files are being requested as the search post and the js file.
For example.... if I post to /search/properties/refno/5/propstatus/finished, a correct post is first made, then when the results form is being rendered a GET action is being done to /search/properties/refno/5/propstatus/finished/jquery/jquery0.5.2.min.js which is incorrect.
To give you an idea of what I am doing. This is the user control. I am only posting the form action. Right now I am posting to a static route just to test.
action="/Property/Search/refno/0/status/Converted/proptype/Airspace/locality/Any locality/pricefrom/0/priceto/No limit" method="post">
This is the master page header js files
<script type="text/javascript" src="../../Scripts/jquery/001-jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="../../Scripts/jquery/jquery.lightbox-0.5.min.js"></script>
<script type="text/javascript" src="../../Scripts/utils/jquery.markaf.mvc.utils.debug.js"></script>
<script type="text/javascript" src="../../Scripts/ui/ui.search.debug.js"></script>
And this is the search form which the controller posts to
Search
<% foreach (var item in Model)
{ %>
Ref No : <%:item.RefNo %>
<%:item.Description %>
Price : <%:item.Price.ToString("c") %>
">Read more...
<%} %>

Embed ContentPlaceHolder between double quotes

I am using .NET MVC, and within view pages I set a contentplaceholder that contains an ID to be used on the master page like so:
View page:
<asp:Content ID="CDomBodyId" ContentPlaceHolderID="DomBodyId" runat="server">LmpDemoRequests</asp:Content>
Master page:
<body id='<asp:ContentPlaceHolder ID="DomBodyId" runat="server"></asp:ContentPlaceHolder>'>
So in this particular case, the body tag would render like this on the final HTML page:
<body id='LmpDemoRequests'>
I would like to have double quotes around the body id tag, but inverting the quotes like the following makes intellisense unable to find the contentplaceholder, giving me a lot of warnings when I compile.
<body id="<asp:ContentPlaceHolder ID='DomBodyId' runat='server'></asp:ContentPlaceHolder>">
Is there any way around this?
This is an issue with ASP.NET editor. It's not specific to MVC. I think the workaround is pretty good and I don't see a specific drawback.
Try declaring BodyID as a property of your MasterPage. Set its value in the View pages. Then you can do something like
<html>
<body='<%= BodyID %>'>
</body
</html>
Not sure if I have misunderstood your question, but you can also add:
<body id="site" runat="server"></body>
And then access it on your page
HtmlControl body = (HtmlControl)Master.FindControl("site");
body.Attributes.Add("class", "LmpDemoRequests");
I hope I understood your correctly.

UrlHelper extension method call encoded not executed

I created a simple extension method for the ASP.NET MVC UrlHelper. It takes no arguments as its job is to look up the name of a stylesheet file from the configuration and return a url to the stylesheet. The extension method looks roughly like this:
public static string SiteStylesheet(this UrlHelper urlHelper)
{
var scriptFilename = UserInterfaceConfiguration.GetSection()
.Mvc.SiteStylesheet;
return urlHelper.Content(string.Format("~/Assets/Scripts/{0}",
scriptFilename));
}
And I use it like this:
<link href="<%= Url.SiteStylesheet() %>" rel="Stylesheet" type="text/css" />
The method does not get executed, however, and the following is rendered:
href="../Views/Shared/%3C%25=%20Url.SiteStylesheet()%20%25%3E"
As you can see the extension method is not executed, rather the entire thing is just encoded. If I change the method signature to accept a parameter:
public static string SiteStylesheet(this UrlHelper urlHelper, string dummy)
then the extension method is executed and the output is as expected:
href="/Assets/Stylesheets/FluidCMS.css"
So my question then is this by design or is this a bug in the ASP.NET MVC Web Form view engine?
This issue has come up a number of times. The root of the issue is that the <head> tag has runat="server", which causes the parser to treat tags as server tags.
The simplest workaround is to just remove runat="server" from the head tag. What you lose is the logic that makes the link URL's relative to the current page, but since you're using your helper anyway, you have no need for this.
When I had this problem, it was because my extension methods were in a namespace that wasn't specified in the web.config.
<add namespace="Your.Extension.Method.Namespace"/>
It goes under configuration\system.web\pages\namespaces
I think you found a bug!
I tried it and found this only happens in the head section of a Master Page and only in the <link> tags (<script> tags render fine).
The problem obviously is the text inside de href attribute is not correctly interpreted as a code nugget.
This goes beyond ASP.NET MVC. I tried it in a Master Page in a classic Web Form ASP.NET site and the problem persists. It seems to be a bug in the Web Form rendering engine or something like that.

Script & CSS Registration Helper in ASP.NET MVC?

I have tried to use ASP.NET MVC for a while, then I face a problem that I don't want to include all of my js and css in master page. But how can I register it in head of master page from my specific view?
The default master page template includes a Content PlaceHolder for the head. If it doesn't you can easily add one:
<head runat="server">
<title></title>
<asp:ContentPlaceHolder ID="head" runat="server" />
</head>
Your views can then put anything they want in the head:
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<script src="Scripts/myScripts.js" type="text/javascript"></script>
<link href="Styles/myStyles.css" rel="stylesheet" type="text/css" />
</asp:Content>
It doesn't look like there's a simple option 'built-in' to the ASP.NET MVC framework just yet. If you are using a user control (.ascx), which you may be if you are creating self-contained controls which also want to manage their own JavaScript requirements, then you can't even use the placeholders to help you out.
In the end I created a helper class and in it there are a couple of methods:
private static SortedList<int, string> GetRegisteredScriptIncludes()
{
var registeredScriptIncludes = System.Web.HttpContext.Current.Items["RegisteredScriptIncludes"] as SortedList<int, string>;
if (registeredScriptIncludes == null)
{
registeredScriptIncludes = new SortedList<int, string>();
System.Web.HttpContext.Current.Items["RegisteredScriptIncludes"] = registeredScriptIncludes;
}
return registeredScriptIncludes;
}
public static void RegisterScriptInclude(this HtmlHelper htmlhelper, string script)
{
var registeredScriptIncludes = GetRegisteredScriptIncludes();
if (!registeredScriptIncludes.ContainsValue(script))
{
registeredScriptIncludes.Add(registeredScriptIncludes.Count, script);
}
}
public static string RenderScripts(this HtmlHelper htmlhelper)
{
var registeredScriptIncludes = GetRegisteredScriptIncludes();
var scripts = new StringBuilder();
foreach (string script in registeredScriptIncludes.Values)
{
scripts.AppendLine("<script src='" + script + "' type='text/javascript'></script>");
}
return scripts.ToString();
}
That's a basic form of it anyway to try and show the way it works. It could be enhanced in many ways, but at the moment it just filters out duplicate script insert requests for you. Whenever you want to add a new script in the ascx (or aspx for that matter) you can do it this way:
<%
Html.RegisterScriptInclude(Url.Content("~/Scripts/MapLayers/MapLayer.js"));
Html.RegisterScriptInclude(Url.Content("~/Scripts/MapLayers/Vehicles.js"));
%>
Then you need to remember to output them once you're done. This is achieved by making the following call at the place in your page where you want to output the script tags:
<%=Html.RenderScripts() %>
Seems to work so far for me. I did half expect to have rendering issues depending at what point RenderScripts was called, especially if not all of the RegisterScriptIncludes had been called yet, but so far it seems to do the job. If you render the scripts last then you should have no problems.
#Jason: WARNING, you shouldn't be using static variables like this... In a web context static variables are shared across all users and all page requests. I am amazed that you haven't run into trouble with your code. The principle is fine but the code is wrong and will give you trouble. In this case you should be using System.Web.HttpContext.Current.Items. See http://www.hanselman.com/blog/ATaleOfTwoTechniquesTheThreadStaticAttributeAndSystemWebHttpContextCurrentItems.aspx for more.
Here is a solution similar to the one Jason gave, but takes vdh_ant's comments into consideration:
http://frugalcoder.us/post/2009/06/29/Handling-Scripts-in-ASPNet-MVC.aspx
technically you should be putting all your js at the bottom of the page for the best performance.
I think the only way you could do this though would be to include the javascript in the VIewData and have the ViewData displayed on the masterpage (not a great solution).
MVC Futures now has built in helpers for this...
1.<head>
2. <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
3. <%= Html.Css("BlueTheme/site.css") %>
4. <%= Html.Script("jquery-1.3.2.js") %>
5.</head>
More information here: http://blog.osbornm.com/archive/2009/10/12/mvc-script-css-helpers.aspx
Mathew,
I took a look at your blog on Html.Css and Html.Script helpers. I don't mean to be critical but I do not see any mention in the blog about how the Css and Script helpers would address the problem discussed here. The problem here is one of needing to "registered" script references at any point during the rendering process, and possibly multiple times (in the case of an template or partial view that is used several times and registers its own scripts), and then outputting the aggregate results, sans duplicates, in a single location.
If your solution addresses this, please correct me with some clarification.
--Regards,
Ken
I wrote just such a manager for MVC, and wrote about it on my blog:
"JavascriptHelper–Managing JS files for ASP.NET MVC"
UPDATE: I added bundling:
"JavascriptHelper:Managing JS files for ASP.NET MVC (With Bundling)"

Resources