The Html.ActionLink
<li> ${Html.ActionLink<HomeController>(c => c.Edit(ViewData.Model.Id, ViewData.Model.Title), "Edit")} </li>
When created as html shows the URL to be Edit/5006?title=One . How do I change this to a pretty URL like Edit/5006/One ?
My Edit Action method is
public ActionResult Edit(int id, string title)
You need to have a route setup:
routes.MapRoute(
"DefaultWithTitle",
"{controller}/{action}/{id}/{title}",
new
{
controller = "Home",
action = "Edit",
id = UrlParameter.Optional,
title = UrlParameter.Optional
}
);
It is not depends on the function stamp, but it depends on the routing configuration.
routes.MapRoute("Edit", // Route name
"{controller}/{action}/{id}/{title}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Take a look at the first answer to this question: HTML.ActionLink method
The important point is that you have to make sure you're using the right overload for ActionLink().
Related
I need to implement SO like functionality on my asp.net MVC site.
For example when user go to https://stackoverflow.com/questions/xxxxxxxx
after loading the subject line is concatenated with the url and url becomes like this https://stackoverflow.com/questions/xxxxxxxx/rails-sql-search-through-has-one-relationship
Above "/rails-sql-search-through-has-one-relationship " part is added to the url.
In webforms it's simple, I could just use url rewriting. But not sure how to accomplish this in MVC
The following line is from Global.asax file
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Account", action = "LogOn", id = UrlParameter.Optional } // Parameter defaults
);
the string that I need to concatenate is in my database so it fetches from there. How can I accomplish this?
This is called a slug route. One way to achieve this is to define a route with an optional slug parameter, and in the controller method check if the parameter has been provided
routes.MapRoute(
name: "Question",
url: "Question/{id}/{slug}",
defaults: new { controller = "Question", action = "Details", slug = UrlParameter.Optional }
);
Then in QuestionController (assumes an id will always be provided)
public ActionResult Details (int id, string slug)
{
if (string.IsNullOrEmpty(slug))
{
// Look up the slug in the database based on the id, but for testing
slug = "this-is-a-slug";
return RedirectToAction("Details", new { id = id, slug = slug });
}
var model = db.Questions.Find(id);
return View(model);
}
You are looking for a custom route. If you look closely, SO doesn't care about the text part of the URL. So:
http://stackoverflow.com/questions/xxxxxxxx/rails-sql-search-through-has-one-relationship
AND
http://stackoverflow.com/questions/xxxxxxxx/
Will both work. You can easily do that with something like:
routes.MapRoute(
"Question",
"questions/{id}/{title}",
new { controller = "Question", action = "Details" });
The trick is add the "slug" at the end when you create links:
#Html.RouteLink(
"Read more.",
"Question",
new { id = question.Id, title = Slugger.ToUrl(question.Title) })
I have set up a similar friendly URL system to stackoverflow's questions.
The old URL syntax was: localhost:12345:/cars/details/1234
I've already set up the returning 301 and the URL generation but getting a file does not exist error when the url is redirected to:
localhost:12345/cars/details/1234/blue-subaru (because of the last "blue-subaru")
Of course I actually want: localhost:12345/cars/1234/blue-subaru :)
How can I achieve this? Thankyou
This is a routing problem so you should little bit changes in your routing like this
routes.MapRoute(
"Default", // Route nameRegister
"{controller}/{action}/{id}/{name}", // URL with parameters
new { controller = "test", action = "Index", id = UrlParameter.Optional,name = UrlParameter.Optional } // Parameter defaults
);
i think this will help you.
You could configure your route to accept the car's name in RouteTable on the global.asax.
routes.MapRoute(
"Cars",
"Car/{id}/{carName}",
new { controller = "Car", action = "Details", id = UrlParameter.Optional, carName = UrlParameter.Optional }
);
And in your CarController you could have your Detail action method and get both parameters (id and carName):
public ActionResult Details(int? id, string carName)
{
var model = /* create you model */
return View(model);
}
Your action link should look like this:
#Html.ActionLink("Text", "Details", "Car", new { id = 1, carName="Honda-Civic-2013" })
In my limited (2 weeks) experience in asp.net MVC3, for most action methods, I have never needed to add a route registration. But I have noticed that if the action method has an input parameter, then I can't access the method with a url of the form www.mysite.com/myController/myAction/myParameter1/myParameter2/myParameter3 (without the ? mark ) unless I map the route. Is that how its supposed to be?
By default, you already have registered route:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
it accepts one parameter, named id, so your action:
public ActionResult MyAction(string id)
will "catch" the request:
www.mysite.com/MyController/MyAction/parameter_value
and id will get value "parameter_value".
If you need more than one parameter (or parameter has to be names something else than "id"), then you have to register new route.
In case when you have 2 parameters, you will register route like this:
routes.MapRoute(
"Default",
"{controller}/{action}/{parameter1}/{parameter2}",
new { controller = "Home", action = "Index", parameter1 = UrlParameter.Optional, parameter2=UrlParameter.Optional }
);
and your action might be:
public ActionResult MyAction(string parameter1, int? parameter2)
Yeah, you need to register the route customizing the route in global.asax according to your requirement.You have to register the route in following way:
routes.MapRoute(
"routeName", // Route name
"{controller}/{action}/{myParameter}", // URL with parameters
new { controller = "Home", action = "Index", myParameter= "" } // Parameter defaults
);
So with above route, it ensures that whenever your url goes in above format, the parameter right after "action/" will be taken as parameter.....
For more than one parameter in your url, you can register like this:
routes.MapRoute(
"routeName", // Route name
"{controller}/{action}/{myParameter1}/{myParameter2}/{myParameter3}", // URL with parameters
new { controller = "Home", action = "Index", myParameter1= "", myParameter2= "", myParameter3= "" } // Parameter defaults
);
My Setup
I have a set of controllers in the normal fashion, which have their usual CRUD action methods inside them. Examples of these controllers are Testimonials, Galleries, and FAQs. These have backing models in Entity Framework, such as Testimonial, Gallery and FAQ, respectively.
You get to these by this sort of URL: /Galleries/Edit/2
All good so far, and all by default conventions...
I also have a set of pages that need to have editable content in them, and these have their content populated from a database via Entity Framework. They use an EF model behind them called "Page". This has a content property (html), and a name property so that I can match the incoming request. These pages are the Home, About and Prices pages.
I have chosen the Home controller to do this - I intend to have the index Action work out which Page to load from the DB by a name parameter:
[AllowAnonymous]
public ActionResult Index(string name = "Home")
{
// look up the page by name in the DB.
var model = context.Pages.FirstOrDefault(p => p.Title == name);
// trap errors.
if (model == null)
{
return RedirectToAction("NotFound", "Error", new { aspxerrorpath = name } );
}
// normal path
return View(model);
}
So, I could in theory add new items to the Pages table/DbSet and these would get mapped properly to this controller and action. I will then add an edit action for admin to edit the content that has the same signature as the index action above.
The Problem
The issue comes with Routing requests...
I had 2 initial routes:
routes.MapRoute("DynamicAccess",
"{name}/{action}",
new { controller = "Home", action = "Index" });
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action="Index", id=UrlParameter.Optional});
This fails when I go to "Galleries/", as it goes through the Home controller each time, and fails if I swap them around. I was also getting requests for Scripts/ folder through to the home controller too....
My Temporary Solution
My current routes now look like this:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.MapRoute("Gallery",
"Gallery/{action}/{id}",
new { controller = "Galleries", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("Testimonials",
"Testimonials/{action}/{id}",
new { controller = "Testimonials", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("FAQs",
"FAQs/{action}/{id}",
new { controller = "FAQs", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("DynamicAccess",
"{name}/{action}",
new { controller = "Home", action = "Index" });
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action="Index", id=UrlParameter.Optional});
routes.MapRoute("Root",
"",
new { controller = "Home", action = "Index" });
routes.MapRoute("AdminAccess",
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new { authenticated = new AuthenticatedAdminRouteConstraint() });
You can see here that I've had to declare a route for each of my static pages above the route for the dynamically resolved Home Route.
Question
This looks clumsy to me - having to add each non-dynamic page to my routes table.
Can anyone point me to a cleaner way of doing this please?
Thanks in advance.
Why not put a constraint on your static routes, which will allow routes that don't match to fall through to the dynamic route?
routes.MapRoute("default",
"{controller}/{action}/{id}",
new {controller="home", action="index", id=UrlParameter.Optional},
new {controller="^(|home|gallery|testimonial|faq)$"});
routes.MapRoute("dynamic",
"{name}/{action}",
new {controller="home", action="index"});
You will have to change your controllers to match the singular name in the constraint but other than that, it ought to work.
I am trying to use proper REST urls with MVC. To do that I switched default Routing from:
{controller}/{action}/{id}
to
{controller}/{id}/{action}
so instead of:
/Customer/Approve/23
there is now
/Customer/23/Approve
ActionLink seems to work ok, but the following code in CustomerController:
[CustomAuthorize]
[HttpGet]
public ActionResult Approve(int id)
{
_customerService.Approve(id);
return RedirectToAction("Search"); //Goes to bad url
}
ends up on url /Customer/23/Search. While it should be going to /Customer/Search. Somehow it remembers 23 (id).
Here is my routing code in global.cs
routes.MapRoute(
"AdminRoute", // Route name
"{controller}/{id}/{action}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { id = new IsIntegerConstraint() }
);
routes.MapRoute(
"Default",
"{controller}/{action}",
new { controller = "Home", action = "Index" });
If I switch the two functions, RedirectToAction starts working, but using:
Html.ActionLink("Approve", "Approve", new { Id = 23})
Now generates /Customer/Approve?id=23, instead of /Customer/23/Approve.
I could specify direct urls like ~/Customer/23/Approve, instead of using ActionLink and RedirectToAction, but would rather stick to functions provided by MVC.
When you use RedirectToAction(), internally, MVC will take the existing route data (including the Id value) to build the url. Even if you pass a null RouteValueDictionary, the existing route data will be merged with the new empty route value data.
The only way around this I can see is to use RedirectToRoute(), as follows:
return RedirectToRoute("Default", new { controller = "Customer", action = "Search"});
counsellorben
Try passing in new (empty) RouteValueDictionary in your controller
return RedirectToAction("Search", new System.Web.Routing.RouteValueDictionary{});
And here:
Html.ActionLink("Approve", "Approve", new { Id = 23})
I don't even know how can it pick up the Customer controller, since you are not specifying it anywhere. Try providing both controller and action to ActionLink helper.
Try passing the current route data to methon in your controller action:
return RedirectToAction("Search", this.RouteData.Values);
Remove this part:
id = UrlParameter.Optional
may be resolve the problem; when you define "id" as an optional parameter, and you have the "Default" map, the "Default" and the "AdminRoute" are same together!
regards.
I was having a similar problem. Route values that were passed to my controller action were being reused when I tried to redirect the user with RedirectToAction, even if I didn't specify them in the new RouteValueDictionary. The solution that I came up with (after reading
counsellorben's post) with was to clear out the RouteData for the current request. That way, I could stop MVC from merging route values that I didn't specify.
So, in your situation maybe you could do something like this:
[CustomAuthorize]
[HttpGet]
public ActionResult Approve(int id)
{
_customerService.Approve(id);
this.RouteData.Values.Clear(); //clear out current route values
return RedirectToAction("Search"); //Goes to bad url
}
I had a similar problem and was able to solve it by adding the id to the default route as well.
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
If there is truly no id in your default route then you could also try:
routes.MapRoute(
"Default",
"{controller}/{action}",
new { controller = "Home", action = "Index", id = string.Empty });