I am looking to produce an MVC site which has complete control of the url structure using routing.
routes.MapRoute(
"BlogView", // Route name
"view/{blogurl}", // URL with parameters
new { controller = "view", action = "view", productLink = ""} // Parameter defaults
);
routes.MapRoute(
"ProductGrid", // Route name
"category/{category}", // URL with parameters
new { controller = "category", action = "Index", category = "" } // Parameter defaults
);
I currently have the follwoing urls;
www.myblog.com/view/first-post
www.myblog.com/view/another-post
www.myblog.com/category/code
www.myblog.com/category/example
The first two urls relate to the detail view, the latter two relating ot a category view.
I have a database with the following structure; I ensure that the url (chrUrl) is a unique key.
url ( idurl (int),
chrURL,
chrAction,
chrController
)
My plan is that it is possible to look up rewrite the route lookup table so that the follwoing urls redirect to the correct view and page in the site;
www.myblog.com/first-post
www.myblog.com/another-post
www.myblog.com/code
www.myblog.com/example
Is this possible? Perofmance aside, is there a problem with this and how shoudl I go about this?
Since you don't have anything to differentiate between view and category items, I'd think about using a default controller which checks if the id is in the categories table and passes control to either the View or the Category controller.
routes.MapRoute(
"Root", // Route name
"/{id}", // URL with parameters
new { controller = "default", action = "redirect"} // Parameter defaults
);
But if you can live with having "/category/" in your category urls, that will be the more elegant solution on the back end.
First up, I would suggest coming up with a URL scheme that you are happy with. (seems you have one already)
Then I would use a ControllerFactory that will be responsible of Instantiating and
running the right action on the right controller. That is independent of any routes that you define in your route table - in fact it wont matter what you have there since you want your URL to be "database driven". You invoke the controller factory from your Global.asax file :
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new Controllers.ControllerFactory());
}
Then in the GetControllerType method in your ControllerFactory, you inspect the URL with
RequestContext.RouteData.Values.ContainsKey("keyname")
to work out the url scheme the user is presenting, and do a database look-up based on that.
If you want to take this one step further, your database can also contain references to the controller to instantiate, but that would be an overkill in your situation. As a quicknote, we use that in a solution where it was important to provide the ability for non-developers to create templates without involving dev - the database held url schemes, controller and views to render on that controller.
While you are at it, if you want to make things more elegant, create a BaseController that your controllers inherit from, and in there set things in your ViewData such as your SEO tags (MetaDescription, Title, etc) - look these up from your database.
Related
I believe this should have been asked and answered somewhere already, or is just a very basic thing, but I did not manage to find anything at all, I am guessing I might be querying my search wrong.
Either way, what I want is to display Index page without any domain paths.
What I want:
http://localhost:50024/
How I was able to make it with domain paths:
http://localhost:50024/Home/Index
I made a HomeController.cs and added a GET method for the Index view... which is in the Home folder under Views folder, and of course that creates domain paths. I do not care if I have to make extra controller or something, I just want it to display my index page without any paths. Thanks in advance!
You must set default values for the parameters in the route configuration in global.asax, similar to this:
routes.MapRoute( "Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults );
So that when the route parameters are missing, the routing system points the request to the desired controller and action.
just tested myself so i know it works
Test
obviously replace with what you have for the defaults in your routing if you have modified them or something
If all you're after is the "root" URL, you can also use ~/, like so:
Home
The Razor engine is smart enough to parse that URL in place, without needing to use the UrlHelper class.
I've created a controller, called ClientController.cs and VS automatically created the necessary View files in /Views/Client. But I wanted to get these pages in a different URL... So, it is /Client but I need it at /admin/client.
What should I change?
Thank you!
It's not clear what your functionality will be in the long run, but here are a few options that allow you to get the URL format you want:
Perhaps you want a controller called "Admin" and an action called "Client". This would give you a path of /Admin/Client by default
Alternatively, you can change your route maps. For example, the following with route /Admin/Client to the Index of your Client controller:
routes.MapRoute(
"Default", // Route name
"Admin/Client/{action}", // URL with parameters
new { controller = "Client", action = "Index" } // Parameter defaults
);
Or maybe even go a far as using "Areas", depending on what you need. Have a Google of that if you're interested in learning more
If you want it to be admin/client, then using the default routing you should create an Admin Controller with an ActionResult method called Client. Your views folder should have an admin folder with your client view inside.
I haven't done a lot of MVC but i believe this is what you do.
Now, the question. I have an eCommerce website and I have a controller called Store with an action Index. When someone types www.mysite.com/sony, I want to route to controller Store and action Index with parameter brand=Sony. If someone type www.mysite.com/sony-tv, I want to route to controller Store and action Index but with the parameters brand=sony and department=tv.
I obtained that by creating a route for every situation storing in a database, and building the routes dynamically when the app starts. Its works fine in a few cases.
First, i need to index the routes. I need to map the route sony-tv before the sony, otherwise the /sony-tv maps to /sony equally.
When i enter at site's home (www.mysite.com) and click at Url.Action("Index","Store", new {brand=Sony}) it route me to www.mysite.com/sony. NICE!. Now, inside the SONY brand i'll click at Url.Action("Index","Store", new {brand=sony, department=tv}) and I can see all TV's from Sony.
Everything is running fine until here. In the database, I have 1 route to /sony where I say i have a parameter named brand with value Sony and a constrained named brand with value Sony. I have another route saying the same way to sony-tv. The pattern sony-tv has a parameter named brand with value sony and a parameter named deparment with value tv, and the sames constraints of parameters.
In my head, its means that the route for www.mysite.com/Sony is Store/Index/brand=sony and the www.mysite.com/sony-tv is Store/Index/brand=sony&department=tv. With the constraints, i understand that if department is not TV or if the parameter department does not exists, it will send to www.mysite.com/sony
When i'm at www.mysite.com/sony-tv, if I pass my mouse over the other brands, the link build to Url.Action("Index","Store", new {brand=Apple}) is www.mysite.com/Apple-Tv
I have a route to Apple-TV equal to Sony. The URL exists but i'm not passing the TV parameter. I passed on this link (brands links) only the brand. I want to move the user to brand's root he's moved to brands + department.
I don't know, its looks like the department variable is passing through again and I don't know how to cancel that.
I'm completely wrong? What i'm doing is valid? I can do that? Where is my mistake?
At cshtml file:
#Html.ActionLink(febrand.Name.ToUpper(), "Index", new { controller = "Store", brand= febrand.FriendlyName, department = string.Empty })
at final html file (show source code from google chrome):
SONY
Fisrt, that seems like a lot of work for something that you could do with two custom routes. Additionally, as your route table gets larger (eg. adding more brands and/or departments), each request to your site will take longer to fulfill due to having to scan a larger list of routes.
So, lets attempt to fix your route table. Place these two routes above your default route in the global.asax:
routes.MapRoute(
"Department",
"{brand}-{department}",
new { controller = "Store", action = "Index" };
routes.MapRoute(
"Brand",
"{brand}",
new { controller = "Store", action = "Index" });
As for your issue, when you create your action link, the routing engine is holding onto the department route value from the view you are currently on. To make it forget that parameter when generating a link, send a null across for the department variable when you do not need it.
#Html.ActionLink("Apple", "Index", new{controller="Store", brand="Apple", department = string.empty});
EDIT
I feel you may be in a weird edge case with your routing (and there are numerous examples of this problem all across SO, such as here and here). The solution, other than the one I provided, is to switch to Html.RouteLink instead of Html.ActionLink.
RouteLink Signature that we are going to use
Html.RouteLink(string linkText, string routeName, object RouteValues)
Example Brand link for your code
#Html.RouteLink(febrand.Name.ToUpper(), "Brand", new { controller = "Store", action = "Index", brand= febrand.FriendlyName})
I have an architecture where I have numerous objects I can configure. Examples of URLs I want are as follows:
/configuration/building/add
/configuration/building/edit/1
/configuration/group/add
/configuration/group/edit/1
I have a Configuration controller but how do I intercept or deal with building/add and building/edit/1 etc... If it were AddBuilding I could simply add an AddBuilding() function, and similarily how do I get it to work for configuration/building/edit/
Here's what you can do for the first one - open up the Global.asax.cs file of your site and put this in RegisterRoutes before the standard MVC catch-all route (the one that uses the route "{controller}/{action}/{id}"):
routes.MapRoute("AddBuilding", "configuration/building/add",
new { controller = "Configuration", action = "AddBuilding" });
The others will be the same, but different names (first parameter) and action, whislt the edit routes but would include an {id} route placeholder and route parameter (but not optional - unlike the MVC default route):
routes.MapRoute("EditBuilding", "configuration/building/edit/{id}",
new { controller = "Configuration", action = "EditBuilding" });
By leaving the id off the route defaults we make it required. I'm assuming this, because I'm guessing the Url /Building/Edit doesn't logically map to anything.
As a side node - including verbs in your urls isn't really in keeping with REST methodology, however you're not the first to do it by a long way (I include myself in that too). That said - trying to keep to it usually makes your life a lot easier, as you'll find your controllers will be cleaner, as will your route table, and your site's URL space will be a lot smaller and more obviously hierarchical. This last point is - handy for zooming around the site at dev time, but more importantly it's crucial for SEO.
So (I've commented this code heavily, hopefully enough to provide some nuggets of knowledge!):
public class ConfigurationController{
////HTTP GET /Buildings
/// DISPLAYS BUILDINGS
public ActionResult Buildings(){
//get model and return view that shows all buildings with perhaps a
//partial in that for creating a new one (or you can use another action)
//The HTML form on that view will POST to the URL handled by the method below.
}
////HTTP POST /Buildings
/// CREATES A NEW BUILDING
//use ActionName here to make this and the method above accessible through
//the same URL
[ActionName("Buildings")]
[HttpPost]
public ActionResult CreateBuilding(BuildingModel model){
//validate the model, create the object and return the same
//view as the Buildings() method above (after re-loading all the
//buildings. Or, you can issue a redirect, effectively transferring
//control back to the method above.
}
////HTTP GET /Configuration/Building/id
///DISPLAYS A BUILDING
public ActionResult Building(int id){
//get building and return view, which also contains Edit functionality
}
////HTTP POST /Configuration/Building/id
///EDITS A BUILDING
[HttpPost]
public ActionResult Building(int id, BuildingModel model){
//very similar to the CreateBuilding method - and again you might
//either simply return a building view at the end, or redirect
//to the method above.
//Note that we don't need [ActionName] here because this and the
//get method can have the same method names, because they are overloads
//i.e. since they have different method signatures we can call them the same
//thing in code.
}
}
I've left off the group stuff to keep it short, and hopefully you'll be able to see how to do it from there.
With this in place, we only need at most two routes in Global.asax.cs - although I think the order will be important:
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("IndividualBuilding", "/configuration/buildings/{id}",
new { controller = "Configuration", action = "Building" });
routes.MapRoute("Buildings", "/configuration/buildings",
new { controller = "Configuration", action = "Buildings" });
Now we are using the HTTP verbs to signify what we intend to do with a particular request, and our URLs have become more 'logical'.
Another refactor
If you want to be 'clever' you can lump both buildings and groups under two routes
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("Individual", "/configuration/{controller}/{id}",
new { controller = "Configuration", action = "List" });
//again, handles GET and POST
routes.MapRoute("Collection", "/configuration/{controller}",
new { controller = "Configuration", action = "Single" });
Now you do both buildings and groups controllers as I showed above, but replace Buildings (remembering the ActionName attribute on the second method) with List and Building with Single.
One final thing to consider is that because of the default MVC route:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Default", action="Home", id = UrlParameter.Optional });
Both of your two controllers can still be routed via /Buildings/Single/1 or /Groups for example. This is a minor issue (dupe content isn't great SEO) but it can be something that people can use to sniff your site.
If you absolutely want to prevent this other url format; you can take out the default route, meaning you'd have to explicitly route other stuff that might already work (not a great issue).
Or you can use a little trick that will make it far harder: use explicit [ActionName] attributes with characters in the route name that won't be allowed through IIS - e.g. ":Single" or ":List", and then adjust our two routes from a couple of code blocks back accordingly.
So firstly you can create a controller action called AddBuilding() as you have hinted.
Then in your Global.asax file in the RegisterRoutes method you can add a route like so:
routes.MapRoute(
"AddBuilding", // Route name
"configuration/building/add", // URL with parameters
new { controller = "Configuration", action = "AddBuilding" }
);
You should not though that you will likely still be able to access the page using "/configuration/addbuilding" because of your default route mapping.
You edit one will be similar expect you will want to map the ID value for this:
routes.MapRoute(
"EditBuilding", // Route name
"configuration/building/edit/{id}", // URL with parameters
new { controller = "Configuration", action = "AddBuilding", id = UrlParameter.Optional }
);
I think you will need to add this code with the default MapRoute setup to ensure that one does not take priority
Another approach would be to create a Configuration MVC area, and then have a building and group controller in that Area.
You can do that by Attribute Routing in ASP.NET MVC 5. Something like following;
// eg: /reviews
[Route(“reviews”)]
public ActionResult Index() { … }
// eg: /reviews/5
[Route(“reviews/{reviewId}”)]
public ActionResult Show(int reviewId) { … }
// eg: /reviews/5/edit
[Route(“reviews/{reviewId}/edit”)]
public ActionResult Edit(int reviewId) { … }
You can add multiple route for the same controller as well. For details please check here
I code lots of ASP.NET but I'm kind of new with .net MVC, I've a default route registered like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
And I want to add another Administrator area on the site and all the URL would be something like "http://localhost/Administrator/controller1", "http://localhost/Administrator/controller2", etc. I've lot of controllers in the Administrator namespace and I'm trying to register those controller with only one MapRoute, I did something like this:
routes.MapRoute("Administrator_default", "Administrator/{controller}/{action}/{id}", new { controller = "Administrator", action = "Index", id = "" });
it works with those controller but one problem is that in some other controller while I try to do a redirect like:
return RedirectToAction("Index", "Forum");
Then I'll always be redirect to http://localhost/Administrator/Forum instead of http://localhost/Forum, it's not a big issue but make the URL looks strange, I tried to restrict to certain namespace but it's not working. It looks just as I'm trying to register two default route and .Net just match the first one, I'm wondering is there a way to make it two default route and map on only specific path only?
This exact issue is why Areas were added to MVC 2. http://www.asp.net/whitepapers/what-is-new-in-aspnet-mvc#_TOC3_2
Agree with Zach's answer.
Not ideal, but you do have the option to have controllers in the controller root folder (e.g. /controllers/HomeController.cs) of your project as well as the controllers in Areas (maybe high level root pages that display menus for areas).
Secondly a quick tip on using the RedirectToAction method. You can specify the area you would like to redirect too using the route parameters e.g:
RedirectToAction("Index","Form", new { area = "MyOtherArea" });