MVC: Html.ActionLink() Resolves to (Edit?Length=) instead of (Edit?Id=) - asp.net-mvc

I have created an Admin area in my MVC 4 application. Under Views Folder called UserManage, I am using Grid.MVC to list out users (sets of 5) over several pages. In the Grid I am listing out the users details for viewing by Admin, and have added:
A column containing a checkbox [ ].
A column containing an EDIT link.
A CREATE button.
A DELETE button (to be used in conjunction with the checkbox for each row).
My User Details page is under my Admin Area in the following path: Project\Areas\Admin\Views\UserManage\Index.cshtml. On my EDIT liniks, I am attempting to pull up the view at Project\Areas\Admin\Views\UserManage\Edit.cshtml and pass the individual UserID to the View to pull up that users data.
In my current code below, my browser resolves to http://localhost:62517/Admin/UserManage/Edit?Length=4 with an IIS 8.0 404 Error (Controller cannot find UserID). However if I manually change the Length to Id as follows, everything works as intended and I end up on my Edit view: http://localhost:62517/Admin/UserManage/Edit?Id=4.
Here is my grid code for the Index.cshtml page. Does anyone know what I need to modify on my Html.ActionLink() so my route will resolved to ?Id= instead of ?Length=?:
<div class="overflowPrevention">
#Html.Grid(Model).Columns(columns =>
{
columns.Add().Encoded(false).Sanitized(false).SetWidth(30).RenderValueAs(o => #Html.CheckBox("checked", false));
columns.Add().Encoded(false).Sanitized(false).RenderValueAs(o => Html.ActionLink("Edit", "Edit", "Edit", new { id = "1" }));
}).AutoGenerateColumns()
</div>

You are using the wrong overload.
#Html.ActionLink(string linkText, string actionName, string controllerName, object htmlAttributes)
instead of
#Html.ActionLink(string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
You can just pass null for htmlAttributes if you don't need to use them:
Html.ActionLink("Edit", "Edit", "UserManage", new { id = "1" }, null)
I'm not sure why you are using Edit, Edit, Edit though. I think that was a typo so I have changed it to use UserManage as the controller name.

Related

Html Action link not taking me to the correct page

On my Home/index page which is returned by my home controller, I have added an HTML Action Link:
#Html.ActionLink("Create Ticket", "Create", "CustomerTicketInfo", new { #class = "btn btn-default" })
When clicked it should take me to a Create page. This Create page is returned by my CustomerTicketInfo controller which allows the user to create a ticket and then submit the ticket.
But when I click the action link, I get a server error/ resource not found. It's trying to go to: http://localhost:61517/Home/Create?Length=18, where a Create method does not exist. It exists in the CustomerTicketInfo Controller.
I'm not sure why this fails. I am stating the Action: Create. And the controller: CustomerTicketInfo in the Action link parameters. But yet it still wants to go to the Home Controller?
What am I doing wrong?
I could just you an tag, however, I want to do this the MVC way.
You are using an overload that is yielding undesirable results:
public static MvcHtmlString ActionLink
(this HtmlHelper htmlHelper,
string linkText,
string actionName,
object routeValues,
object htmlAttributes)
param 1: link text
param 2: action
param 3: not controller, but route data (note how the length of the string "CustomerTicketInfo" == 18)
param 4: HTML attributes
Instead, you probably want this:
#Html.ActionLink( "Create Ticket", "Create", "CustomerTicketInfo", null, new { #class = "btn btn-default" } )
Note the extra parameter, which causes the correct overload to be used.
Yes, Tim's solution would solve your problem. Since it's sometimes hard to remember the correct overloads I prefer constructing the #Html.ActionLink like below so I know what parameter I'm assigning value to.
#Html.ActionLink( linkText: "Create Ticket",
actionName: "Create",
controllerName: "CustomerTicketInfo",
routeValues: null,
htmlAttributes: new
{
#class = "btn btn-default"
})
In that way it's quite readable on what's what. If we need to pass in multiple values for the routeValues then it can be done like
routeValues: new
{
type = 'whatever type',
id = 'id value'
},

Razor actionlink autogenerating ?length=7 in URL?

I have the link below on a razor page:
#Html.ActionLink("Create New Profile", "Create", "Profile", new { #class="toplink" })
It appears in thes source of view page as shown below:
Create New Profile
When I click on the link the URL is like this:
http://localhost:54876/admin/profile/create?length=7
I don't want ?length=7. Why is this auto generated?
The ActionLink override you are using matches to the (string linkText, string actionName, Object routeValues, Object htmlAttributes) override. So your "Profile" value is being passed to the routeValues parameter. The behavior of this function with respect to this parameter is to take all public properties on it and add it to the list of route values used to generate the link. Since a String only has one public property (Length) you end up with "length=7".
The correct overload you want to use is the (string linkText, string actionName, string controllerName, Object routeValues, Object htmlAttributes) and you call it loke so:
#Html.ActionLink("Create New Profile", "Create", "Profile", new {}, new { #class="toplink"})
I'm not sure the exact cause of this, but change it to:
#Html.ActionLink("Create New Profile", "Create", "Profile", new {}, new { #class="toplink" })
I don't know which overload MVC is picking when you leave off the last parameter (htmlattributes is the added one), but that will fix it. One of these days I'll investigate and figure out exactly what's going on.
Another thing to note, since you are defining the controller in the #ActionLink, which you may not need to do, for example, the view that your "Create New Profile" #ActionLink is expressed in might be "/admin/profile/index.cshtml", a view that lists existing profiles, in this case, you do not need to define the controller in the #ActionLink as the #ActionLink is already relative to the ProfileController, so your #ActionLink could be
#Html.ActionLink("Create New Profile", "Create", null, new { #class="toplink" })
I used null instead of new{} as the marked answer does, I think this is more appropriate myself. ActionLink overloads are not the most straightforward thing ever.

Edit and Create view using EditCreate.ascx partial in ASP.NET MVC

If you look at the NerdDinner example of creating and editing dinners then you see they use a partial (ViewUserControl or ASCX) DinnerForm to put the functionality of creating and editing dinners into one file because it is essential the same and they use it using RenderPartial("DinnerForm").
This approach seems fine for me but I've run into a problem where you have to add additonal route values or html properties to the Form tag.
This picks up the current action and controller automatically:
<% using (Html.BeginForm()) { %>
However, if I use another BeginForm() overload which allows to pass in enctype or any other attribute I have to do it like this:
<% using ("Create", "Section", new { modal = true }, FormMethod.Post, new { enctype = "multipart/form-data" }))
and as you can see we lose the ability to automatically detect in which View we are calling RenderPartial("OurCreateEditFormPartial"). We can't have hardcoded values in there because in Edit View this postback will fail or won't postback to the right controller action.
What should I do in this case?
What about adding the following HTML helpers:
public static MvcHtmlString CurrentAction(this HtmlHelper htmlHelper)
{
return htmlHelper.ViewContext.RouteData.Values["action"];
}
public static MvcHtmlString CurrentController(this HtmlHelper htmlHelper)
{
return htmlHelper.ViewContext.RouteData.Values["controller"];
}
Then you could go something like this:
<% using (Html.CurrentAction, Html.CurrentController, new { modal = true }, FormMethod.Post, new { enctype = "multipart/form-data" }))
Its not 100% perfect but you could also add an additional HTML helper which would streamline it a little more:
public static MvcForm BeginForm(this HtmlHelper htmlHelper, object routeValues, FormMethod method, object htmlAttributes)
{
return htmlHelper.BeginForm(Html.CurrentAction, Html.CurrentController, routeValues, method, htmlAttributes);
}
Let me know if this helps.
Cheers
I'm answering in an old thread because it came up number two on a Google search whilst I was looking for the same thing :) Found on this SO post:
Html.BeginForm and adding properties
With MVC3 (not sure about MVC2) you can pass null in for the action and controller and get the default routes that BeginForm() would use.
#Html.BeginForm(null, null, FormMethod.Post, new { enctype="multipart/form-data"})
Cheers!

ASP.NET MVC - Getting Html.BeginForm() to remember Querystring params when submitting via GET

I have a form rendered via Html.BeginForm(), it exists as a component in the Master page so that it appears on every page in the application. I have done this using Html.RenderAction() from Mvc Futures assembly. It's a simple search form that updates some items in the same component underneigh the search form itself, and performs a GET so that the search term appears in the querystring.
<div class="sideBarContent">
<h2>Search Products</h2>
<% using (Html.BeginForm(ViewContext.RouteData.Values["action"].ToString(),
ViewContext.RouteData.Values["controller"].ToString(), FormMethod.Get)) { %>
<fieldset>
<legend>Search Products</legend>
<div class="formRow">
<label for="ProductsSearch">Search</label>
<%= Html.TextBox("ProductsSearch") %>
</div>
<input type="submit" value="Search" class="button" />
</fieldset>
<% } %>
<ul>
// Products will eventually be listed here
</ul>
</div>
I need this form to do the following:
1) It should perform a GET to whatever current page it is on appending 'ProductsSearch' as a querystring parameter (eg. example.com/?ProductsSearch=test or example.com/books/fiction?ProductsSearch=test)
2) It should remember any exising querystring parameters that are already in the querystring, maintaining them after you click Search button eg. example.com/myOrders?page=2 after Search click it should go to example.com/myOrders?page=2&ProductsSearch=test)
I can get it to do 1) but can't work out 2).
I relise that normally for a from to GET and appending querystring params it needs to have hidden form fields, so I could write a utility function that automatically adds a bunch of hidden form fields for any querystring values, but I wanted to check that there's wasn't an easier approach, or maybe I'm going about it the wrong way.
Cheers!
You'll need to do the hidden form field method.
Even if you could attach the entire querystring to the end of the URL in the action attribute of the <form> tag, browsers don't pay attention to this when doing GET form submissions.
Your method isn't too difficult; you'd want to do something like this:
public static string QueryStringAsHidden(this HtmlHelper helper)
{
var sb = new StringBuilder();
foreach (var key in HttpContext.Current.Request.QueryString.AllKeys)
{
if (! key.StartsWith("ProductSearch"))
sb.Append(helper.Hidden(key, HttpContext.Current.Request.QueryString[key]));
}
return sb.ToString();
}
I put the .StartsWith() in there because you don't want to be on a search page and submit the search string twice (and now you can prepend paging and other search-specific variables with ProductSearch.
Edit: PS: To get the form to post to the current page, you don't have to explicitly provide action and controller -- you can also send nulls.
Edit2: Why even bother with a helper method? :)
<% HttpContext.Current.Request.QueryString.AllKeys.Where(k => !k.StartsWith("ProductSearch")).ToList().ForEach(k => Response.Write(Html.Hidden(k, HttpContext.Current.Request.QueryString[k]))); %>
James
A direct to call BeginForm() does keep your query string values. Any other overload tends to fail. I love the ease of using BeginForm() from my forms, but needed a way to class all my styled forms a certain way an not lose the query string values in the action.
Here is what I came up with:
public static MvcForm BeginNormalForm<T>(this HtmlHelper<T> htmlHelper)
{
var dictionary = new Dictionary<string, object> {{"class", "normal"}};
var rvd = new RouteValueDictionary();
if (htmlHelper.ViewContext.HttpContext != null && htmlHelper.ViewContext.HttpContext.Request != null)
{
foreach (var key in htmlHelper.ViewContext.HttpContext.Request.QueryString.AllKeys)
{
rvd[key] = htmlHelper.ViewContext.HttpContext.Request.QueryString[key];
}
}
var form = htmlHelper.BeginForm(null, null, rvd, FormMethod.Post, dictionary);
return form;
}
Seems to work well and keeps my class attribute.
Use one of the overloads of BeginForm that takes a routeValues object or dictionary.
Additional properties not in the route will be added as query parameters.

asp.net mvc Html.ActionLink() keeping route value I don't want

I have the following ActionLink in my view
<%= Html.ActionLink("LinkText", "Action", "Controller"); %>
and it creates the following URL http://mywebsite.com/Controller/Action
Say I add an ID at the end like so: http://mywebsite.com/Controller/Action/53 and navigate to the page. On this page I have the markup I specified above. Now when I look at the URL it creates it looks like this:
http://mywebsite.com/Controller/Action/53 (notice the addition of the ID)
But I want it to remove the ID and look like it did originally, like this http://mywebsite.com/Controller/Action (notice no ID here)
Any ideas how I can fix this? I don't want to use hard coded URLs since my controller/actions may change.
The solution is to specify my own route values (the third parameter below)
<%= Html.ActionLink("LinkText", "Action", "Controller",
new { id=string.Empty }, null) %>
It sounds like you need to register a second "Action Only" route and use Html.RouteLink(). First register a route like this in you application start up:
routes.MapRoute("ActionOnly", "{controller}/{action}",
new { controller = "Home", action = "Index" } );
Then instead of ActionLink to create those links use:
Html.RouteLink("About","ActionOnly")
The problem is the built in methods take input from the URL you are currently on as well as what you supply. You could try this:
<%= Html.ActionLink("LinkText", "Action", "Controller", new { id = ""}) %>
That should manually wipe the id parameter.
Don't know why, but it didn't work for me (maybe because of Mvc2 RC). Created urlhelper method =>
public static string
WithoutRouteValues(this UrlHelper helper, ActionResult action,params string[] routeValues)
{
var rv = helper.RequestContext.RouteData.Values;
var ignoredValues = rv.Where(x=>routeValues.Any(z => z == x.Key)).ToList();
foreach (var ignoredValue in ignoredValues)
rv.Remove(ignoredValue.Key);
var res = helper.Action(action);
foreach (var ignoredValue in ignoredValues)
rv.Add(ignoredValue.Key, ignoredValue.Value);
return res;
}
If you either don't know what values need to be explicitly overridden or you just want to avoid the extra list of parameters you can use an extension method like the below.
View
The implementation details are in this blog post
I explicitly set the action name as "Action/". Seems a little like a hack but it's a quick fix.
#Html.ActionLink("Link Name", "Action/", "Controller")
Another way is to use ActionLink(HtmlHelper, String, String, RouteValueDictionary) overload, then there are no need to put null in the last parameter
<%= Html.ActionLink("Details", "Details", "Product", new RouteValueDictionary(new { id=item.ID })) %>
The overloads of Html.ActionLink are changed on the later versions of MVC. On MVC 5 and above. This is how to do this:
#Html.ActionLink("LinkText", "Action", "Controller", new { id = "" }, null)
Note I passed "" for id parameter and null for HTMLATTRIBUTES.
I needed my menu links to be dynamic. Rather than implement a lot of extra code and routing for every single page I simple dispensed with the HTML helper.
#item.MenuItemName

Resources