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.
Related
I have a problem with href of link tag used in my view model like <link th:href="#{css/todo/index}" rel="stylesheet" />.
This work nice on a controller action like example.test/todos but doesn't work when I try with a same action that just have a different URI like example.test/todo/create.
The problem is I don't have right links href to static files (css ; images ; and js). In the first action URI is example.test/css/todo/index.css but is example.test/todo/css/todo/create.css on the second action.
How to fix this ?
I using spring 2.2 and thymeleaf
I have finally fix my problem by rewriting all links with a / first
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)([/\?].*)?" });
i'm using the MVC Sitemap Provider 4 and want to show always the canonical tag, not only when a second link is existing. Is there somebody who can help me?
The best way to do this is to edit (or create another) template to build the canonical tag. If you installed using the NuGet package, in your /Views/Shared/DisplayTemplates folder you will have a CanonicalHelperModel.cshtml file (or with a .ascx extension if you are using webforms instead of razor). The default looks like this:
#model MvcSiteMapProvider.Web.Html.Models.CanonicalHelperModel
#using System.Web.Mvc.Html
#using MvcSiteMapProvider.Web.Html.Models
#if (!String.IsNullOrEmpty(Model.CurrentNode.CanonicalUrl)) {
<link rel="canonical" href="#Model.CurrentNode.CanonicalUrl" />
}
You can change it so it uses the URL property if no canonical URL was resolved by adding an else block.
#model MvcSiteMapProvider.Web.Html.Models.CanonicalHelperModel
#using System.Web.Mvc.Html
#using MvcSiteMapProvider.Web.Html.Models
#if (!String.IsNullOrEmpty(Model.CurrentNode.CanonicalUrl)) {
<link rel="canonical" href="#Model.CurrentNode.CanonicalUrl" />
} else {
<link rel="canonical" href="#string.Format("http://www.mysite.com{0}, Model.CurrentNode.Url)" />
}
You may need to devise a better way to get the base URL (domain name and http/https) and reliably concatenate it with the URL property, as the canonical tag requires an absolute URL. You could just grab the current Request.RawUrl instead of Model.CurrentNode.Url, but you will need to ensure it is properly sanitized (and HTML encoded) before displaying it on the page to prevent XSS attacks. However, if you use the Request.RawUrl just to get the base URL (by loading it into a Uri object and then using the base URL parts), you should be fine.
This will only work for pages that have a matching node defined in the SiteMap. If reading the nodes from an external source such as a database, you should ensure that each record in the database has a node in the SiteMap so there is a one-to-one correlation (at least for the pages you want indexed in search engines). You should not be using preservedRouteParameters in this case. For more information on that subject see How to Make MvcSiteMapProvider Remember a User's Position.
On a side note, there really is no need to do what you are asking because the only pages that require canonical tags are the duplicates.
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.
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)"