Setting custom MVC routes with fixed parts - c#

I have 2 controllers:
public class HomeController : Controller
{
public ActionResult Index(string id) //id is category slug
{
if(string.IsNullOrEmpty(id))
id = "MainCategory";
var model = getCategoryPageModel(id);
return View(model);
}
}
public class PostController : Controller
{
public ActionResult Index(string id) //id is post slug
{
var model = getPostModel(id);
return View(model);
}
}
And this is my route config:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//angular route for admin section
routes.MapRoute(
name: "AngularCatchAllRoute",
url: "Admin/{*any}",
defaults: new { controller = "Admin", action = "Index"}
);
//route which includes language
routes.MapRoute(
name: "DefaultLocalized",
url: "{lang}/{controller}/{action}/{id}",
constraints: new { lang = #"(\w{2})|(\w{2}-\w{2})" }, // en or en-US
defaults: new { controller = "Home", action = "Index", id = "" }
);
//do I need this one??
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I want to have two types of custom routes:
www.mysite.xyz/categoryName - calls Home/Index/id with default language ('sr')
www.mysite.xyz/en/categoryName - calls Home/Index/id with language 'en'
www.mysite.xyz/Post/postID - calls Post/Index/id with default language('sr')
www.mysite.xyz/en/Post/postID - calls Post/Index/id with language 'en'
My 'DefaultLocalized' route already works fine with default and custom language route part, but my url has to contain all route parts: controller/action/id. I just want to simplyfy urls to be more readable to users.
Actually I made it work for post with 'lang' to be mandatory:
www.mysite.xyz/sr/Post/postID - but I want 'sr' to be default like in 'DefaultLocalized' route, there I don't have to set lang to be 'sr'...
I already have tried some answers from other similar questions but I did not make it work.

You can use attribute routing as below:
[Route("~/{cate}")]
[Route("{lang}/{cate}")]
public IActionResult Index(string lang, string cate)
{
return View();
}
That work for me with urls:
http://[host]/Mobile
http://[host]/en/Mobile
Hope this help!

Related

Returning a 404 when the URL does not contain Index

I am having trouble with routing in MVC. I have created a controller for my contact page, but unless I specify the route as /contact/index it will return a 404. I cannot see why it can't find the View with just /contact in the URL. My RouteConfig looks fine to me.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "T",
url: "T/{action}",
defaults: new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "Holding",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Holding", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The only reason I can see it not finding its View is because of the new Route I have configured to display a site holding page. Interestingly /t does display the 'demo' homepage, so I can't see why it doesn't like just /contact.
This S.O article told me that I could fix the problem by giving it its own MapRoute but I shouldn't have to do all that?
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Holding()
{
return View();
}
}
public class ContactController : Controller
{
// GET: Contact
public ActionResult Index()
{
return View();
}
}
It must be something silly, but I can't work it out.
You have route conflicts
/contact would match
routes.MapRoute(
name: "Holding",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Holding", id = UrlParameter.Optional }
);
But since contact controller has no Holding action you will get a 404 Not Found
And since it matched the Holding route it wont go on to the next Default route as first match wins.
The added route is too general so it will get a lot of false matches.
Based on the controllers shown, the added route is not needed. the holding path would still match the default route template. So it can actually be removed altogether.

How to Create URL friendly route and remove index

I know how to create a URL friendly route and i also know how to remove index. But I'm wondering how do I combine the two together?
Using this tutorial https://www.jerriepelser.com/blog/generate-seo-friendly-urls-aspnet-mvc/ I was able to add the following code to allow for url friendly routes.
routes.Add("ProductDetails", new SeoFriendlyRoute("drink/{id}",
new RouteValueDictionary(new { controller = "Drink", action = "Index" }),
new MvcRouteHandler()));
So instead of my url being test.com/index/drink/1 it now becomes test.com/index/drink/coke
The next set of code I have is to remove the index from the url.
routes.MapRoute("DrinkRoute",
"drink/{id}",
new { controller = "Drink", action = "Index" });
This will succesfully convert test.com/index/drink/1 to test.com/drink/1
May I ask how do I combine the two together so that I can have a route that will lead me to the correct controller action and display test.com/drink/coke
RouteConfig
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
You can also achieve the same using attribute routing which would provide more control of the desired routes.
Reference Attribute Routing in ASP.NET MVC 5
First you would need to enable attribute routing by calling routes.MapMvcAttributeRoutes(); in your RouteConfig. Make sure it is registered before convention-based routes.
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Attribute routes
routes.MapMvcAttributeRoutes();
//Default convention-based routes
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
With attribute routing enabled you can specify your routes by annotating your actions and controllers.
[RoutePrefix("drink")]
public class DrinkController : Controller {
[HttpGet]
[Route("{name}")] // GET drink/coke
public ActionResult Index(string name) {
//...use name to get model
return View();
}
//..
}
The above DrinkController.Index action is now mapped to GET drink/coke assuming test.com is the host of the controller as shown in your example.
Any controllers or actions not annotated by routing attributes will default back to the convention based routes (if any) registered in the routing table.
This means that you can have a mix of convention-based and attribute-based routes defined for your controllers.
Note however that once you use the attribute routes on a controller that you will have to use it on all its public actions.
If I understand you correctly, you can achieve desired behavior with RouteConfig.cs:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "DrinkRoute",
url: "drink/{id}",
defaults: new { controller = "Drink", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
In that case the url test.com/drink/coke will be hit with the controller DrinkController.cs and the action method Index. Id will be coke. Source code of this controller:
public class DrinkController : Controller
{
public ActionResult Index(string id)
{
return View();
}
}
You can remove the SEO Routes and give your action or controller full control:
public class DrinkssController : Controller
{
[Route("drink/{drinkName}")]
public ActionResult Index(string drinkName)
{
var model = _drinks.First(x => x.name == drinkName);
return View(model);
}
}

How to Create a dynamic Controller to handle many static Views in ASP.NET MVC?

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

ASP.NET MVC - Write less code for route template

This is my code:
routes.MapRouteLowercase(
name: "productadd",
url: "product/add",
defaults: new
{
controller = "Product",
action = "Add"
}
, namespaces: new[] { "project.Controllers" });
routes.MapRouteLowercase(
name: "productlike",
url: "product/like",
defaults: new
{
controller = "Product",
action = "Like"
}
, namespaces: new[] { "project.Controllers" });
routes.MapRouteLowercase(
name: "productshow",
url: "product/{id}/{seoName}",
defaults: new
{
controller = "Product",
action = "Get",
id = UrlParameter.Optional,
seoName = UrlParameter.Optional
}
, namespaces: new[] { "project.Controllers" });
I want a solution for writing less codes, actually a template for productshow and another template for product actions
you can use Attribute Based Routing in MVC. This is available by default in MVC5, or can be installed as a NuGet package in MVC4.
With Attribute Based Routing, you can define Attributes on your action methods, rather than magic string matches in the routing table. You can also perform more advanced type checking, such as minimum and maximum values, and optionally name routes for easy reference in your Razor.
as an example:
[RoutePrefix("product")]
public class ProductController : Controller {
//route /product
[Route]
public ActionResult Index() { ... }
//route /product/add
[Route("add")]
public ActionResult Add() { ... }
//route /product/like
// Like
[Route("like", Name="productlike")]
public ActionResult Like() { ... }
//route /product/{id}/{seoName}
[Route("{id?}/{seoName?}")]
public ActionResult Get(int? id, string seoName) { ... }
}
Saman, you can create a default route, like this one below. I'm not sure if that will work with "MapRouteLowercase", you can give it a try.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {
controller = "Home",
action = "Index",
id = UrlParameter.Optional }
);
This link has more options if you want.

Custom URL Routing in Asp.Net MVC 4

How can i do like this url (http://www.domain.com/friendly-content-title) in Asp.Net MVC 4.
Note: This parameter is always dynamic. URL may be different: "friendly-content-title"
I try to Custom Attribute but I dont catch this (friendly-content-title) parameters in ActionResult.
Views:
Home/Index
Home/Video
ActionResult:
// GET: /Home/
public ActionResult Index()
{
return View(Latest);
}
// GET: /Home/Video
public ActionResult Video(string permalink)
{
var title = permalink;
return View();
}
RouteConfig:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Home Page",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "Video Page",
url: "{Home}/{permalink}",
defaults: new { controller = "Home", action = "Video", permalink = "" }
);
}
What should I do for catch to url (/friendly-content-title)?
To enable attribute routing, call MapMvcAttributeRoutes during configuration. Following are the code snipped.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
}
}
In MVC5, we can combine attribute routing with convention-based routing. Following are the code snipped.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
It is very easy to make a URI parameter optional by adding a question mark to the route parameter. We can also specify a default value by using the form parameter=value. here is the full article.
Radim Köhler's solution is a good one.
But another option if you want more control over routing is using a custom constraint.
Here's an example
RouteConfig.cs
routes.MapRoute(
"PermaLinkRoute", //name of route
"{*customRoute}", //url - this pretty much catches everything
new {controller = "Home", action = "PermaLink", customRoute = UrlParameter.Optional},
new {customRoute = new PermaLinkRouteConstraint()});
So then on your home controller you could have action like this
HomeController.cs
public ActionResult PermaLink(string customRoute)
{
//customRoute would be /friendly-content-title..do what you want with it
}
The magic of this happens in the IRouteConstraint that we specified as the 4th argument in the MapRoute call.
PermaLinkRouteConstraint.cs
public class PermaLinkRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
var permaRoute = values[parameterName] as string;
if (permaRoute == null)
return false;
if (permaRoute == "friendly-content-title")
return true; //this indicates we should handle this route with our action specified
return false; //false means nope, this isn't a route we should handle
}
}
I just wanted to show a solution like this to show you can basically do anything you want.
Obviously this would need to be tweaked. Also you'd have to be careful not to have database calls or anything slow inside the Match method, as we set that to be called for every single request that comes through to your website (you could move it around to be called in different orders).
I would go with Radim Köhler's solution if it works for you.
What we would need, is some marker keyword. To clearly say that the url should be treated as the dynamic one, with friendly-content-title. I would suggest to use the keyword video, and then this would be the mapping of routes:
routes.MapRoute(
name: "VideoPage",
url: "video/{permalink}",
defaults: new { controller = "Home", action = "Video", permalink = "" }
);
routes.MapRoute(
name: "HomePage",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
Now, because VideoPage is declared as the first, all the urls (like these below) will be treated as the dynamic:
// these will be processed by Video action of the Home controller
domain/video/friendly-content-title
domain/video/friendly-content-title2
while any other (controllerName/ActionName) will be processed standard way

Categories