A bug in the routing engine for .NET? - asp.net-mvc

I have an ASP.NET MVC application. In the application, I have a bunch of similarly structured routes for different actions:
/Admin/Addresses/{AddressId}/Delete
/Admin/Phones/{PhoneId}/Delete
/Admin/Notes/{NoteId}/Delete
/Admin/Files/{FileId}/Delete
None of which work... I have been checking the routes and the actions for 5 hours now, and I know they are all written the way they should be, but it's still 404ing all of them.
The funny thing is that the following routes, which are also similar in structure work just fine:
/Admin/Addresses/{Id}/{Type}
/Admin/Phones/{Id}/{Type}
/Admin/Notes/{Id}/{Type}
/Admin/Files/{Id}/{Type}
The only difference between the two sets is that the delete routes use GET and are supposed to return JSON, while the othere ones use POST and redirect.
Has anyone ran into this before?
EDIT: Here's a bigger code sample per the requests on the comments. First code sample is of the ONLY working route (which is probably because it's the first in the list of routes to use the specified url structure) and the second is the next route in line, which isn't working at all...
Routes.MapRoute("Administration (Delete Employee)", "Administration/Employees/{EmployeeId}/Delete", new {
controller = "Administration",
action = "DeleteEmployee"
});
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult DeleteEmployee(short EmployeeId) {
try {
db.DeleteEmployee(EmployeeId);
return Json(new IJsonStatus() {
Success = true
});
} catch (Exception ex) {
Shared.LogWarning(ex);
return Json(new IJsonStatus() {
Success = false,
Exception = ex.Message
});
};
}
And the non-working route:
Routes.MapRoute("Administration (Delete Address)", "Administration/Addresses/{AddressId}/Delete", new {
controller = "Administration",
action = "DeleteAddress"
});
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult DeleteAddress(int AddressId) {
try {
db.DeleteAddress(AddressId);
return Json(new BaseResponse() {
Success = true
});
} catch (Exception ex) {
Shared.LogWarning(ex);
return Json(new BaseResponse() {
Success = false,
Exception = ex.Message
});
};
}

Probably could be useful to see your entire route mapping call rather than just a snippet. That stuff is very, very order of operations dependent.
Second, check out the MVC routing debugger. It helps demystify alot of route mystification issues.

Often times HTTP POST is used to request delete actions. Are you using POST on these actions that are decorated with [AcceptVerbs(HttpVerbs.Get)] ?

Try mapping it more like this:
RouteTable.Routes.Add(new Route(
"Administration/Forums/{action}/{id}",
new RouteValueDictionary(new { controller = "Forums", action = "Index", id = "" }),
new MvcRouteHandler()));
RouteTable.Routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" });

I added the debugger and it matched two routes:
Administration/Notes/{Id}/{Type} <--POST
Administration/Notes/{NoteId}/Delete <--GET
So, I assume that it matched the post route because of the Id in it, which really stands for either CustomerId or EmployeeId because it's a unified action which diferentiates based on the Type.
However, I would have expected the /Delete on the second route to force it into the real route, but I guess it stops at the first matching parameter?

Related

How do I form the URL to call this WebAPI function?

I have this controller in an MVC 5 WebAPI application and I can't seem to figure out how to form the URL to call it. I keep getting a 404. tried .../ssa, /ssa/ssamedians, /ssa/ssamedians?titles=abc... What am I missing?
public class ssaController : ApiController
{
public IHttpActionResult getSsaMedians(string Titles = "")
{
SsaDB db = new SsaDB();
try
{
IEnumerable<Title_Medians> medians = db.getTitleMedians(Titles, null, null);
System.Web.HttpContext.Current.Response.AppendHeader("Access-Control-Allow-Origin", "*");
return Ok(medians);
}
catch
{
return NotFound();
}
}
There are also Actions called getSsaMediansByAaa() and getSsaMediansByBbb(). Once I got rid of the api/ in the routeTemplate, I now get a "Multiple actions were found that match the request".
Open your WebApiConfig.cs file and add this:
config.Routes.MapHttpRoute(
name: "SSATitles",
routeTemplate: "api/{controller}/{action}/{Titles}",
defaults: new { controller = "ssa", action = "getSsaMedians", Titles = UrlParameter.Optional }
);
For more information on webapi routing look here:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
You have an parameter in your method, that means that you need to add an extra link in your url.
Examlple.com/DirToApi/getSsaMedians/YourString
You may also want to check your RouteConfig file in your App_Start

Redirect with ASP.NET MVC MapRoute

On my site, I have moved some images from one folder to another.
Now, when I receive a request for old images '/old_folder/images/*' I want to make a permanent redirect to new folder with these images '/new_folder/images/*'
For example:
/old_folder/images/image1.png => /new_folder/images/image1.png
/old_folder/images/image2.jpg => /new_folder/images/image2.jpg
I have added a simple redirect controller
public class RedirectController : Controller
{
public ActionResult Index(string path)
{
return RedirectPermanent(path);
}
}
Now I need to setup proper routing, but I don't know how to pass the path part to the path parameter.
routes.MapRoute("ImagesFix", "/old_folder/images/{*pathInfo}", new { controller = "Redirect", action = "Index", path="/upload/images/????" });
Thanks
I would do in next way
routes.MapRoute("ImagesFix", "/old_folder/images/{path}", new { controller = "Redirect", action = "Index" });
and in controller like that
public class RedirectController : Controller
{
public ActionResult Index(string path)
{
return RedirectPermanent("/upload/images/" + path);
}
}
first download and install RouteMagic package from this link , then redirect your old address to the new address Like the below code :
var NewPath = routes.MapRoute("new", "new_folder/images/{controller}/{action}");
var OldPath = routes.MapRoute("new", "old_folder/images/{controller}/{action}");
routes.Redirect(OldPath ).To(NewPath );
for more information please check out the following link
Redirecting Routes To Maintain Persistent URLs
Answer above using RouteMagic is a good idea, but the example code is wrong (it's included in Phil's post as a bad example).
From the RouteMagic Github demo site global.asax.cs:
// Redirect From Old Route to New route
var targetRoute = routes.Map("target", "yo/{id}/{action}", new { controller = "Home" });
routes.Redirect(r => r.MapRoute("legacy", "foo/{id}/baz/{action}")).To(targetRoute, new { id = "123", action = "index" });
If you specify two routes, you will be setting up an extra mapping that will catch URLs which you don't want.

How to route PUT and DELETE requests for the same url to different controller methods

I was searching for an answer to this question, and found this question, which is indeed very similar. However the solutions(s) posted there don't seem to be working for me... I wonder if it has to do with the question's age.
Given the following URL:
/my/items/6
I want HTTP PUT requests for this URL to be handled by one action method, and HTTP DELETE requests to be handled by another action method. Below are the routes I defined (note these are based in an area, so context is an AreaRegistrationContext instance, if that matters):
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Replace" },
new
{
httpMethod = new HttpMethodConstraint("POST", "PUT"),
}
);
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Destroy" },
new
{
httpMethod = new HttpMethodConstraint("POST", "DELETE"),
}
);
URL generation works fine with both of these routes, however there are problems when routing incoming requests. Only the first-declared route correctly maps to its respective action.
I dug into the HttpMethodConstraint source code and discovered that it does not care about the "X-HTTP-Method-Override" parameter, only HttpContext.Request.HttpMethod.
I was able to solve this problem with the following custom route constraint class:
public class HttpMethodOverrideConstraint : HttpMethodConstraint
{
public HttpMethodOverrideConstraint(params string[] allowedMethods)
: base(allowedMethods) { }
protected override bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
var methodOverride = httpContext.Request
.Unvalidated().Form["X-HTTP-Method-Override"];
if (methodOverride == null)
return base.Match(httpContext, route, parameterName,
values, routeDirection);
return
AllowedMethods.Any(m =>
string.Equals(m, httpContext.Request.HttpMethod,
StringComparison.OrdinalIgnoreCase))
&&
AllowedMethods.Any(m =>
string.Equals(m, methodOverride,
StringComparison.OrdinalIgnoreCase))
;
}
}
...and these route definitions:
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Replace" },
new
{
httpMethod = new HttpMethodOverrideConstraint("POST", "PUT"),
}
);
context.MapRoute(null,
"my/items/{id}",
new { area = "AreaName", controller = "ControllerName", action = "Destroy" },
new
{
httpMethod = new HttpMethodOverrideConstraint("POST", "DELETE"),
}
);
My question: is it really necessary to have a custom route constraint to accomplish this? Or is there any way to make it work out-of-the-box with standard MVC & routing classes?
Action filters are your friend...
HttpDeleteAttribute, HttpPutAttribute, HttpPostAttribute, HttpGetAttribute

ASP.Net MVC: So wait... The Default Route Parameter Has To Be Named id?

Has anyone else had this issue? I have to be doing something wrong.
So I'm setting up a route for errors like this:
routes.MapRoute
(
"SharedError",
"Shared/Error/{error}",
new { error = "" }
);
And calling like:
return parentController.RedirectToRoute("SharedError", new RouteValueDictionary(new { error = errorMessage.ToString() }));
And on the controller:
public ActionResult Error(String error)
Simple right? Well when this is actually run, error is null despite the url looking like:
/Shared/Error/ThisIsTheError
But the error parameter in the Error method is null. (And yes I've tried other words)
Now if I replace all that with the word 'id' everything works.
Global.asax.cs
routes.MapRoute
(
"SharedError",
"Shared/Error/{id}",
new { id = "" }
);
Redirect:
return parentController.RedirectToRoute("SharedError", new RouteValueDictionary(new { id = errorMessage.ToString() }));
Shared Controller:
public ActionResult Error(String id)
Is id a must have word for all routes if you have a default route taking in a value?
You always need to specify the controller and action parameters:
routes.MapRoute
(
"SharedError",
"Shared/Error/{error}",
new { controller = "Shared", action="Error", error = "" }
);
I'm guessing that the reason why using id works for you is because you have the default route still registered, which has id as a parameter and default values for controller and action.
Note, if you still have the default route, make sure you add your route before it, otherwise it will match the other one first.

ASP.net MVC Areas and creating an ActionLink with ID (SEO / clean URL)

I am building a Help Desk Ticket system for a client using ASP.NET MVC 1.0 / C#. I have implemented Steven Sanderson's "App Areas in ASP.NET MVC, Take 2" and it is working great.
In my Globabl.asax page I have some routes defined as such:
public static void RegisterRoutes(RouteCollection routes)
{
// Routing config for the HelpDesk area
routes.CreateArea("HelpDesk", "ProjectName.Areas.HelpDesk.Controllers",
routes.MapRoute(null, "HelpDesk/{controller}/{action}", new { controller = "Ticket", action = "Index" }),
routes.MapRoute(null, "HelpDesk/Ticket/Details/{TicketId}", new { controller = "Ticket", action = "Details", TicketId = "TicketId" })
);
}
So, if I enter "http://localhost/HelpDesk/Ticket/Details/12" in the browser address bar manually, I get the results I expect. Here is my controller:
public ActionResult Details(int TicketId)
{
hd_Ticket ticket = ticketRepository.GetTicket(TicketId);
if (ticket == null)
return View("NotFound");
else
return View(ticket);
}
In my view I have:
<%= Html.ActionLink(item.Subject, "Details", new { item.TicketId } )%>
But that code generates "http://localhost/HelpDesk/Ticket/Details?TicketId=12" which also returns the expected results. My Question is...
How do I define an ActionLink when using Steven Sanderson's Areas that will create a clean URL like: "http://localhost/HelpDesk/Ticket/Details/12" ?
Try
<%= Html.ActionLink(item.Subject, "Details", new { TicketId = item.TicketId } )%>
The ActionLink method expects a dictionary with keys that match the parameter names. (Note that passing an anonymous object is a convenience for this). Anything else I believe it will just tag onto the end of the URL.
EDIT: The reason that this isn't working for you is because your first route matches and takes precedence (controller and action), but defines no TicketId parameter. You need to switch the order of your routes. You should always put your most specific routes first.
Try
<%= Html.ActionLink(item.Subject, "Details", new { TicketId=item.TicketId } )%>
I think Womp has it ...
Oh and while you are swapping your routes try
routes.MapRoute(null, "HelpDesk/Ticket/Details/{TicketId}", new { controller = "Ticket", action = "Details"})
I think the , TicketId = "id" is messing things up
Hope that helps,
Dan

Resources