I have an action like this:
public class News : System.Web.Mvc.Controller
{
public ActionResult Archive(int year)
{
/ *** /
}
}
With a route like this:
routes.MapRoute(
"News-Archive",
"News.mvc/Archive/{year}",
new { controller = "News", action = "Archive" }
);
The URL that I am on is:
News.mvc/Archive/2008
I have a form on this page like this:
<form>
<select name="year">
<option value="2007">2007</option>
</select>
</form>
Submitting the form should go to News.mvc/Archive/2007 if '2007' is selected in the form.
This requires the form 'action' attribute to be "News.mvc/Archive".
However, if I declare a form like this:
<form method="get" action="<%=Url.RouteUrl("News-Archive")%>">
it renders as:
<form method="get" action="/News.mvc/Archive/2008">
Can someone please let me know what I'm missing?
You have a couple problems, I think.
First, your route doesn't have a default value for "year", so the URL "/News.mvc/Archive" is actually not valid for routing purposes.
Second, you're expect form values to show up as route parameters, but that's not how HTML works. If you use a plain form with a select and a submit, your URLs will end up having "?year=2007" on the end of them. This is just how GET-method forms are designed to work in HTML.
So you need to come to some conclusion about what's important.
If you want the user to be able to select something from the dropdown and that changes the submission URL, then you're going to have to use Javascript to achieve this (by intercepting the form submit and formulating the correct URL).
If you're okay with /News.mvc/Archive?year=2007 as your URL, then you should remove the {year} designator from the route entirely. You can still leave the "int year" parameter on your action, since form values will also populate action method parameters.
I think I've worked out why - the route includes {year} so the generated routes always will too..
If anyone can confirm this?
Solution
Okay here is the solution, (thanks to Brad for leading me there).
1) Require default value in route:
routes.MapRoute(
"News-Archive",
"News.mvc/Archive/{year}",
new { controller = "News", action = "Archive", year = 0 }
);
2) Add a redirect to parse GET parameters as though they are URL segments.
public ActionResult Archive(int year)
{
if (!String.IsNullOrEmpty(Request["year"]))
{
return RedirectToAction("Archive", new { year = Request["year"] });
}
}
3) Make sure you have your redirect code for Request params before any code for redirecting "default" year entries. i.e.
public ActionResult Archive(int year)
{
if (!String.IsNullOrEmpty(Request["year"]))
{
return RedirectToAction("Archive", new { year = Request["year"] });
}
if (year == 0)
{
/* ... */
}
/* ... */
}
3) Explicitly specify the default value for year in the Url.RouteUrl() call:
Url.RouteUrl("News-Archive", new { year = 0 })
Related
I have an MVC 4 app and I am using a RESTful methodology for my URLs. I have the following routes registered in my app (along with others that are not relevant to my question:
//EDIT
routes.MapRoute(alias + "_EDIT", alias + "/{id}/edit",
new { controller = controllerName, action = "edit" },
new { httpMethod = new RestfulHttpMethodConstraint(HttpVerbs.Get) });
//PUT (update)
routes.MapRoute(alias + "_PUT", alias + "/{id}",
new { controller = controllerName, action = "update" },
new { httpMethod = new RestfulHttpMethodConstraint(HttpVerbs.Put) });
I have the following methos in my controller mapping to these routes:
public override ActionResult Edit(int id)
{...}
public override ActionResult Update(RequestEditViewModel userModel)
{
if (!ModelState.IsValid)
{
//do some stuff to ensure lookups are populated
...
return View("Edit", userModel);
}
}
In my app when I perform a request to edit a request my URL looks like:
http://server/request/1/edit
it correctly calls the Edit method on my controller.
My Edit.cshtml uses the followng to ensure the Update method is called on PUT:
#using (Html.BeginForm("Update", "Request"))
{
#Html.HttpMethodOverride(HttpVerbs.Put);
...
}
My form is generated as follows:
<form action="/requests/71" method="post" autocomplete="off" novalidate="novalidate">
<input name="X-HTTP-Method-Override" type="hidden" value="PUT"/>
...
</form>
When I click the submit button it correctly calls my Update method.
OK...Now for the issue. If my model is NOT valid I want to return back the Edit model. As you can see in the above code but, the URL is the one called from the submit button:
http://server/request/1
not
http://server/requests/1/edit
I have tried an reviewed two other options but both of these redirect the request back through the Edit method again which adds additional overhead and also puts all the model values in the querystring which I do NOT want:
return RedirectToAction("Edit", userModel);
return RedirectToRoute("requests_Edit", userModel);
So, is there a way to just return the View as I have in my code but, ensure the URL changes back and include the "/edit"?
The only alternative I have come up with is to perform an AJAX call and put the update that way the URL never changes, but I was trying to avoid that for this form.
Conceptually, you want to be doing something like a Server.Transfer (that is, making on URL appear to be another.) This discussion may be of use to you:
How to simulate Server.Transfer in ASP.NET MVC?
EDIT: Obviously this is a vastly simplified version of my site and if I make a test app, with this pattern it works fine. In our real app, we are using T4MVC and this is all within an area. I'm guessing one of these factors is causing my issue...
EDIT2: All the default routes are defined and if I navigate directly to /AreaName/ControllerName/SubChild?val=123 it renders.
I have a peculiar problem with Mvc and am hoping someone can help...
I have a controller with the following action methods
public ActionResult Index()
{
return View(GetModel());
}
public ActionResult Result Child(string blah)
{
return View(GetModel(blah));
}
public ActionResult Result SubChild(int val)
{
return View(GetModel(val));
}
I then have 3 razor views.
Index.cshtml
<div>
#Html.Action("Child", new { blah = "raaa"})
</div>
Child.cshtml
<div>
#*ERROR HERE*#
#Html.Action("SubChild", new { val = 123})
</div>
SubChild.cshtml
<h1>#Model.val</h1>
When I navigate to / I get an exception thrown saying that
"No route in the route table matches the supplied values." on the Html.Action calling the SubChild Action.
This is all within the same area and the same controller. If I change the markup and use Html.Partial for the call to the Child view (and construct the model and pass it in the view), it renders fine. The issue comes when I call Html.Action within a view that's already being rendered using Html.Action.
I've tried fully qualifying the action using
/area/controller/action, specifying the controller in the Html.Action call, passing the area as a parameter in the route values and combinations of all of these.
Does anyone have any ideas what this might be? I'm assuming that you can call Html.Action in Views that are being rendered using it, I guess I might be wrong...
Well, out of the box MVC 3 has the default route parameter named id. Your SubChild action has a parameter named val, so that is probably the issue.
Either rename the parameter in the Action to id, or add a new route
routes.MapRoute(
"SubChild",
"{controller}/SubChild/{val}",
new
{
controller = "ControllerName",
action = "SubChild",
val = UrlParameter.Optional
}
);
Are your parameters really named blahand val? Because normally the first parameter is always called id. Check the method RegisterRoutes(RouteCollection routes) in your global.asax.cs. There must be something like
routes.MapRoute("Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }); // Parameter defaults
That indicates how your parameters have to be named.
I think your Actions have to be like this:
public ActionResult Index()
{
return View(GetModel());
}
public ActionResult Result Child(string id)
{
return View(GetModel(id));
}
public ActionResult Result SubChild(int id)
{
return View(GetModel(id));
}
Then the code in your views has to be:
Index.cshtml
<div>
#Html.Action("Child", new { id = "raaa"})
</div>
Child.cshtml
<div>
#Html.Action("SubChild", new { id = 123})
</div>
It appears the problem is to do with our areas and routing setup.
On the 2nd pass, we are losing the reference to the area in the routevaluedictionary and as such it can't find the correct route. Where we are registering the area, we need to register the correct route.
Thanks for the help with this, I've upvoted the other answers as I think they may help someone else in the future.
I have a requirement to add specific functionality to an asp.net mvc2 web site to provide addtional SEO capability, as follows:
The incoming URL is plain text, perhaps a containing a sentence as follows
"http://somesite.com/welcome-to-our-web-site" or
"http://somesite.com/cool things/check-out-this-awesome-video"
In the MVC pipeline, I would like to take this URL, strip off the website name, look up the remaining portion in a database table and call an appropriate controller/view based on the content of the data in the table. All controllers will simply take a single parameter bieng the unique id from the lookup table. A different controller may be used depnding on different urls, but this must be derieved from the database.
If the url cannot be resolved a 404 error needs to be provided, if the url is found but obsolete then a 302 redirect needs to be provided.
Where the url is resolved it must be retained in the browser address bar.
I have had a look at the routing model, and custom routing and can't quite work out how to do it using these, as the controller would not be predefined, based on a simple route. I am also unsure of what to do to provide 404, 302 back to the headers also. Perhpas I need a custom httpmodule or similar but going there went beyond my understanding.
This must be possible somehow... we did it years ago in Classic ASP. Can anyone help with some details on how to achieve this?
Well, the simplest way would be to have an id somewhere in the url (usually the first option)
routes.MapRoute(
"SEORoute", // Route name
"{id}/{*seostuff}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, seostuff = UrlParameter.Optional } // Parameter defaults
);
In your controller you'd have something like
public class HomeController {
public ActionResult Index(int id) {
//check database for id
if(id_exists) {
return new RedirectResult("whereever you want to redirect", true);
} else {
return new HttpNotFoundResult();
}
}
}
If you don't want to use the id method you could do something else like...
routes.MapRoute(
"SEORoute", // Route name
"{category}/{page_name}", // URL with parameters
new { controller = "Home", action = "Index", category = UrlParameter.Optional, pagename = UrlParameter.Optional } // Parameter defaults
);
public ActionResult Index(string category, string page_name) {
//same as before but instead of looking for id look for pagename
}
The problem with the latter is that you would need to account for all types of routes and it can get really difficult if you have a lot of parameters that match various types.
This should get you in the right direction. If you neeed some clarification let me know and I'll see if I can write a specific route to help you
Additional
You could probably do what you're looking for like
public ActionResult Index() {
//Create and instance of the new controlle ryou want to handle this request
SomeController controller = new SomeController();
controller.ControllerContext = this.ControllerContext;
return controller.YourControllerAction();
}
but I don't know any of the side effects by doing that...so it's probably not a good idea - but it seems to work.
How do i map multiple url's to the same action in asp.net mvc
I have:
string url1 = "Help/Me";
string url2 = "Help/Me/Now";
string url3 = "Help/Polemus";
string url1 = "Help/Polemus/Tomorow";
In my global.asax.cs file i want to map all those url to the following action:
public class PageController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
}
Now in MVC 5 this can be achieved by using Route Attribute.
[Route("Help/Me")]
[Route("Help/Me/Now")]
[Route("Help/Polemus")]
[Route("Help/Polemus/Tomorow")]
public ActionResult Index()
{
return View();
}
Add the following line to your routing table:
routes.MapRoute("RouteName", "Help/{Thing}/{OtherThing}", new { controller = "Page" });
EDIT:
foreach(string url in urls)
routes.MapRoute("RouteName-" + url, url, new { controller = "Page", action = "Index" });
In my case I was looking to simply combine two 'hardcoded' routes into one and stumbled upon this post. I wanted to clean out my RouteConfig.cs a little - because it had so many similar routes.
I ended up using some simple 'or' logic in a regular expression and basically changed:
routes.MapRoute(
"UniqueHomePage",
"Default",
new { controller = "Redirector", action = "RedirectToRoot" }
);
routes.MapRoute(
"UniqueHomePage2",
"Home",
new { controller = "Redirector", action = "RedirectToRoot" }
);
Into a single route:
routes.MapRoute(
"UniqueHomePageGeneric",
"{url}",
new { controller = "Redirector", action = "RedirectToRoot" },
new { url = "Home|Default" }
);
Note for the SEO-savy or -interested: The reason for pointing multiple URL's to one and the same action, is to then redirect them to one and the same page again. In this case the homepage. So the idea is to prevent duplicate content issues. When you use this method for pointing for NON redirecting actions, but actions that show their own views, then you might be CAUSING duplicate content issues :P.
You can just add the routes into your route table as you need them, same as any other route. Just give them unique names, hard coded URL and point them to the same controller / action. It will work fine.
If you use pattern matching instead of hard coded URLs just make sure you get all your routes in the right order so the correct one is selected from the list. So /Help/Me should appear before /Help/{Page} if the hard coded route goes to a different page to the pattern matched one. If you put /help/{page} in the route tabel 1st this will match to /help/me and your hard coded named action for that route would never fire.
On a side note, if this is a public facing site and SEO is important please be careful if you have multiple URLs returning the same data, it will be flagged as duplicate. If this is the case, then use the Canonical tag, this gives all the page rank from all the URLS that go to that single page to the one you name and removes the duplicate content issue for you.
I'm having some trouble with ASP.NET MVC Beta, and the idea of making routes, controller actions, parameters on those controller actions and Html.ActionLinks all work together. I have an application that I'm working on where I have a model object called a Plot, and a corresponding PlotController. When a user creates a new Plot object, a URL friendly name gets generated (i.e.). I would then like to generate a "List" of the Plots that belong to the user, each of which would be a link that would navigate the user to a view of the details of that Plot. I want the URL for that link to look something like this: http://myapp.com/plot/my-plot-name. I've attempted to make that happen with the code below, but it doesn't seem to be working, and I can't seem to find any good samples that show how to make all of this work together.
My Route definition:
routes.MapRoute( "PlotByName", "plot/{name}", new { controller = "Plot", action = "ViewDetails" } );
My ControllerAction:
[Authorize]
public ActionResult ViewDetails( string plotName )
{
ViewData["SelectedPlot"] = from p in CurrentUser.Plots where p.UrlFriendlyName == plotName select p;
return View();
}
As for the ActionLink, I'm not really sure what that would look like to generate the appropriate URL.
Any assistance would be greatly appreciated.
The answer is pretty simple: You have to supply enough values in your "ActionLink" that will fulfill your Route. Example:
<%= Html.ActionLink("Click Here", "ViewDetails", "Plot", new { name="my-plot-name" }, null)%>
If you leave out the "name=" part of the ActionLink method, then the RouteEngine won't see this link as being good enough to "match"... so then it would go to the default route.
This code above will make the URL look the way you want it.
How about this code-fix? (Note the name = null, appened to the end of the 4th line....)
routes.MapRoute(
"PlotByName",
"plot/{name}",
new { controller = "Plot", action = "ViewDetails", name = null }
);
and this should be renamed.. (notice plotName is renamed to name)
public ActionResult ViewDetails(string name ) { ... }
does that help?