Mvc: pass id but not show it in friendly url - asp.net-mvc

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

Related

ActionLink not working as expected

I'm passing empid and I want it to be shown in url but it shows like querystring:
<li>#Html.ActionLink(key.Value, "Attendance","HOD", new {empid=key.Key}, null)
</li>
The link I want to display is like:
/HOD/Attendance/xyz%2Fabc
but it shows me like this:
/HOD/Attendance?empid=xyz%2Fabc //it's like query string but i don't want that.
Can somebody please help? I appreciate any little help. Thanks a lot in advance.
I've this RouteMap added to Global.asax
routes.MapRoute (
"HOD_AttByEmpID", // Route name
"{controller}/{action}/{empid}", // URL with parameters
new { controller = "Account", action = "LogOn",
empid = UrlParameter.Optional }
);
Did you tried like this?
#Html.ActionLink(key.Value, "Attendance","HOD", new { key.Key},null)
Source : HTML.ActionLink method
I guess that is because your value contains a '/'
Maybe you can try :
<li>#Html.ActionLink(key.Value, "Attendance","HOD",
new {empid=Server.UrlEncode(key.Key)}, null)
</li>

I'm not getting friendly url in a form with GET method

I setup a route like that:
routes.MapRoute(
name: "Pesquisar",
url: "Pesquisar/{aaa}/{bbb}/{id}",
defaults: new { controller = "Home", action = "Pesquisar",
aaa = UrlParameter.Optional,
bbb = UrlParameter.Optional,
id = UrlParameter.Optional
}
);
When I press Send button in a form (with GET method) the url is like that:
http://localhost:00000/Pesquisar?aaa=One&bbb=Two
But I was expecting for:
http://localhost:00000/Pesquisar/One/Two
When you map a rout, it adds it to the end of a list. When the router looks for the rule to match, it starts at the begining of the list and itterates through it. It will take the first rule that matches, not the most specific rule. Because it is natural to append code to the end, the default rule (which works for almost everything) will be at the start.
Try re-ordering your code to look like this:
///The specific rout which you want to use
routes.MapRoute(
name: "Pesquisar",
url: "{action}/{aaa}/{bbb}/{id}",
defaults: new { controller = "Home", action = "Pesquisar",
aaa = UrlParameter.Optional,
bbb = UrlParameter.Optional,
id = UrlParameter.Optional
}
);
///The generic catch all router
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
More information can be found in this question:
Setting up ASP.Net MVC 4 Routing with custom segment variables
When I press Send button in a form (with GET method) the url is like that:
http://mydomain.com/Pesquisar?aaa=One&bbb=Two
But I was expecting for:
http://mydomain.com/One/Two
This is because the browser is unaware of the fancy url you want, as the standard form Get method is to append form values in the querystring.
What you mostly likely have to do is something like Creating Canonical URLs including an id and title slug, except redirect to the url you want if it's not the url you want to display.
Or you can use jQuery to manually create the url you want on submit, but requires more client side work.
Not sure if you get a clean URL directly from a html form GET.
One suggestion would be to POST it to an action, do what you need to do with the data, then on completion, redirect to your clean URL.
e.g.
ViewModel:
public sealed class PesquisarModel
{
public string aaa { get; set; }
public string bbb { get; set; }
public string id { get; set; }
}
Controller Actions:
[HttpGet]
public ActionResult Pesquisar(PesquisarModel m)
{
return View();
}
[HttpPost]
[ActionName("Pesquisar")]
public ActionResult PesquisarPost(PesquisarModel m)
{
//do stuff
return RedirectPermanent("/pesquisar/" + m.aaa + "/" + m.bbb + "/" + m.id);
}
View:
#model MyApplication.Models.PesquisarModel
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.aaa)
#Html.TextBoxFor(m => m.bbb)
#Html.TextBoxFor(m => m.id)
<button type="submit">Submit</button>
}
This is the browser behavior. When making a GET request browser appends all KeyValue pairs to querystring.
The mentioned route format will be available, when we use Html.ActionLink or Html.RouteUrl etc.
You could probably write few code in OnActionExecuting ( or you can use any handler) to reconstruct RouteData and redirect with appropriate url. Below code is not tested
var queries = this.Request.QueryString;
foreach(var query in queries)
{
// Add/Update to RequestContext.RouteData
}
var redirectUrl = Url.RouteUrl("Pesquisar",this.RequestContext.RouteData);
Response.Redirect(redirectUrl);
That's expected behavior.
The routing system is on server side. The browser knows nothing about routes, and what you're doing happens in the browser.
If you want to get that route you have to compose it on the client side with a custom script which uses the <form>'s action, an the values of the <input type="text"> values.
You cannot generate the Url on the server side (which could be done with some UrlHelper extension methods) because the changes on the text boxes wouldn't be updated.
This is not advisable because if you make changes on the routes, you could forget to update them in your browser scripts, breaking you application.
You could avoid this problem by creating the url in the server side using an UrlHelper extension method with special placeholders, which could be easily replaced on the client side. I.e. generate an url like this:
http://localhost/Pesquisar/$aaa$/$bbb$/$id$
by providing RouteValues like this: new {aaa="$aaa$, bbb="$bbb$, id="$id$"} to an UrlHelper method. This url can be stored in the value property of a hidden field.
Then, make a browser script for the click event of your button, recover the url with the placeholders from the hidden field, and replace the placeholders with the actual values of the textboxes. To execute the get run this: document.location = theUrl;
If you want to d this for many differnt instances you could create a Helper to geenrate the hidden field with the Url, and a javascript which makes the replacements.
The question is... is it worth the effort?

How can map an asp.net MVC route with more than 3 components?

I'm trying to learn asp.net mvc, and almost everywhere I see route description with three components like /Controller/Action/{anyParams}
I'd like to know if I can map a route similar to,
/Folder(or namespace)/Controller/Action/params...
ex:
/Admin/Student/Edit/id
/ABC/Faculty/Add/`
/XYZ/Student/Edit/id
or in general,
/XYZ/Controller1/Action/{param}
Yep the second parameter in the MapRoutes function (usually in Global.asax.cs is Url and this can be any pattern you want. something like
routes.MapRoute("MyRoute", "XYZ/Controller1/Action/{param}",
new {controller = "Controller1", action = "Action"}});
should do the trick.
You can make your routes as complex as you want.
F.e. the following route:
routes.MapRoute("some-route", "products/detail/order/{id}/{name}/",
new { controller = "Products", action = "Order" },
new { id = "^\d+" });
will route to the following function:
public class ProductsController : Controller {
public ActionResult Order (int id, string name) {
}
}
So you can specify as many parameters as you want, and they will be passed into your action as function parameters.

path_prefix for asp.net mvc routes

I read this article about how you can prefix routes in ruby on rails. I want to be able to do the same thing with asp.net mvc
So I want to be able to define a route like :
/photographers/1/photos/2 //photo 2 of photographer with id 1
/photographers/1/photos //all of photographer with id 1
Any tips ?
EDIT:
"photographers/{id}/photos/{photoID}" - seems to do the job quite ok, BUT how can I support
RedirectToAction<PhotosController>(x => x.Add());
I would like to redirect to : /photographers/1/photos/add
Define your route like this:
routes.MapRoute(
"Photographers",
"photographers/{id}/photos/{photoID}",
new { controller = "Photographers", action = "Photo", photoID = null });
Then define your controller action like this:
public ActionResult Photo(int id, int? photoID)
{
// If photoID is not null, show just that photo.
// Otherwise, show all photographs.
}
You could use regex routing or use wildcards in your routing table so that the {id*} matches the /1/photos/2 for the photographers default controller, parse the string, and redirect to an appropriate action.
Also take a look at this post about nested resources.
RouteTable.Routes.Add(
new Route { Url = "events/[eventId]/tickets/[action]/[id]",
Defaults = new { controller = "Tickets",
action = "List", id = (string)null },
RouteHandler = typeof(MvcRouteHandler) });

Having issues with MVC Routing

I'm trying to implement routing such as the following:
posts/535434/This-is-a-post-title
posts/tagged/tags+here
// Matches {controller}/{action}/{id} - Default
// Displays all posts with the specified tags
// uses PostsController : ActionTagged(string tags)
posts?pageSize=50&pageIndex=4
// Matches {controller}/{action}/{id} - Default
// Displays all posts
// uses PostsController : Index(int? pageSize, int? pageIndex)
Here's the problem I want to do this:
posts/39423/this-is-a-post-title-here
// Typically this is implemented using an action like 'Details'
// and would normally look like : posts/details/5
I can't seem to get the routing working right. I tried something like this:
{controller}/{id}/{description}
and set the default action to be "Display" which works, but then won't allow me to navigate to other named actions like "Tagged".
What am I missing?
Thanks!
Two things:
First, you should always order your routes in decreasing specificity (e.g. most specific case first, least specific case last) so that routes will "fall through", if one doesn't match it will try the next.
So we want to define {controller}/{postid}/... (must be a postid) before we define {controller}/{action}/... (could be anything else)
Next, we want to be able to specify that if the provided value for postid does not look like a Post ID, the route should fail and fall through to the next one. We can do this by creating an IRouteConstraint class:
public class PostIDConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
//if input looks like a post id, return true.
//otherwise, false
}
}
We can add it to the route definition like so:
routes.MapRoute(
"Default",
"{controller}/{postid}/{description}",
new { controller = "Posts", action = "Display", id = 0 },
new { postid = new PostIDConstraint() }
);
I'm not 100% I understand your question, but it sounds like you can just define a couple different routes.
routes.MapRoute("PostId", "posts/{id}/{title}",
new { Controller = "Posts", Action = "DisplayPost", id = 0, title = "" },
new { id = #"\d+" });
routes.MapRoute("TaggedPosts", "posts/tagged/{tags}",
new { Controller = "Posts", Action = "DisplayTagged", tags = "" });
routes.MapRoute("Default", "posts",
new { Controller = "Posts", Action = "Index" });
You can use regular expressions to validate parameters like I used for id in the first route, or if you want some better validation do something like Rex M posted. The querystring parameters pageSize and pageIndex don't need to be included in your route; they will just be passed in to your Index method as long as the parameter names match.
The part of the url that's the "description" actually isn't used.
For example, this post is 519222 and I can still get to it using the url: Having issues with MVC Routing

Resources