I currently have a Products controller with a hardcoded 'Product' actionresult per product (as the products are fixed and do not change):
site.com/Products/Product
site.com/nl/Products/Product
This results in a single page per product containing all information.
Now I would like to create multiple pages per product to highlight some features or options instead of showing a single product page.
e.g.:
site.com/nl/Products/Product/Detail1
site.com/nl/Products/Product/Option2
site.com/nl/Products/Product/Option16
What is the best way to do this?
Should I create e.g. ProductDetail1 action and a ProductOption2 action?
You can responce different views in a single action
public class ProductsController : Controller
{
public ActionResult Product(int id, string view,)
{
Product prod = Context.GetProduct(id);
if(!string.IsNullOrEmpty(view))
{
switch(view.ToLower()){
case "detail": return View("Detail", prod.Detail);
case "option1": return View("Option1", prod.GetOption(1));
case "option2": return View("Option2", prod.GetOption(2));
}
}
return View();
}
}
Well you have your Controller Method:
public class ProductsController : Controller
{
public ActionResult Product(string option)
{
//here your logic
return View();
}
}
You can change your Default Route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{option}",
defaults: new { controller = "Home", action = "Index", option = UrlParameter.Optional }
);
Now if you call url like site.com/nl/Products/Product/Detail1 option object in your controller will have value Detail1. And you can do anything what whould you like with this param.
Related
I'm developing an online marketplace system in ASP.NET MVC where Users are able to create their own shops in my website with their own unique address (e.g MySite.com/UserShop) like some social medias such as Instagram which users' pages would be like "Instagram.com/YourPage".
I wanna do something similar in ASP.NET MVC. This is my stucture:
I have multiple controllers such as Home, Panel and ... and I also have a controller named ShopController which I excepted its Index action method to show users' pages (shops). It's like this:
[RoutePrefix("")]
public class ShopController : Controller
{
[Route("{shopName}")]
public ActionResult Index(string shopName)
{
return View();
}
}
When I enter the some address http://MySite/UserPage it works fine, but when I want to open the urls of my own website like http://MySite/Panel - I get exception: Multiple controller types were found that match the URL.
I think I have to set order for controllers and action methods (First my own action methods, and then ShopController), I know it can be done within single controllers by using [Order] attribute, but I don't know how to do this across all controllers.
How can I fix this and make it work properly?
Because of the desired flexibility with the custom user routes, you will end up with route conflicts as the user route is too general, which will make it also match your other site routes and cause route conflicts.
Attribute routes are checked before convention-based routes, so the shop controller will catch all requests.
Convention based routes would need to be used in this case if you want user routes to be on the root of the site. This is because the order in which routes are added to the route table are important as general routes will match before more specialised/targeted routes.
Consider mixing attribute routing and convention-based routing where the custom user routes will use convention based routes while your other controllers will use attribute routing.
[RoutePrefix("Home")]
public class HomeController : Controller {
[HttpGet]
[Route("")] //GET home
[Route("~/", Name = "default")] // Site root
public ActionResult Index() {
return View();
}
[HttpGet]
[Route("contact")] //GET home/contact
[Route("~/contact")] //GET contact
public ActionResult Contact() {
return View();
}
[HttpGet]
[Route("about")] //GET home/about
[Route("~/about")] //GET about
public ActionResult About() {
return View();
}
//...other actions
}
[RoutePrefix("Panel")]
public class PanelController : Controller {
[HttpGet]
[Route("")] //GET panel
public ActionResult Index() {
return View();
}
[HttpGet]
[Route("another-action")] //GET panel/another-action
public ActionResult Other() {
return View();
}
//...other actions
}
The attribute routes, because they are registered before convention routes will match before the user defined routes, which can use convention-based routing
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Attribute routing
routes.MapMvcAttributeRoutes();
//Convention based routing
//User route:
routes.MapRoute(
name: "UserRoot",
url: "{shopName}/{action}", //eg MySite.com/UserShop
defaults: new { controller = "Shop", action = "Index"}
);
//Catch-All InValid (NotFound) Routes
routes.MapRoute(
name: "NotFound",
url: "{*url}",
defaults: new { controller = "Error", action = "NotFound" }
);
}
}
The attribute routes would then need to be removed from the shop controller to avoid conflicts with the other site controllers
public class ShopController : Controller {
//eg MySite.com/UserShop
//eg MySite.com/UserShop/index
public ActionResult Index(string shopName) {
return View();
}
//eg MySite.com/UserShop/contact
public ActionResult Contact(string shopName) {
return View();
}
//eg MySite.com/UserShop/about
public ActionResult About(string shopName) {
return View();
}
//...other actions
}
So now calls to MySite.com/UserShop will be routed to the correct shop controller and still allow the site controllers to be accessed.
While it is however more labour intensive than the convention based user routes, that is the trade off to get the desired routing behavior.
I have a bunch of mostly-static pages (about 40),
like: order-form01.html, order-form02.html, orderform03.html etc..
Should each of them have its own Controller/Action, or is that possible to have one dynamic Controller/Action for all of them?
My Url should look like this: http://MyProject/GlobalController/IndividualView and for the above example: http://MyProject/OrderForm/order-form01, http://MyProject/OrderForm/order-form02 etc..
Thanks in advance.
Yes it's very easy AND you don't need a switch statement or any other redundant logic.
public class MyController
{
public ActionResult Page(string file)
{
return View(file);
}
}
The magic is in the Route Map:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// New MapRoute for your 40+ files..
routes.MapRoute(
"OrdeForm",
"OrderForm/{file}",
new { controller = "MyController", action = "Page", {file} = "" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
Additionally:
I pass the View name in the query string.
Is not required, but is supported. The following urls will work:
// Url Parameters
http://MyProject/OrderForm/order-form01
http://MyProject/OrderForm/order-form02
// Querystring Parameters
http://MyProject/OrderForm?file=order-form01
http://MyProject/OrderForm?file=order-form02
The only catch is that you need to rename your html files to cshtml and place them in the correct directory for the ViewEngine to find.
#Erik, I also bit of new to mvc . Could you please explain your route map as of how is it possible with default raute again and again
Routes are broken down into 3 values:
Controller
Action
Parameter(s)
At a bare minimum, the controller and action are required. Where the values come from is not dependent on the Url. For example, in the following Url and Map Route...
// Url
http://MyProject/
// MapRoute
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = "" }
);
// Controller named "Home" matches the default in the above route
// Method named "Index" matches the default in the above route
public class HomeController {
public ActionResult Index() {
return new EmptyResult();
}
}
... everything still works because we provided a default value for the controller and action.
Ok let's break down the URL you want:
http://MyProject/OrderForm/order-form01
http://MyProject/OrderForm/order-form02
http://MyProject/<identifier>/{parameter}
You have one identifier that tells me route (OrderForm) and one changing value that because it changes and you want one value, should be a parameter.
http://MyProject/<identifier>/{file}
The name of the parameter makes no difference as long as it matches the signature of the controller method:
http://MyProject/{Controller}/{file}
public class HomeController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
or
http://MyProject/{Controller}/{einstein}
public class HomeController {
public ActionResult Index(string einstein) {
return new EmptyResult();
}
}
I named the parameter file, because it tells me it's the parameter is a name of a file, whereas the name einstein has no inherent description so is a terrible name for a variable.
http://MyProject/{Controller}/{file}
// MapRoute
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = "" }
);
// Controller named "Home" matches the default in the above route
// Method named "Index" matches the default in the above route
public class HomeController {
public ActionResult Index() {
return new EmptyResult();
}
}
Now we only want this route to run when the identifier is OrderForm so we don't allow that to be a value, we hard code it.
url: "OrderForm/...
Next we have a value that keeps changing, so we to add url parameter:
url: "OrderForm/{file}"
Now we have an issue because we aren't allowing MVC to parse values from the url to populate Controller nor Action so we must supply them.
routes.MapRoute(
name: "",
url: "OrderForm/{file}",
defaults: new { controller = "Home", action = "Index", file = "" }
);
Here we've mapped the url http://MyProject/OrderForm/{file} to:
public class HomeController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
Now I would choose to to update the defaults to something more specific and descriptive:
routes.MapRoute(
name: "",
url: "OrderForm/{file}",
defaults: new { controller = "OrderForm", action = "Index", file = "" }
);
public class OrderFormController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
Hope that all makes sense.
After the question edited :my solution is, you can have one controller/action and it should call view (cshtml). Your querystring data should be pass to view as of viewbag variable and partial views should be called acording to the viewbag variable. noo need of editing routing table even(if you are willing to pass it as a query string).
//your routeconfig will be
routes.MapRoute(
name: "default",
url: "{controller}/{file}",
defaults: new { controller = "OrderForm", action = "Index", file = "" }
);
//here no need to have 40 routing table one is enough
//your controller/action will be
public class OrderForm
{
public ActionResult Index(string file)
{
ViewBag.Orderform=file
return View(file);
}
}
//view bag variable is accessible in view as well as in javascript
But I would say as best practice, you can modify default routing to access all urls and navigate it to same controller/action and let that action to return the view. After that use angular / knockout js to handle client side routing and based on it the partial views should be loaded.(still your url will be different for your 40 pages but noo need to pass it as query string)
//your route table will be
routes.MapRoute(
name: "default",
url: "{controller}/{file}",
defaults: new { controller = "OrderForm", action = "Index"}
);
//your controller will be
public class OrderForm
{
public ActionResult Index()
{
return View(file);
}
Navigation should be handled by client side routing
What would be the easiest way to make a page title to be the url?
Currently I have:
http://localhost:53379/Home/Where
http://localhost:53379/Home/About
http://localhost:53379/Home/What
and would like to have
http://localhost:53379/where-to-buy
http://localhost:53379/about-us
http://localhost:53379/what-are-we
I thought about adding a route to each page (there's only 9 pages) but I wonder if there's something better, for example for big sites.
routes.MapRoute(
name: "Default",
url: "where-to-buy",
defaults: new {
controller = "Home",
action = "Where",
id = UrlParameter.Optional
}
);
...
and I would like to have it in English and Local language as well, so adding more routes would not make that much sense...
If you need to fetch pages dynamically from the database, define a new route which will catch all requests. This route should be defined last.
routes.MapRoute(
name: "Dynamic",
url: "{title}",
defaults: new {
controller = "Home",
action = "Dynamic",
title = ""
}
)
Then in your controller:
public class HomeController {
public ActionResult Dynamic(string title) {
// All requests not matching an existing url will land here.
var page = _database.GetPageByTitle(title);
return View(page);
}
}
Obviously all pages need to have a title (or slug, as it's commonly referred to) defined.
If you have static actions for each page, you could use AttributeRouting. It will allow you to specify the route for each action using an attribute:
public class SampleController : Controller
{
[GET("Sample")]
public ActionResult Index() { /* ... */ }
[POST("Sample")]
public ActionResult Create() { /* ... */ }
[PUT("Sample/{id}")]
public ActionResult Update(int id) { /* ... */ }
[DELETE("Sample/{id}")]
public string Destroy(int id) { /* ... */ }
[Route("Sample/Any-Method-Will-Do")]
public string Wildman() { /* ... */ }
}
I use it on a mid-sized project and it's working pretty well. The big win is that you always know where your routes are defined.
I need my page names to have a dash in the name. E.G our-vision
I'm new to MVC & c# so I may be going about all this wrong.
Here is my controller:
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
//
// GET: /our-vision/
public ActionResult ourVision()
{
return View();
}
}
And then in my views, I have Views/Home/ourVision.cshtml.
When I compile and go to http://localhost/ourVision it works, but when I go to http://localhost/our-vision it does not.
Here is my routing:
routes.MapRoute(
"Default", // Route name
"{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
You'll need to do a few things in order to achieve that.
First, to achieve our-Vision, you'll need to give your action method the ActionName attribute, like so:
[ActionName("our-Vision")]
public ActionResult ourVision()
Next, you'll have to rename your ourVision.cshtml view to be our-Vision.cshtml
Finally, whenever you're using Url.Action or ActionLink, you need to use our-Vision and not vision, like so:
Url.Action("our-Vision", "Home");
IMHO
The best way to do this - is define new route in route engine:
routes.MapRoute(
"OurVision", // Route name
"our-vision", // URL with parameters
new { controller = "Home", action = "ourVision" } // Parameter defaults
);
So I am a little confused as to how to handle some MVC Routing
I have an AdminController
public class AdminController : Controller
{
//
// GET: /Admin/
public ActionResult Index()
{
return View();
}
public ActionResult Users()
{
return View();
}
public ActionResult Books()
{
return View();
}
}
Which works fine. So I can go to /Admin/Books
This is the admin menu for managing books. Now in there I'd like to be able to route like
/Admin/Books/ViewBook/10
or
/Admin/Books/Add
Something like that. I can't seem to grasp how to route these things that way.
I made a controller
AdminBookController
public class AdminBooksController : Controller
{
//
// GET: /AdminBooks/
public ActionResult List()
{
return View();
}
public ActionResult Add()
{
return View();
}
[HttpGet]
public ViewResult BookDetails(Guid guid)
{
return View();
}
[HttpPost]
public ViewResult BookDetails(ModifyBook Book)
{
if (ModelState.IsValid)
return View("Book successfully Edited!");
else
return View();
}
}
}
but I don't want it to be /AdminBooks I feel like /Admin/Books/Action/Param is much nicer.
Thanks in Advance!
If you want those urls to map to your AdminBooks controller, you'll need to map the following routes (in this order):
// maps /Admin/Books/ViewBook/{id} to AdminBooksController.BookDetails(id)
routes.MapRoute(
"AdminBooks_ViewBook", // Route name
"Admin/Books/ViewBook/{id}", // URL with parameters
new { controller = "AdminBooks", action = "BookDetails", id = UrlParameter.Optional } // Parameter defaults
);
// maps /Admin/Books/{action}/{id} to AdminBooksController.{Action}(id)
routes.MapRoute(
"AdminBooks_Default", // Route name
"Admin/Books/{action}/{id}", // URL with parameters
new { controller = "AdminBooks", action = "List", id = UrlParameter.Optional } // Parameter defaults
);
Note: be sure to put these mappings before the default MVC route.
Consider creating an Admin Area and adding a BookController to that Area. See the following link for a walkthrough:
http://msdn.microsoft.com/en-us/library/ee671793.aspx
You can add a new route in your Global.asax file.
See this question:
Use MVC routing to alias a controller