route to reflect hierarchical url/menu structure - asp.net-mvc

i've created a basic mvc3 website whereby each controller represents the first folder in a url structure.
for example, the "food" and "drinks" folders below are controller. there are only two controllers which contain all of the sub-items in them.
ie in the first line of the example, controller=food, method=asian
in the second line controller=food, method=pad-thai and so on and so forth.
www.mysite.com/food/asian/
www.mysite.com/food/asian/pad-thai
www.mysite.com/food/italian/chicken-parmigiana
www.mysite.com/drinks/cocktails/bloody-mary
how would i write routes so that www.mysite.com/food/asian/pad-thai will direct to the food controller and the paid thai method within that controller, and also have a rule to send from www.mysite.com/food/asian/ to the food controller and asian index method??

The MVC design pattern isn't for rewriting URLs to point to folder structures. It can do this but it certainly isn't its main purpose. If you're trying to create a URL structure with static content, it might be easier to use the URL rewriting functionality built into IIS.
If you're creating a full MVC application, set up FoodController and DrinkController to serve up your views, for example:
public class FoodController : Controller
{
public ActionResult ViewDishByTag(string itemType, string itemTag)
{
// If an itemType is displayed without itemTag, return an 'index' list of possible dishes...
// Alternatively, either return a "static" view of your page, e.g.
if (itemTag== "pad-thai")
return View("PadThai"); // where PadThai is a view in your shared views folder
// Alternatively, look up the dish information in a database and bind return it to the view
return ("RecipeView", myRepo.GetDishByTag(itemTag));
}
}
Using the example above, your route might look a little like this:
routes.MapRoute(
"myRoute",
"{controller}/{itemType}/{itemTag}",
new
{
controller = UrlParameter.Required,
action = "ViewDishByTag",
itemtype = UrlParameter.Optional,
itemTag = UrlParameter.Optional
}
);
Your question doesn't contain much detail about your implementation, so if you'd like me to expand on anything, please update your question.

Related

Mvc: Same ActionResult with different name depending url

I would like to know what's the best way to achieve this.
I have an ActionResult in my controller, actually it has news name, now I need to internationlize my website and I can't have the same news name, it must to change depending the country where it's visited.
for example, now I need something like.
www.something.com/en/us/news for english version
www.something.com/co/es/noticias for spanish version
you have the point for the next countries.
I don't think I need to create x methods depending x urls that make exactly the same, but I don't know how to achieve it in a really efficient way ... thanks
You could create a new class TranslatedRoute and TranslationProvider to map the different translations to the same action. Then you can plug these into the routing system and override the default mapping.
Here's a good blog post which describes the idea: http://blog.maartenballiauw.be/post/2010/01/26/Translating-routes-%28ASPNET-MVC-and-Webforms%29.aspx
How does your routing work now? Maybe something like this answer would work, if you don't already use it. Perhaps something variation of this, with the parts of the URL in a different order, to suit your needs. For example, the controller doesn't necessarily have to come first in the route (or at all, in this case just always using the same controller name). Make some sort of map that gets you the word "news" in each different language, using the language code as a key.
// populate this map somewhere - language code to word for "news" (and any other name of the controllers that you have)
var newsControllerMap = new Dictionary<string, string>();
newsControllerMap["en"] = "news"; // etc.
// ...
// inside of the RouteConfig class (MVC 4) or RegisterRoutes() method in Global.asax.cs (MVC 3)
// just making an assumption that whatever class/entity you use ("LanguageAndCountry" in this case) also has a country code to make this easier. Obviously this would be refactored to have better naming/functionality to make sense and meet your needs.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
LanguageAndCountryRepository langRepo = new LanguageAndCountryRepository();
var languagesandCountries = langRepo.GetAllLanguagesWithCountries();
foreach (LanguageAndCountry langAndCountry in languagesandCountries)
{
routes.MapRoute(
"LocalizationNews_" + langAndCountry.LanguageAbbreviation,
langAndCountry.LanguageAbbreviation + "/" + langAndCountry.CountryCode + "/" + newsControllerMap[langAndCountry.LanguageAbbreviation],
new { lang = language.LanguageAbbreviation, country = langAndCountry.CountryCode, controller = "News", action = "Index"});
// map more routes to each controller you have, each controller having a corresponding map to the name of the controller in any given language
}

Handling Brochure-style pages in ASP.Net MVC

I've built a number of ASP.Net MVC sites and in each of them there are a number of pages suited to MVC's Controllers and Actions, and a number of pages that are really just brochure pages - /why, /why/ouradvantage, /about, etc - pages that have no real functionality, just a View, maybe a Layout, and that's it.
For these brochure-style pages I'd really prefer to have just the View and a good Route to find it, so I could put the /why at /Brochure/Why.cshtml or /Brochure/Why/Index.cshtml and either way it will be picked up just fine. I'd like to avoid making silly Controllers and Actions (as I've done in the past) to handle this set of URLs and pages.
How can I go about this in an ASP.Net MVC project? This must be a common need.
EDIT: An example of how I can do this the verbose way:
I could use the standard MVC route ({controller}/{action}/{id}) and spam a bunch of useless Controllers to get the set of URLs and pages I want. Every time I want to add a brochure-style (no functionality, just a View) page I'd add a Controller or Action like this:
Why Controller:
public class WhyController : Controller
{
public ViewResult Index()
{
return View();
}
public ViewResult OurAdvantage()
{
return View();
}
}
This puts a View at /why and /why/ouradvantage - clean URLs. If I wanted an /about page, I could add another Controller that does nothing but return a View named AboutController. If it had 5 subpages I could add 5 Actions to that Controller, all of those Actions doing nothing but return a View.
If these brochure-style pages in the MVC site amounted to say, 100 pages, I'd have quite a few needless Controllers and Actions all doing nothing really. Not very DRY. I'm interested in a way to just put Views in a folder in my Project and have them accessible just for being there (Configuration through Convention), at clean URLs like /why and /why/ouradvantage.
There are a few ways that get me close:
I could put a bunch of .cshtml pages in and visit them directly - but then I have to have the file extension in the URL and the View files themselves have to sit in the root.
I could use ASP.Net Areas to define an Area for these, but then all brochure-style pages have to sit at least one URL segment deep and I still have the above problem of file extensions in URLs.
There are crazy Routes I can define.
I suspect this comes up often in MVC projects that have a small or large number of these Brochure-style pages - it seems like there should be a clean way to do this.
EDIT: A crappy solution that spams the routing engine.
Create a class that maps routes like:
public static void MapRoutes(RouteCollection routes, string appRoot, string path)
{
if (!path.Contains("~/"))
throw new NotSupportedException("Pages path must be virtual (use ~/ syntax).");
var physicalPath = appRoot + path.Substring(2).Replace("/", "\\");
var dir = new DirectoryInfo(physicalPath);
var pages = dir.GetFiles("*.cshtml", SearchOption.AllDirectories);
int rootLength = appRoot.Length;
var rootParsed = pages.Select(p => "~/" + p.FullName.Substring(rootLength).Replace("\\", "/"));
int folderPathLength = path.Length + 1;
var mapped = rootParsed.Select(p => new {
Url = p.Substring(folderPathLength, p.Length - 7 - folderPathLength),
File = p
});
var routedPages = mapped.Select(m => routes.MapRoute(
name: m.Url,
url: "{*url}",
defaults: new { path = m.File, Controller = "Brochure", Action = "Catchall" },
constraints: new { url = m.Url }
)).ToArray();
}
You can call this in RouteConfig like:
BrochureRoute.MapRoutes(routes, Server.MapPath("~/"), "~/Brochure");
That obviously maps all these pages to a BrochureController, which you'll need as well:
public class BrochureController : Controller
{
public ViewResult Catchall(string path)
{
return View(path);
}
}
2 problems:
It spams the routing engine as I mentioned - if you have 100 pages you have 100 routes.
Passing the path as above seems to upset the normal Razor pipeline - visiting a page in this manner gets you an error like:
The view at '~/Brochure/About.cshtml' must derive from WebViewPage
To map individual pages RouteCollection.MapPageRoute method can be used.
routes.MapPageRoute("", "why", "~/Brochure/Why.aspx");
Sure, you could use Razor view engine (.cshtml) instead of WebForms.
Check MSDN documentation for some more details.
Update 2
You are right you won't be able to use this for for .cshtml pages. However, you don't need to use routing to access Web Pages (.cshtml files). It is enough to create the files, and omit the extension in URLs. To achieve your desired structure you could do this:
Your web project must allow Web Pages rendering. To enable this go to web.config and set webpages.Enabled to true. (<add key="webpages:Enabled" value="true" />)
Create folder why in your web application root
Add MVC ViewPage named Index.cshtml. This will be accessible from http://yoursite.com/why
Add MVC ViewPage named ouradvantages.cshtml. This will be accessible from http://yoursite.com/why/ouradvantages
You will also be able to access url data using #UrlData collection
Check more about this here. More about Web Pages in general is also available on asp.net website.
If for some reason you really need to use routing, you'll need custom RouteHandler. You can find one implemented on Nuget. Usage examples of this package are here.
Update
If you'd like to avoid manually adding each route you have few choices.
1) Create a convention to identify brochure pages
You could do this by expecting URLs to be brochure pages by default, and isolating "non-brochure" pages to specific sections:
routes.MapPageRoute("Default", "{brochurepage}", "~/Brochure/{brochurepage}.aspx");
// isolate non-brochure pages to "site" section
routes.MapRoute(
"",
"site/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
2) Hold brochure page names in a collection
List<string> brochurePages = new List<string>()
{ "about", "why", "contact" }; // add more pages here
....
foreach (var brochurePage in brochurePages)
routes.MapPageRoute("", brochurePage, "~/Brochure/" + brochurePage + ".aspx");
I have to be very specific about two URLs you mentioned in your question. To implement routing for "/why" and "/why/ouradvantage" you will NOT be able to write elegant code. This will take special handling.
If your issue is that you don't want to have separate controllers for all these "silly" pages, may I suggest that you create a logical way to route them to a singular controller and then render the correct data/ view based on the route?
For instance, your URLs could read "/Pages/Why/About" and you could route that to your pages controller and it would receive the "Why" and "About" as parameters and you could render your content accordingly. Does that make sense?

ASP.NET MVC Dynamic Action with Hyphens

I am working on an ASP.NET MVC project. I need to be able to map a route such as this:
http://www.mysite.com/Products/Tennis-Shoes
Where the "Action" part of the URL (Tennis-Shoes") could be one of a list of possibilities. I do not want to have to create a separate Action method in my controller for each. I want to map them all to one Action method and I will handle the View that is displayed from there.
I have this working fine by adding a route mapping. However, there are some "Actions" that will need to have a hyphen in them. ASP.NET MVC routing is trying to parse that hyphen before I can send it to my action. I have tried to create my own custom Route Handler, but it's never even called. Once I had a hyphen, all routes are ignored, even my custom one.
Any suggestions? Details about the hyphen situation? Thanks you.
Looking at the URL and reading your description, Tennis-Shoes in your example doesn't sound like it should be an action, but a Route parameter. Let's say we have the following controller
public class ProductsController : Controller
{
public ActionResult Details(string product)
{
// do something interesting based on product...
return View(product);
}
}
The Details action is going to handle any URLs along the lines of
http://www.mysite.com/Products/{product}
using the following route
routes.MapRoute(
null,
"Products/{product}",
new
{
controller = "Products",
action = "Details"
});
You might decide to use a different View based on the product string, but this is just a basic example.

Refactoring my route to be more dynamic

Currently my URL structure is like this:
www.example.com/honda/
www.example.com/honda/add
www.example.com/honda/29343
I have a controller named HondaController.
Now I want to refactor this so I can support more car manufacturers.
The database has a table that stores all the manufacturers that I want to support.
How can I keep my URL like above, but now support:
www.example.com/ford
www.example.com/toyota/add
etc.
I can easily rename the HondaController to CarController, and just pass in the string 'honda' or 'toyota' and my controller will work (it is hard coded to 'honda' right now).
Is this possible? I'm not sure how how to make a route dynamic based on what I have in the database.
Any part of your route can be dynamic just be making it into a route parameter. So instead of "/honda/{action}", do:
/{manufacturer}/{action}
This will give you a parameter called "manufacturer" that was passed to your action method. So your action method signature could now be:
public ActionResult add(string manufacturer) { }
It would be up to you to verify that the manufacturer parameter correctly matched the list of manufacturers in the database - it would probably be best to cache this list for a quicker lookup.
Updated: What I mean by "you have to take out the default parameters" for the default route is this. If you have:
route.MapRoute("Default", "/{controller}/{action}/{id}",
new { id = 1 } // <-- this is the parameter default
);
then this route will match any url with two segments, as well as any url with three segments. So "/product/add/1" will be handled by this route, but so will "/product/add".
If you take out the "new { id = 1 }" part, it will only handle URL's that look like "/product/add/1".
i have made something like this for granite as i wanted to have a material controller but have a url like so:
black/granite/worktops
black/quartz/worktops
etc
i did this route:
routes.MapRoute("Quote", "quote/{color}/{surface}/{type}",
new {controller = "Quote", action = "surface"});
swap quote for car so u can have:
car/honda/accord
your route can then be
routes.MapRoute("cars", "car/{make}/{model}",
new {controller = "Cars", action = "Index"});
your actionResults can then look like this:
public ActionResult Index(string make, string model)
{
//logic here to get where make and model
return View();
}
that i think covers it
What I recommend is instead using:
domain/m/<manufacturer>/<action>
Where 'm' is the manufacturer controller. This will allow you to use the same controller for all of your extensions and save you a lot of headache in the future, especially when adding new features. Using a one-letter controller is often times desirable when you want to retain your first variable ( in this case) as the first point of interest.

URLs the asp.net mvc way

In the conventional website a url displayed as:
http://www.mySite.com/Topics
would typically mean a page sits in a subfolder below root named 'Topics' and have a page named default.htm (or similar).
I'm trying to get my head in gear with the MVC way of doing things and understand just enough of routing to know i should be thinking of URLs differently.
So if i have a db-driven page that i'd typically script in a physical page located at /Topics/index.aspx - how does this look in an MVC app?
mny thx
--steve...
It sounds like you are used to breaking down your website in terms of resources(topics, users etc) to structure your site. This is good, because now you can more or less think in terms of controllers rather than folders.
Let's say you have a structure like this in WebForms ASP.NET.
-Topics
-index.aspx
-newtopic.aspx
-topicdetails.aspx
-Users
-index.aspx
-newuser.aspx
-userdetails.aspx
The structure in an MVC app will be pretty much the same from a users point of view, but instead of mapping a url to a folder, you map a url to a controller. Instead of the folder(resource) having files inside it, it has actions.
-TopicController
-index
-new
-details
-UserController
-index
-new
-details
Each one of these Actions will then decide what view (be this html, or json/xml) needs to be returned to the browser.
Actions can act differently depending on what HTTP verb they're repsonding to. For example;
public class UserController : Controller
{
public ActionResult Create()
{
return View(new User());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(User user)
{
// code to validate /save user
if (notValid)
return new View(user);
else
return new View("UserCreatedConfirmation");
}
}
This is sort of a boiled down version of RESTful URLs, which I recommend you take a look at. They can help simplify the design of your application.
It looks just like you want it to be.
Routing enables URL to be quite virtual. In asp.net mvc it will end at specified controller action method which will decide what to do further (i.e. - it can return specified view wherever it's physical location is, it can return plain text, it can return something serialized in JSON/XML).
Here are some external links:
URL routing introduction by ScottGu
ASP.NET MVC tutorials by Stephan Walther
You would have an default view that is associated with an action on the Topics controller.
For example, a list page (list.aspx) with the other views that is tied to the list action of the Topics controller.
That is assuming the default routing engine rules, which you can change.
Read more here:
http://weblogs.asp.net/scottgu/archive/2007/12/03/asp-net-mvc-framework-part-2-url-routing.aspx
IMHO this is what you need for your routes.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Topics", action = "Index", id = "" } // Parameter defaults
);
You would need a TopicsController that you build the View (topics) on.

Resources