MVC3 how to create URLs - asp.net-mvc-3

I see people using Html.ActionLink() and Url.RouteUrl() etc. etc.
But surely this will lead to a maintenance nightmare if routes need to be redesigned?
How are people organising the generation of URLs in a typesafe and manageable way?

Strongly typed URL generation via lambda expressions was available for a period of time during the MVC 1.0 beta timeframe. It was removed since the MVC architecture does not actually have a 1-to-1 mapping between action names and controller method names. See this Phil Haack blog post for details.
It is of course still possible to do it, and assuming you're not using action names that differ from method names, it should work fine.

You can use T4MVC to generate typesafe checks at compile time for your MVC urls.
T4MVC analyses your Controller classes, and generates code that will generate typesafe url's.
Instead of
#Html.ActionLink("New customer", "Create", new { Controller = "Customer", orgID = orgID })
You can use code like:
#Html.ActionLink("New customer", MVC.Customer.Create(orgID))

If you want to call a action you use the Html.ActionLink(). This will create a <a href="..." ></a> hyperlink to chosen action.
If you want to create a url and use it not for a hyperlink, you can use the Url.Content() or the Url.RouteUrl(). The content accepts a string and gerenates a safe url. The Route url takes a route object.

Related

MVC Routing Engine routes same formatted route to different controller actions

Okay, I did my homework and search SO, and indeed I found similar questions but not reporting the behavior I'm getting.
Here is the deal, I have defined a route:
routes.MapRoute("CategoryName", "Category/Name/{text}",
new { controller = "Category", action = "Name", text = "" });
The twist here is the following:
This url: http://www.url.com/Category/Name/existingCategoryName
And this url: http://www.url.com/Category/Name/anotherExistingCategoryName
Both url's should go to the same controller method which is public ActionResult Name(string text) but sadly the first url is going to the default Index method, the second is being routed correctly.
I wonder why this happens, as I've been with .net mvc for several years and never experienced this behavior.
As a side note here are some facts:
As it's being route to different methods, I doubt the code inside them has something to do with it.
When manually write the category to something it doesn't exists in the DB as a category name it goes through the Name method.
The routes are placed correctly, as I'm aware the first route that matches the pattern will win.
Even I tried place the CategoryName route first, the behavior is the same.
When writing each link in the Category/Index I use the same #Html.RouteLink() helper, so all the links are formatted the same way.
Thanks in advance!
Are you using the - sign in the failing route?
Maybe you can find more information with the Routing debugger
And maybe you can look at this question: Failing ASP.NET MVC route. Is this a bug or corner case?
Phil Haack also give an possible answer to your problem in: ASP.NET routing: Literal sub-segment between tokens, and route values with a character from the literal sub-segment

What are the benefits of using MVC HTML helpers like ActionLink, BeginForm, TextBox, etc instead of the native HTML tags?

In a SO response to a different question, a user stated that it allowed you to avoid hard-coding route values into a html link tag, but that is not really valid since you have to put in the controller, action, area, etc as strings so you are still hard-coding the route values.
How is this:
#Html.ActionLink(linkText: "MyLink", actionName: "MyAction", controllerName: "MyController", new { id = #myId }, new { area = "SomeArea"})
better than this:
<a href='/SomeArea/MyController/MyAction/myId'>MyLink</a>
Your observation is only true if (a) you're using strictly the default routing format and (b) if your application will always be installed at the root of the site. If you don't do the former (say create a short cut route /help which goes to the Home controller and Help action, and subsequently change it by introducing a Help controller with more actions, then you'll need to update all of your hard-coded anchor tags. A better alternative is using the RouteLink helper with the route name and, optionally, other parameters.
With regard to the latter, I typically use a single server for most of my staging deployments and the application does NOT sit at the site root, but rather in a subdirectory. Production deployment is mixed, but many applications get installed at the site root. Using the helpers allows me to ignore the difference during development as the helper properly constructs the url relative to the current site in all cases. This is so helpful that I even use it for scripts, css files, images, etc. via the UrlHelper to make sure that any paths specified for those do not break between staging and production.
There seems to be little to benefit in using the helper, providing you make one change - add a tilda so that the router automatically resolves the address to the correct place.
<a href='~/SomeArea/MyController/MyAction/myId'>MyLink</a>

MVC3 Routing - Routes that builds on each other

I have an AREA setup in my project. I need to make the routes for the area progressive, meaning that the route will build on each other.
I'm looking at this as something like a link list. Each node in the list will have a reference to a parent. As move from left to right in the list it builds, and from right to left it removes.
In the area, I have companies and that have contacts, and child companies.
For example, I have companies that would have the following:
/Companies/list
/Company/{Id}
/Company/{id}/add
/Company/{id}/edit
/Company/{id}/delete
For the contact section I need to create the following routes:
/Company/{id}/contacts/list
/Company/{id}/contact/{id}/add
/Company/{id}/contact/{id}/edit
/Company/{id}/contact/{id}/delete
How do I make sure that /Company/{id} is always in the Contact and Child Company sections of the route?
I hope that I have made my question clear.
Subjective Generalities (take with a pinch of salt):
First off, you are using Company (singular) for companies, but then you are using contacts (plural) for the contacts. There is nothing wrong with this, from a structural point of view, but your users will thank you if you are consistent with your pluralizations. I would use the plural in both cases, but that is just my preference... it looks more like English.
You also use lower case for contacts, but upper case for Company. Doesn't look professional.
The next thing that is confusing is that you are using two {id} parameters, one for companies, one for contacts. I presume these are the ids for Company and Contacts respectively. But I am confused, but being human, I am able to deduce context unlike a computer. So you would be better of specifying the parameters in your routes. Ie:
/Companies/{CompanyId}/Contacts/{ContactId}/[action]
Answering your Question with an Example:
I get the feel you don't understand routes properly. If you did, your question would be more specific.
Your route parameters can come from a number of sources, depending on how the route is requested.
You could hard code it into a link. Or, more usefully, your route registration would be designed to catch requests that map to your Action signatures.
For example, I have an eLearning app with tutors, pupils, courses and steps (ie, the steps are like sections of a course, the pupil advances through the course step by step)
The route registration looks something like:
Route or Area Registration:
context.MapRoute(
"StepDisplay",
"Course/{CourseId}/Step/{StepOrder}/Pupil/{PupilName}/{TutorName}",
new { controller = "Course", action = "Display", TutorName = UrlParameter.Optional },
new[] { "ES.eLearningFE.Areas.Courses.Controllers" }
);
This route will catch a request from the following ActionLink:
ActionLink in View:
#Html.ActionLink(#StepTitle, MVC.Courses.Course.Actions.Display(Model.CourseId, step.StepOrder, Model.Pupil.UserName, tutorName))
Now, I just need to show you the Display action's signature:
CoursesController:
public virtual ActionResult Display(int CourseId, int StepOrder, string PupilName, string TutorName)
There are a few things to note here:
That I am able to call this specific route by giving the user a link to click on.
I construct this link using the Html.ActionLink helper
I have used David Ebbo's t4mvc nuget package so that I can specify the action I am calling and its parameters. By which I mean specifying the ActionResult parameter of the Html.ActionLink helper using:
MVC.Courses.Course.Actions.Display(Model.CourseId, step.StepOrder, Model.Pupil.UserName, tutorName)
If you think about it, what routes do is translate the url of a request into an action, so the parameters of my route are either the controller name, the action name or else they are the names of parameters in the action signature.
You can see now why naming two distinct route parameters with the same
name is such a bad idea (largely because it won't work).
So, look at your action signatures, and design your routes and your action links so that the everything marries up together.
MVC doesn't work by magic!! (Although the way it uses name conventions might lead you to believe it)

Should I still use querystrings for page number, etc, when using ASP.NET 4 URL Routing?

I switched from Intelligencia's UrlRewriter to the new web forms routing in ASP.NET 4.0. I have it working great for basic pages, however, in my e-commerce site, when browsing category pages, I previously used querystrings that were built into my pager control to control paging and now am not sure how to handle this using routing.
I defined a MapPageRoute as:
routes.MapPageRoute("cat-browse", "Category/{name}_{id}", ~/CategoryPage.aspx");
This works great. Now, somebody clicks to go to page 2. Previously I would have just tacked on ?page=2 to the url. How do I handle this using web forms routing? I know I can do something like:
http://www.mysite.com/Category/Arts-and-Crafts_17/page/2
But in addition to page, I can have filters, age ranges, gender, etc.
Should I just keep defining routes
that handle these variables like
above?
Should I continue using querystrings
and if so, how do you define a route
to handle that?
The main reason to use url routing is to expose clean, user-and-SEO-friendly, URLs. If this is your goal, then try to stick to it and not use querystring parameters. Note: I don't believe we need to completely ban the use of querystrings and, depending on your situation, you may decide it best to use querystring parameters for parameters that are not used frequently, or where no real value is added by making the information more semantically meaningful.
So here's what I would do:
Define a catch-all for all your other parameters:
routes.MapPageRoute("cat-browse", "Category/{name}_{id}/{*queryvalues}", "~/CategoryPage.aspx");
In /CategoryPage.aspx, access the router parameter and then parse as appropriate:
Page.RouteData.Values["queryvalues"]
Instead of using the syntax of Arts-and-Crafts_17/**page/2/age/34** for these parameters, I perfer to use the following syntax: Arts-and-Crafts_17/pg-2/age-34/
If you do this, the catch-all parameter 'querystring', will equal pg-2/age-34. You can now easily parse this data and add each name/value to the page context. Note that you will need to do something along these lines since each of these parameters are optional on your site.
You can take advantage of C# 4.0 named and optional parameters. Please have a look at this example from haacked
If you are using a lower version of the framework, you can also use code from the link above. But instead of declaring the method as
public ActionResult Search(int? page=0)
{}
you can declare it as
public ActionResult Search(int? page)
{
if(page == null)
{
page=0;
}
}
HTH

need help with mvc & routes

I'm very new to MVC and I'm trying to get a new site set up using it. For SEO reasons we need to make the url of a page something like "Recruiter/4359/John_Smith" or basically {controller}/{id}/{name}. I have that working when I create the url in the code behind like so...
//r is a recruiter object that is part of the results for the view
r.Summary = searchResult.Summary + "... <a href=\"/Recruiter/" + r.Id + "/" + r.FirstName + "_" + r.LastName + "\">Read More</a>"
But when I am using the collection of results from a search in my view and iterating through them I am trying to create another link to the same page doing something like <%=Html.ActionLink<RecruiterController>(x => x.Detail((int)r.Id), r.RecruiterName)%> but that doesn't work. When I use that code in the view it gives me a url in the form of /Recruiter/Detail/4359 I was told by a coworker that I should use the Html.ActionLink to create the link in both the view and the controller so that if the route changes in the future it will automatically work. Unfortunately he wasn't sure how to do that in this case. So, my problems are...
How can I make the Html.ActionLink work in the view to create a url like I need (like r.Summary above)?
How do I use the Html.ActionLink in a controller instead of hardcoding the link like I have above?
I came across this blog post which got me going in the right direction.
http://www.chadmoran.com/blog/2009/4/23/optimizing-url-generation-in-aspnet-mvc-part-2.html
It is a good idea to use the ActionLink method to write out links as your coworker says, that way they will always match your routes.
In your current case the reason it is writing out the method is because it is based on the default routing. You can fix this by adding another route above the default one in the Global.asax. You just need to stipulate the format you want like this:
routes.MapRoute(
"Recruiter",
"Recruiter/{id}/{name}",
new { controller = "Recruiter", action = "Details" }
);
MVC will work through your routes in the order they are registered so putting this before the default will make it use your route instead.
EDIT:
You might find this route debugging tool useful.

Resources