Create proper link from MVC5 Ajax.ActionLink - asp.net-mvc

I have an mvc view that contains a table of data. I want to include a 'Remove' button in there so users can delete records. I'm having trouble generating the proper link to go to my Delete action. In order to delete i need two values from the table, not just an id.
<td>
#Ajax.ActionLink("Delete",
"DeleteWorkItem",
"Project/Work",
new { hId = #w.ProjectId, workId = #w.WorkId},
new AjaxOptions()
{
AllowCache = false,
HttpMethod = "DELETE",
Confirm = "Are you sure you want to delete this record?"
})
</td>
is what i have so far but when the link is clicked it creates a Get request to a url and appends the two parameters in the query string. How can i get those parameter values in the url instead of the query string. Also, I want to call the http delete method but no matter what i put in that options value i get a get
My WorkController
[HttpDelete]
public ActionResult DeleteWorkItem(int hId, int workId)
{
this.brWorkManager.Delete(forhealthId, workId);
return RedirectToAction("Details");
}
my route:
routes.MapRoute(
"WorkItemDelete",
"FHProject/Work/DeleteWorkItem",
new { controller = "Work", action = "DeleteWorkItem" },
new { httpMethod = new HttpMethodConstraint("DELETE") }
);

Related

RouteValues without using Query String?

I have a form generated through Razor:
using (Ajax.BeginForm("SaveProfile", "Settings", new { AccountID = Model.AccountID },
new AjaxOptions { HttpMethod = "Post" }))
The RouteValues object, new { AccountID = Model.AccountID } gets shoved into the Query String, even though I don't specify to do so anywhere in my project.
How can I pass these values to my controller action without letting them show up in the URL?
MVC model-binding will recognize values passed by the query-string or the form (post) variables.
So you could include hidden inputs in the form with the var names as the name attributes and the values as the value attributes:
#Html.HiddenFor(model => model.AccountID)

Mvc: pass id but not show it in friendly url

I have a view that renders a list of news, in that i have an href tag, and i need to call a Detail method and pass it the news id, now i have a querystring in my url like that
News/Details?id=x... but i need something like News/Details/Category/Title-of-something, a friendly url with news category, name and without the id
this is my action, it works but i get that querystring
foreach (var item in Model)
{
Read More
}
I was trying with something like, with a Url.RouteUrl
routes.MapRoute(
name: "Details",
url: "{controller}/{action}/{id}/{category}/{newsName}",
defaults: new { controller = "News", action = "Details"}
);
but it never goes to Details actionresult, and also i need to pass the id parameter for showing some news Details, but i don't want to display it in the friendly url. Im really confused how to achieve it. Thanks in advance
Try this:
#Html.RouteLink("Read More", "Details", new { action = "Details", id = item.newsId, category = item.categoryName, newsName = item.newsName })
Use :
Read More
Make sure your route is not overridden by other route. So put your route first in RoutConfig.cs
Change your code to:
foreach (var item in Model)
{
Read More
}//change name=item.newsName to newsName=item.newsName
Also make sure that your controller has correct signature as per your routing details:
public class NewsController : Controller
{
public ActionResult Details(int id, string category, string newsName )
}
First URL Correct
Read More
You may Route Debugger whenever yo see issue related to routes

Using HtmlHelper.BeginForm to match a complicated route

I have a complicated route that I would like to match with an HtmlHelper.BeginForm method. I've read quite a few articles and answers on using route value dictionaries and object initializers and html attributes. But they all fall short of what I want to do...
Here is the route I want to match:
// Attempt to consolidate all Profile controller actions into one route
routes.MapRoute(
"Profile",
"{adminUserCode}/{controller}s/{customerId}/{action}/{profileId}",
new { adminUserCode = UrlParameter.Optional, controller = "Profile"},
new { adminUserCode = #"\d+", customerId = #"\d+", profileId = #"\d+" }
);
An example url for the controller & action I want to match with this would be:
http://mysite.com/123/Profiles/456/UpdatePhoneNumber/789
With the actual phone number being in the POST body
And here is the closest syntax I've come to getting right:
#using (Html.BeginForm(
"UpdatePhoneNumber",
"Profile",
new {
customerId = Model.LeadProfile.CustomerId,
profileId = Model.CustomerLeadProfileId
}))
{
<!-- the form -->
}
But this puts the parameters in the object as query string parameters, like this:
<form method="post"
action="/mvc/123/Profiles/UpdatePhoneNumber?customerId=78293&profileId=1604750">
I just tried this syntax on a whim, but it output the same thing as the other overload
#using (Html.BeginForm(new
{
controller = "Profile",
customerId = Model.LeadProfile.CustomerId,
action = "UpdatePhoneNumber",
profileId = Model.CustomerLeadProfileId
}))
I know I can just fall back on raw HTML here, but there seems like there should be a way to get the silly HtmlHelper to match more than the most basic of routes.
If you want to use complicated routes in your form you need to use the BeginRouteForm Method
#using (Html.BeginRouteForm("Profile", new
{
controller = "Profile",
customerId = Model.LeadProfile.CustomerId,
action = "UpdatePhoneNumber",
profileId = Model.CustomerLeadProfileId
}))

Ambient values in mvc2.net routing

I have following two routes registered in my global.asax file
routes.MapRoute(
"strict",
"{controller}.mvc/{docid}/{action}/{id}",
new { action = "Index", id = "", docid = "" },
new { docid = #"\d+"}
);
routes.MapRoute(
"default",
"{controller}.mvc/{action}/{id}",
new { action = "Index", id = "" },
new { docConstraint = new DocumentConstraint() }
);
and I have a static "dashboard" link in my tabstrip and some other links that are constructed from values in db here is the code
<ul id="globalnav" class = "t-reset t-tabstrip-items">
<li class="bar" id = "dashboard">
<%=Html.ActionLink("dash.board", "Index", pck.Controller, new{docid =string.Empty,id = pck.PkgID }, new { #class = "here" })%>
</li>
<%
foreach (var md in pck.sysModules)
{
%>
<li class="<%=liClass%>">
<%=Html.ActionLink(md.ModuleName, md.ActionName, pck.Controller, new { docid = md.DocumentID}, new { #class = cls })%>
</li>
<%
}
%>
</ul>
Now my launching address is localhost/oa.mvc/index/11 clearly matching the 2nd route. But when I visit any page that has mapped to first route and then come back to dash.board link it shows me localhost/oa.mvc/7/index/11 where 7 is docid and picked from previous Url.
I understand that my action method is after docid and changing it would not clear the docid.
My question here is, can I remove docid in this scenario without changing the route?
I have the same "not clearing out" value problem...
I've stepped into source code and I don't understand the reason for being of segment commented as : // Add all current values that aren't in the URL at all
# System\Web\Routing\ParsedRoute.cs, public BoundUrl Bind(RouteValueDictionary currentValues, RouteValueDictionary values, RouteValueDictionary defaultValues, RouteValueDictionary constraints) method from line 91 to line 100
While the clearing process is correctly handled in method preceding steps, this code "reinjects" the undesired parameter into acceptedValues dictionary!?
My routing is defined this way:
routes.MapRoute(
"Planning",
"Plans/{plan}/{controller}/{action}/{identifier}",
new { controller = "General", action = "Planning", identifier = UrlParameter.Optional },
new { plan = #"^\d+$" }
);
// default application route
routes.MapRoute(
"Default",
"{controller}/{action}/{identifier}",
new {
controller = "General",
action = "Summary",
identifier = UrlParameter.Optional,
plan = string.Empty // mind this default !!!
}
);
This is very similar to what you're using. But mind my default route where I define defaults. Even though my default route doesn't define plan route value I still set it to string.Empty. So whenever I use Html.ActionLink() or Url.Action() and I want plan to be removed from the URL I call it the usual way:
Url.Action("Action", "Controller", new { plan = string.Empty });
And plan is not included in the URL query string any more. Try it out yourself it may work as well.
Muhammad, I suggest something like this :
(written 5 mn ago, not tested in production)
public static class MyHtmlHelperExtensions {
public static MvcHtmlString FixActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes) {
var linkRvd = new RouteValueDictionary(routeValues);
var contextRvd = htmlHelper.ViewContext.RouteData.Values;
var contextRemovedRvd = new RouteValueDictionary();
// remove clearing route values from current context
foreach (var rv in linkRvd) {
if (string.IsNullOrEmpty((string)rv.Value) && contextRvd.ContainsKey(rv.Key)) {
contextRemovedRvd.Add(rv.Key, contextRvd[rv.Key]);
contextRvd.Remove(rv.Key);
}
}
// call ActionLink with modified context
var htmlString = htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
// restore context
foreach (var rv in contextRemovedRvd) {
contextRvd.Add(rv.Key, rv.Value);
}
return htmlString;
}
}
This is such a frustrating problem and I would venture to say that it is even a bug in ASP.Net MVC. Luckily it's an easy fix using ActionFilters. If you are using MVC3 then I would just put this as a global attribute to clear out ambient values. I made this attribute discriminatory, but you can change it to clear all attributes.
The assumption here is that by the time the Result is executing (your view most likely), you have already explicitly specified all your ActionLinks and Form Actions. Thus this will execute before they (the links) are evaluated, giving you a new foundation to generate them.
public class ClearAmbientRouteValuesAttribute : ActionFilterAttribute
{
private readonly string[] _keys;
public ClearAmbientRouteValuesAttribute(params string [] keys)
{
if (keys == null)
_keys = new string[0];
_keys = keys;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
foreach (var key in _keys) {
// Why are you sticking around!!!
filterContext.RequestContext.RouteData.Values.Remove(key);
}
}
}
// Inside your Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new ClearAmbientRouteValuesAttribute("format"));
}
Hope this helps someone, cause it sure helped me. Thanks for asking this question.
In this particular scenario I have two recommendations:
Use named routes. The first parameter to the MapRoute method is a name. To generate links use Html.RouteLink() (and other similar APIs). This way you'll always choose the exact route that you want and never have to wonder what gets chosen.
If you still want to use Html.ActionLink() then explicitly set docid="" to clear out its value.
Here's how I solved my problem, it may take a little adapting to get it to work, but I felt like I could get what I needed and just use routing more or less normally:
Excerpted from Apress Pro ASP.Net.MVC 3 Framework:
A value must be available for every segment variable defined in the URL pattern.
To find values for each segment variable, the routing system looks first at the
values we have provided (using the properties of anonymous type), then the
variable values for the current request, and finally at the default values defined in
the route. (We return to the second source of these values later in this chapter.)
None of the values we provided for the segment variables may disagree with the
default-only variables defined in the route. These are variables for which default
values have been provided, but which do not occur in the URL pattern. For
example, in this route definition, myVar is a default-only variable:
routes.MapRoute("MyRoute", "{controller}/{action}",
new { myVar = "true" });
For this route to be a match, we must take care to not supply a value for myVar or to make
sure that the value we do supply matches the default value.
The values for all of the segment variables must satisfy the route constraints. See
the “Constraining Routes” section earlier in the chapter for examples of different
kinds of constraints.
Basically I used the rule about a route not matching if it doesn't define a segment, but has a default variable used to give me a little more control over whether a route was chosen for outbound routing or not.
Here's my fixed routes, notice how I specify a value for category that would never be valid and don't specify a segment for category. This means that route will be skipped if I have a category, but will use it if I only have a page:
routes.MapRoute(null, "receptionists/faq/{page}", new { controller = "Receptionist", action = "Faq", page = 1, category = (Object)null }, new { page = #"^\d+$" });
routes.MapRoute(null, "receptionists/faq/{category}/{page}", new { controller = "Receptionist", action = "Faq", page = 1 }, new { category = #"^\D+$", page = #"^\d+$" });
For Category Links
#Html.ActionLink("All", "Faq", new { page = 1 })
#foreach (var category in Model.Categories)
{
#Html.ActionLink(category.DisplayName, "faq", new { category = category.DisplayName.ToLower(), page = 1 })
}
For Page Links
#for (var p = 1; p <= Model.TotalPages; p++)
{
#Html.ActionLink(p.ToString(), "Faq", new { page = p, category = Model.CurrentCategory})
}

How to get user input for Ajax.ActionLink call for action method?

Suppose I want to use ajax action link to save one field with no change for view.
Here is my view(it is a strongly-typed view bound to type Model):
<%= Html.TextBox("myComment", Model.MyComment)%>
<%= Ajax.ActionLink("SAVE", "SaveComment", "Home",
Model, new AjaxOptions { HttpMethod = "POST"})
%>
Here is my Action for ajax actionlink in controller:
[AcceptVerbs(HttpVerbs.Post)]
public void SaveComment(Model model)
{
MyRepository repository = new MyRepository();
Model mod = repository.GetModel(model.ID);
mod.MyComment = model.MyComment;
try
{
repository.Save();
}
catch(Exception ex)
{
throw ex;
}
}
The problem is: I can't get the user input in textbox for Mycomment in action method. Whatever user input in browser for Mycomment, when click on Ajax ActionLink SAVE, then I check the value from param model for Mycomment, there is nothing changed for MyComment.
Confused is: Model should be bound to view in bi-way. but for this case, it seems not.
Then I changed the Ajax.ActionLink call to:
Ajax.ActionLink("SAVE", "SaveComment", "Home",
new {commentinput =Model.MyComment, new AjaxOptions { HttpMethod = "POST"})
then changed action method signature as:
public void SaveComment(string commentinput)
I still can't get the user input.
How to get the user input in controller action method?
AFAIK Ajax.ActionLink doesn't allow you to easily get form values and post them with AJAX. There are some workarounds which seem quite hackish (looking at the last paragraph Adapting the URL Dynamically makes me cry).
Here's the jQuery way (I prefer it if compared to MS Ajax):
<%= Html.TextBox("myComment", Model.MyComment) %>
<%= Html.ActionLink("SAVE", "SaveComment", "Home", new { id = "saveComment" }) %>
And then unobtrusively attach a click event handler to the anchor:
$(function() {
$('a#saveComment').click(function(evt) {
$.ajax({
url : this.href,
data: {
// Assuming your model has a property called MyComment
myComment: $('#myComment').val()
},
success: function(data, textStatus) {
// Comment saved successfully
}
});
evt.preventDefault();
});
});

Resources