How to leave URL parameters unescaped in ASP.NET MVC? - asp.net-mvc

I've noticed the returnurl URL parameter on the Stackoverflow login/logout links are not escaped but when I try to add path as a parameter to a route it gets escaped.
So /login?returnurl=/questions/ask shows /login?returnurl=%2fquestions%2fask and it's kind of ugly. How do I get it to not escape the returnurl value?
Here's what I'm doing in the code:
Html.ActionLink("Login", "Login", "Account", new { returnurl=Request.Path }, null)

How do I get it to not escape the
returnurl value
How's about this?
var url = Url.Action("Login", "Account", new {returnurl = Request.Path});
var unEncodedUrl = HttpUtility.UrlDecode(url);
Response.Write("<a href='" + unEncodedUrl + "'>...</a>");
Be sure that's what you want though, URL encoding has its purpose.

I understand one of the comments about encoding happening for a reason; this would only be an exception, not the rule.
Here's what I put together, how can it be improved?
public static string ActionLinkNoEscape(this HtmlHelper html, string linkText, string actionName, string controllerName, object values, object htmlAttributes)
{
RouteValueDictionary routeValues = new RouteValueDictionary(values);
RouteValueDictionary htmlValues = new RouteValueDictionary(htmlAttributes);
UrlHelper urlHelper = new UrlHelper(html.ViewContext.RequestContext, RouteTable.Routes);
string url = urlHelper.Action(actionName, controllerName);
url += "?";
List<string> paramList = new List<string>();
foreach (KeyValuePair<string, object> pair in routeValues)
{
object value = pair.Value ?? "";
paramList.Add(String.Concat(pair.Key, "=", Convert.ToString(value, CultureInfo.InvariantCulture)));
}
url += String.Join("&", paramList.ToArray());
TagBuilder builder = new TagBuilder("a");
builder.InnerHtml = string.IsNullOrEmpty(linkText) ? "" : HttpUtility.HtmlEncode(linkText);
builder.MergeAttributes<string, object>(htmlValues);
builder.MergeAttribute("href", url);
return builder.ToString(TagRenderMode.Normal);
}

The parameter is not unescaped. You'll notice the URL:
http://stackoverflow.com/users/login?returnurl=%2fquestions%2fask
does actually work - SO is reading and unescaping that parameter as normal. If you wanted to include other out-of-bounds characters such as '&' in the parameter you would still have to escape them.
The trick is merely that the '/' character in particular does not need to be %-escaped in query parameters. It does have to be escaped in other contexts such as in a path part, so URLEncode always encodes it, to be safe.
If you just want the URL to look prettier, simply escape the parameter as normal (which you must do to escape all the other characters that must be handled correctly), and then do a string replace on '%2f' with '/'.

My solutionto a similar problem was to write my own extension. After digging around in the code I couldn't find a way to do it otherwise. Yours might look like this.
public static class HtmlHelperExtensions
{
public static string LoginLinkWithReturnUrl( this HtmlHelper helper,
string linkText,
string action,
string controller,
string returnUrl,
object htmlAttributes )
{
TagBuilder builder = new TagBuilder("a");
builder.Attributes.Add( "href",
string.Format( "/{0}/{1}?returnurl={2}",
controller,
action,
returnUrl ) );
var attrDict = new RouteValueDictionary( htmlAttributes );
builder.MergeAttributes( attrDict );
builder.InnerHtml = linkText;
return builder.ToString();
}
}
I think I had the same problem making and using a UrlHelper so I went with the string.Format mechanism instead. YMMV.

I don't believe there's a way around it that's built into the framework. The actual construction of the URL happens in the System.Web.Routing.ParsedRoute.Bind method and there aren't any conditions used to prevent the escaping.
Looks like an extension method is the way to go but one that is slightly more robust than the one mentioned previously.

Related

How to encrypt URL parameters in MVC

I'm trying to encrypt the URL parameters by implementing an EncryptedActionLink that returns a link with an encrypted parameter "p" to a generic action "ResolveUrl". The controller should recieve the request and invoke the proper action, or redirect it to the actual action without showing later the unencrypted values at the address bar (RedirectToAction doesn't work because of this).
So far, I've done this extension method:
public static MvcHtmlString EncryptedActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
{
var RouteValueDictionary = new RouteValueDictionary(routeValues);
RouteValueDictionary.Add("actionName", actionName);
RouteValueDictionary.Add("noise", new Random().Next(5000,10000));
var routeValuesText = RouteTable.Routes.GetVirtualPath(null, RouteValueDictionary).VirtualPath;
var Encryption64 = new Encryption64();
var routeValuesTextCrypto = Encryption64.Encrypt(routeValuesText, "ABC123AB");
return htmlHelper.ActionLink(linkText, "ResolveUrl", controllerName, new { p = routeValuesTextCrypto }, htmlAttributes);
}
using this method, i get the following URL:
<%: Html.EncryptedActionLink("MyText", "MyAction", "MyContoller", new { Parameter1 = 123, Parameter2 = "String", Parameter3 = false }, null)%>
http://localhost:21536/MyContoller/ResolveUrl?p=iqo6yhy0Zl3jZXdMmnJ9KdvQhqCb5X6gg19%2FqZ8XUe19r5PJ6xO84plZr1GUHCHNY9h2SDO1o4CaF9W2DdmpywXooEQ1S0rNYjpnH4s3wb%2FqM8sGxoqAqyIoC%2F2nqW7U
Now, all my contollers inherits from ContollerBase. There I define the ResolveUrl Action as this:
public ActionResult ResolveUrl(String p)
{
var Encryption64 = new Encryption64();
var query = Encryption64.Decrypt(p, "ABC123AB");
if (query.Length > 2)
query = query.Substring(2);
var tokens = query.Split(new String [] { "&" }, StringSplitOptions.RemoveEmptyEntries);
var RouteValueDictionary = new RouteValueDictionary();
for (int i = 0; i < tokens.Count(); i++)
{
var centerPos = tokens[i].IndexOf("=");
RouteValueDictionary.Add(tokens[i].Substring(0,centerPos),tokens[i].Substring(centerPos+1));
}
Type thisType = this.GetType();
MethodInfo theMethod = thisType.GetMethod(RouteValueDictionary["actionName"].ToString());
var theParameters = theMethod.GetParameters();
var theParametersObject = new object[theParameters.Count()];
System.ComponentModel.TypeConverter converter = new System.ComponentModel.TypeConverter();
for (int i=0 ; i<theParameters.Count();i++)
{
theParametersObject[i] = converter.ConvertTo(RouteValueDictionary[theParameters[i].Name],theParameters[i].ParameterType);
}
return (ActionResult)theMethod.Invoke(this, theParametersObject);
}
the thing about that code is that the ResolveUrl doesn't work. First, when there are two implementatios for one action (POST/GET) then an exception is throwed. And the second thing that fails is the parameter type conversion (for exampte converting from string to an nullable type).
How can I encrypt the URL parameters? Is any of my code useful? What is the best way to do this?
if you are trying to encrypt url parameters (route values) you can use custom valuedataprovider that will automatically decrypt the value on action without showing unencrypted value in address bar.

Method not found: 'System.Web.Mvc.MvcHtmlString System.Web.Mvc.Html.LinkExtensions.RouteLink(System.Web.Mvc.HtmlHelper, System.String, System.Object)'

I have a project that it had been developed under MVC1 and after aa few months ago I upgraded it in MVC2.
Everything was well, uppon the day I needed to format my computer.
And what can goes wrong with a format? I don't know Laughing
I have installed the MVC2 after the formating,I import the existing project, I build it, and no error displayed while it been build, but from the time I've uploaded it in the production server I am getting this error.
Method not found:
'System.Web.Mvc.MvcHtmlString
System.Web.Mvc.Html.LinkExtensions.RouteLink(System.Web.Mvc.HtmlHelper,
System.String, System.Object)'.
I can't understand what caused the problem.
Certainly I assumed that is have to do with MVC1 and I referenced it, but with no luck again.
Theese are the methods that the problem is came from
public static string PagerLinks(this HtmlHelper htmlHelper, string controllerName,
string actionName, int pageCount, int pageIndex, string cssClass, string moreTextCssClass,
string currentPageCssClass, int totalProducts) {
return PagerLinks(htmlHelper, controllerName, actionName, pageCount, pageIndex, "First",
"Previous", "Next", "Last", "more pages", cssClass, moreTextCssClass,
currentPageCssClass, totalProducts);
}
public static string PagerLinks(this HtmlHelper htmlHelper, string controllerName,
string actionName, int pageCount, int pageIndex, string firstPage, string previousPage,
string nextPage, string lastPage, string moreText, string cssClass, string moreTextCssClass,
string currentPageCssClass, int totalProducts) {
var builder = new StringBuilder();
if (String.IsNullOrEmpty(controllerName))
throw new Exception("controllerName and actionName must be specified for PageLinks.");
if (pageCount <= 1)
return String.Empty;
if (String.IsNullOrEmpty(cssClass)) builder.Append("<div>");
else builder.Append(String.Format("<div class=\"{0}\">", cssClass));
builder.Append(string.Format("{0} <b>{1}</b>", "total", totalProducts));
builder.Append("<br />");
if (pageIndex != 1) {
// first page link
builder.Append(
htmlHelper.RouteLink( "First", new {
controller = controllerName,
action = actionName,
id = 1
}, new { title = firstPage }
)
);
////
///
more code
///
///
}
This is the call syntax
<%=Html.PagerLinks((string)ViewData["Controller"], "Page", (int)ViewData["TotalPages"], (int)ViewData["Page"], "theCssClass", "theMoreTextCssClass", "theCurrentPageCssClass", (int) ViewData["TotalProducts"])%>
Inside PagerLinks method exists this line that I supposed that this is ,
as the error message said, the error.
htmlHelper.RouteLink( "First", new {controller = controllerName,action = actionName,id = 1}, new { title = firstPage })
but the I am getting the error only when i call it via
<%=Html.PagerLinks((string)ViewData["Controller"], "Page", (int)ViewData["TotalPages"], (int)ViewData["Page"], "theCssClass", "theMoreTextCssClass", "theCurrentPageCssClass", (int) ViewData["TotalProducts"])%>
if I comment out the above line and replaced it with the below line just for testing, nothing happens
htmlHelper.RouteLink( "First", new {controller = controllerName,action = actionName,id = 1}, new { title = firstPage })
I'll appreciate any suggestions
Check for any refernces in your web.config and make sure it is referencing v2 for System.Web.MVC.
I got this myself when updating from MVC1 to MVC2 on:
<%=Html.ActionLink<AccountController>(a=>a.LogOut(),"Logout") %>
If you use MVC Futures (as I did in ActionLink above) or MVC Contrib, don't forget to update these to MVC2. It surely solved my problem.
Good luck/Lasse
When I had this problem, I updated my version of the System.Web.MVC.dll in
C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 2\Assemblies and then stopped/restarted my web servers and all was right with the world.

MVC RouteLink being double encoded

I have the following link defined in a page that will be built based on the route defined in the web.config
<%= Html.RouteLink(product.DisplayName, "ShopProductNames", new {Id = product.Id, ProductName = product.DisplayName.Replace(' ', '-') }) %>
I need to URL encode the DisplayName in the URL of that link, however, when I add encoding in the following way:
<%= Html.RouteLink(product.DisplayName, "ShopProductNames", new {Id = product.Id, ProductName = Url.Encode(product.DisplayName.Replace(' ', '-')) }) %>
It double encodes my DisplayName (in the URL), and I get an error in IIS.
My DisplayName property is not being encoded before it's passed to the page. Also, RouteLink does not appear to be Url encoding for the rendered link by default as it's not picking up spaces or ampersands when the page is rendered.
Anyone know what I'm doing wrong?
UPDATE: I'm actually referring to the URL generated by RouteLink, not the link text itself
UPDATE 2: here is the route I'm using
routes.MapRoute(
"ShopProductNames",
"Shop/{productName}/p/{id}/{*nameInfo}",
new
{
controller = "Product",
action = "Detail"
}
);
Look at HtmlHelper.cs file, line 140:
internal static string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues)
{
RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
VirtualPathData vpd = routeCollection.GetVirtualPath(requestContext, routeName, mergedRouteValues);
if (vpd == null) {
return null;
}
string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
return modifiedUrl;
}
Url is created by routeCollection.GetVirtualPath() method (System.Web.Routing.dll). Using Reflector you'll see that it uses Uri.EscapeDataString() internally (System.Web.Routing.ParsedRoute.Bind method)

How do I access query string parameters in asp.net mvc?

I want to have different sorting and filtering applied on my view
I figured that I'll be passing sorting and filtering params through query string:
#Html.ActionLink("Name", "Index", new { SortBy= "Name"})
This simple construction allows me to sort. View comes back with this in query string:
?SortBy=Name
Now I want to add filtering and i want my query string to end up with
?SortBy=Name&Filter=Something
How can I add another parameter to list of already existing ones in ActionLink? for Example:
user requests /Index/
view has
#Html.ActionLink("Name", "Index", new { SortBy= "Name"})
and
#Html.ActionLink("Name", "Index", new { FilterBy= "Name"})
Links: The first one looks like /Index/?SortBy=Name and The second is /Index/?FilterBy=Name
I want when user pressed sorting link after he applied some filtering - filtering is not lost, so i need a way to combine my params.
My guess is there should be a way to not parse query string, but get collection of parameters from some MVC object.
so far the best way I figured out is to create a copy of ViewContext.RouteData.Values
and inject QueryString values into it.
and then modify it before every ActionLink usage.
still trying to figure out how to use .Union() instead of modifying a dictionary all the time.
<% RouteValueDictionary tRVD = new RouteValueDictionary(ViewContext.RouteData.Values); %>
<% foreach (string key in Request.QueryString.Keys )
{
tRVD[key]=Request.QueryString[key].ToString();
} %>
<%tRVD["SortBy"] = "Name"; %>
<%= Html.ActionLink("Name", "Index", tRVD)%>
My solution is similar to qwerty1000's. I created an extension method, ActionQueryLink, that takes in the same basic parameters as the standard ActionLink. It loops through Request.QueryString and adds any parameters found to the RouteValues dictionary that are not already present (so we can overwrite the original query string if needed).
To preserve the existing string but not add any keys the usage would be:
<%= Html.ActionQueryLink("Click Me!","SomeAction") %>
To preserve the existing string and add new keys the user would be:
<%= Html.ActionQueryLink("Click Me!","SomeAction", new{Param1="value1", Param2="value2"} %>
The code below is for the two usages, but it should be pretty easy to add other overloads to match the other ActionLink extensions as needed.
public static string ActionQueryLink(this HtmlHelper htmlHelper,
string linkText, string action)
{
return ActionQueryLink(htmlHelper, linkText, action, null);
}
public static string ActionQueryLink(this HtmlHelper htmlHelper,
string linkText, string action, object routeValues)
{
var queryString =
htmlHelper.ViewContext.HttpContext.Request.QueryString;
var newRoute = routeValues == null
? htmlHelper.ViewContext.RouteData.Values
: new RouteValueDictionary(routeValues);
foreach (string key in queryString.Keys)
{
if (!newRoute.ContainsKey(key))
newRoute.Add(key, queryString[key]);
}
return HtmlHelper.GenerateLink(htmlHelper.ViewContext.RequestContext,
htmlHelper.RouteCollection, linkText, null /* routeName */,
action, null, newRoute, null);
}
<%= Html.ActionLink("Name", "Index", new { SortBy= "Name", Filter="Something"}) %>
To preserve the querystring you can:
<%= Html.ActionLink("Name", "Index",
String.IsNullOrEmpty(Request.QueryString["SortBy"]) ?
new { Filter = "Something" } :
new { SortBy=Request.QueryString["SortBy"], Filter="Something"}) %>
Or if you have more parameters, you could build the link manually by using taking Request.QueryString into account.
Use ActionLinkCombined instead of ActionLink
public static string ActionLinkCombined(this HtmlHelper htmlHelper, string linkText, string actionName,
object routeValues)
{
var dictionary = new RouteValueDictionary();
foreach (var pair in htmlHelper.ViewContext.Controller.ValueProvider)
dictionary[pair.Key] = pair.Value.AttemptedValue;
if (routeValues != null)
{
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(routeValues))
{
object o = descriptor.GetValue(routeValues);
dictionary[descriptor.Name] = o;
}
}
return htmlHelper.ActionLink(linkText, actionName, dictionary);
}
MVC4
#Html.ActionLink("link text","action",new { #id = 5, #name = "textName", #abc = "abc" })
OR
#Html.ActionLink("link text", "action", "controller", new { #id = 5, #name = "textName", #abc = "abc" }, new { #class = "cssClass" })
querystring would be like:
yourDomainRout/action/5?name=textName&abc=abc
it would have class="cssClass"

Is there a way to include a fragment identifier when using Asp.Net MVC ActionLink, RedirectToAction, etc.?

I want some links to include a fragment identifier. Like some of the URLs on this site:
Debugging: IE6 + SSL + AJAX + post form = 404 error#5626
Is there a way to do this with any of the built-in methods in MVC? Or would I have to roll my own HTML helpers?
As Brad Wilson wrote, you can build your own link in your views by simply concatenating strings. But to append a fragment name to a redirect generated via RedirectToAction (or similar) you'll need something like this:
public class RedirectToRouteResultEx : RedirectToRouteResult {
public RedirectToRouteResultEx(RouteValueDictionary values)
: base(values) {
}
public RedirectToRouteResultEx(string routeName, RouteValueDictionary values)
: base(routeName, values) {
}
public override void ExecuteResult(ControllerContext context) {
var destination = new StringBuilder();
var helper = new UrlHelper(context.RequestContext);
destination.Append(helper.RouteUrl(RouteName, RouteValues));
//Add href fragment if set
if (!string.IsNullOrEmpty(Fragment)) {
destination.AppendFormat("#{0}", Fragment);
}
context.HttpContext.Response.Redirect(destination.ToString(), false);
}
public string Fragment { get; set; }
}
public static class RedirectToRouteResultExtensions {
public static RedirectToRouteResultEx AddFragment(this RedirectToRouteResult result, string fragment) {
return new RedirectToRouteResultEx(result.RouteName, result.RouteValues) {
Fragment = fragment
};
}
}
And then, in your controller, you'd call:
return RedirectToAction("MyAction", "MyController")
.AddFragment("fragment-name");
That should generate the URL correctly.
We're looking at including support for this in our next release.
In MVC3 (and possibly earlier I haven't checked), you can use UrlHelper.GenerateUrl passing in the fragment parameter. Here's a helper method I use to wrap the functionalityL
public static string Action(this UrlHelper url, string actionName, string controllerName, string fragment, object routeValues)
{
return UrlHelper.GenerateUrl(
routeName: null,
actionName: actionName,
controllerName: controllerName,
routeValues: new System.Web.Routing.RouteValueDictionary(routeValues),
fragment: fragment,
protocol: null,
hostName: null,
routeCollection: url.RouteCollection,
requestContext: url.RequestContext,
includeImplicitMvcValues: true /*helps fill in the nulls above*/
);
}
#Dominic,
I'm almost positive that putting that in the route will cause routing issues.
#Ricky,
Until MVC has support for this, you can be a little more "old school" about how you make your routes. For example, you can convert:
<%= Html.ActionLink("Home", "Index") %>
into:
<a href='<%= Url.Action("Index") %>#2345'>Home</a>
Or you can write your own helper that does essentially the same thing.
The short answer is: No. In ASP.NET MVC Preview 3 there's no first-class way for including an anchor in an action link. Unlike Rails' url_for :anchor, UrlHelper.GenerateUrl (and ActionLink, RedirectToAction and so on which use it) don't have a magic property name that lets you encode an anchor.
As you point out, you could roll your own that does. This is probably the cleanest solution.
Hackily, you could just include an anchor in a route and specify the value in your parameters hash:
routes.MapRoute("WithTarget", "{controller}/{action}/{id}#{target}");
...
<%= Html.ActionLink("Home", "Index", new { target = "foo" })%>
This will generate a URL like /Home/Index/#foo. Unfortunately this doesn't play well with URL parameters, which appear at the end of the URL. So this hack is only workable in really simple circumstances where all of your parameters appear as URL path components.
This is a client side solution but if you have jquery available you can do something like this.
<script language="javascript" type="text/javascript">
$(function () {
$('div.imageHolder > a').each(function () {
$(this).attr('href', $(this).attr('href') + '#tab-works');
});
});
</script>
Fragment identifiers are supported in MVC 5. See ActionLink's overloads at https://msdn.microsoft.com/en-us/library/dd460522(v=vs.118).aspx and https://msdn.microsoft.com/en-us/library/dd492938(v=vs.118).aspx.

Resources