How do I map unmatched routes to the index action for that controller?
I'm using a client side router for routes like /Home/foo
routes.MapRoute(
name: "Test",
url: "{controller}/{*.}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
This currently results in a 404.
Your route that you used is correct, the problem is the orders of the routes that need to be added in write format:
for example if you have some routes like:
routes.MapRoute(
name: "PreTest",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Test",
url: "{controller}/{*.}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
So it is always matched with first route PreTest. Check your routes order. It is work like a dictionary that ordered. Check this for more information.
I would create an AuthorizeAttribute to handle your case. Then you can decorate your controller with that attribute.
Here's a small example to redirect your action base on a value in the route:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class RedirectAttribute:ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if(filterContext.Controller.ControllerContext.RouteData.Values.ContainsValue("Foo"))
{
//Redirect to the login for example
UrlHelper urlHelper = new UrlHelper(filterContext.HttpContext.Request.RequestContext);
string url = urlHelper.Action("actionName", "controllerName");
filterContext.Result = new RedirectResult(redirectUrl);
}
}
}
Here's how to use it in a controller:
[Redirect]
public class MyCustomController : AsyncController
{
public ActionResult Index()
{
return View();
}
public ActionResult Foo()
{
//It will redirect
return View();
}
}
Related
This question already has answers here:
Routing in ASP.NET MVC, showing username in URL
(2 answers)
Closed 4 years ago.
I have an Asp.Net MVC project whereby we allow our users to have public profiles.
I would like to improve the url, so that it is more friendly, and shorter.
The existing code is as follows -
public class ProfileController : Controller
{
private readonly IUserProfileService _userProfileService;
public ProfileController(IUserProfileService userProfileService)
{
this._userProfileService = userProfileService;
}
public ActionResult Index(string id)
{
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
return View(viewModel);
}
}
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Required for the route prefix attributes to work!
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"ProfileUrlIndexActionRemoval",
"Profile/{id}",
new { controller = "Profile", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The aforementioned code allows the following url to work (based on the default MVC routing) - www.mydomain.com/profile/john-doe
What routing do I need to implement, in order to allow the following url to work instead - www.mydomain.com/john-doe
Thanks.
This is a little tricky as you want the friendly URL in the root of the site while not conflicting with any other routes.
That would mean that if you have any other routes like About or Contact you would need to make sure that are in the route table before the friendly route to avoid route conflicts.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Required for the route prefix attributes to work!
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"ProfileUrlIndexActionRemoval",
"Profile/{id}",
new { controller = "Profile", action = "Index" }
);
routes.MapRoute(
name: "Home",
url: "Home/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "About",
url: "About/{action}/{id}",
defaults: new { controller = "About", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Contact",
url: "Contact/{action}/{id}",
defaults: new { controller = "Contact", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default_Frieldly",
"{*id}",
new { controller = "Profile", action = "Index" }
);
}
}
And finally because the default route will capture all unmatched routes, you will need to take not found profiles into account.
public class ProfileController : Controller {
//...code removed for brevity
public ActionResult Index(string id) {
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
if(viewModel == null) {
return NotFound();
}
return View(viewModel);
}
}
By having the profile controller prefix in the original URL it made it unique so as to avoid route conflicts, but in wanting the root friendly URL, while not impossible, you see the hoops needed to jump through in order to get the desired behavior.
This is how I would do it. Register a route that matches any string after the root slash.
Note that this severely limits the routes you can use for the application since not everything matching /{id} may actually be a user ID, which is why applications will typically prefix the route with /profile or /p.
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "UserIdRoute",
url: "{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
public ActionResult Index(string id)
{
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
return View();
}
I'm using ASP.NET MVC 4 with C#. I'm using areas and it's named like "Admin"
Here is my route config;
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(name: "PageBySlug",
url: "{slug}",
defaults: new {controller = "Home", action = "RenderPage"},
constraints: new {slug = ".+"});
routes.MapRoute(name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional},
namespaces: new[] { "Web.Frontend.Controllers.Controllers" });
}
}
I generated frontend page links like; "products/apple-iphone"
So I want to call them like this.
But the error is: The code can't get the controller / action method.
I used frontend page links like;
#Html.ActionLink(linkItem.Title, "RenderPage", routeValues: new {controller = "Home", slug = linkItem.PageSlug})
#Html.RouteLink(linkItem.Title, routeName: "PageBySlug", routeValues: new { controller = "Home", action = "RenderPage", slug = linkItem.PageSlug })
#linkItem.Title
#linkItem.Title
They are rendering url links like; http://localhost:1231/products/apple-iphone
It's like what I want. But when I click any link, asp.net mvc gives me this error:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /products/apple-iphone
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.6.1069.1
Here is my controller;
namespace Web.Frontend.Controllers
{
public class HomeController : BaseFrontendController
{
public ActionResult Index()
{
return View();
}
public ActionResult RenderPage(string slug)
{
return View();
}
}
}
So how can I catch every link request like this combined slug and turn my coded view ?
The problem is, When you request products/iphone, the routing engine don't know whether you meant the slug "products/iphone" or the controller "products" and action method "iphone".
You can write a custom route constraint to take care of this. This constraint will check whether the slug part of the urls is a valid controller or not, if yes,the controller action will be executed.
public class SlugConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values, RouteDirection routeDirection)
{
var asm = Assembly.GetExecutingAssembly();
//Get all the controller names
var controllerTypes = (from t in asm.GetExportedTypes()
where typeof(IController).IsAssignableFrom(t)
select t.Name.Replace("Controller", ""));
var slug = values["slug"];
if (slug != null)
{
if (controllerTypes.Any(x => x.Equals(slug.ToString(),
StringComparison.OrdinalIgnoreCase)))
{
return false;
}
else
{
var c = slug.ToString().Split('/');
if (c.Any())
{
var firstPart = c[0];
if (controllerTypes.Any(x => x.Equals(firstPart,
StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
}
return true;
}
return false;
}
}
Now use this route constraint when you register your custom route definition for the slug. make sure you use {*slug} in the route pattern. The * indicates it is anything(Ex : "a/b/c")(Variable number of url segments- more like a catch all)
routes.MapRoute(name: "PageBySlug",
url: "{*slug}",
defaults: new { controller = "Home", action = "RenderPage" },
constraints: new { slug = new SlugConstraint() }
, namespaces: new string[] { "Web.Frontend.Controllers.Controllers" });
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
, new string[] { "Web.Frontend.Controllers.Controllers" });
you can provide only this type of link
#linkItem.Title
Because Routetable find your route using Route name provided by you. so controller name and action name is not necessary.
I have Login action method in my home controller like this
[HttpGet]
public ActionResult Login()
{
return View();
}
I am having this Action method as start page of my application, however I want to re-write it like this
www.abc.com/MySite/security/login
I write this attribute after [HttpGet]
[Route("MySite/security/Login")]
Now the problem is,when I am running the application,its giving me error
The resource cannot be found.
This is my RoutConfig
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 = "Login" , id = UrlParameter.Optional }
);
}
How can I fix this issue,Also I am having same name method with HttpPost attribute,should I have to write Rout Attribute on it as well?
This should do the work:
[RoutePrefix("MySite/Security")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
[HttpPost]
[Route("Login")]
public ActionResult Login()
{
return View("~/Views/Home/Index.cshtml");
}
}
EDITED:
There is one way, but I'm not sure if it's the best way. You need to create another controller called DefaultController like this:
public class DefaultController : Controller
{
//
// GET: /Default/
public ActionResult Index()
{
return RedirectToAction("Login","Home");
}
}
In your RouteConfig.cs, change the 'Default' route with this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Default", action = "Index", id = UrlParameter.Optional }
);
This should do the job. I'm still trying to find other better ways.
First, you should add custom route on the top of a default route, since you have 2 action methods with different HTTP protocols and want to make custom routing with same action name.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
// custom route
routes.MapRoute(
name: "Login",
url: "MySite/{controller}/{action}/{id}",
defaults: new { controller = "Security", action = "Login", id = UrlParameter.Optional }
);
// default route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home" , action = "Index" , id = UrlParameter.Optional }
);
}
Note that your controller with Login method should be named SecurityController, then you can set attribute routing like this code:
// set all default prefix to /Security path
[RoutePrefix("Security")]
public class SecurityController : Controller
{
[Route("Login")]
public ActionResult Login()
{
return View();
}
}
Additionally, make sure you already registered the route in Global.asax file.
Any improvements & suggestions welcome.
i have project with Map Route (that's all):
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
and I have method in controller:
public ViewResult List(int id = 1)
{
...
}
and in List.cshtml:
#Html.ActionLink(i.ToString(), "List", "Product", new { id = i }, null)
but i want to change id to page, but not change it in RouteConfig.cs, i think that's some attribute which can config my route for action. I want this solution:
#Html.ActionLink(i.ToString(), "List", "Product", new { page = i }, null)
and
[maybe here I can add my specify route?]
public ViewResult List(int page = 1)
{
...
}
You can use attribute routing to override the convention.
First make sure attribute routing is enabled:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
}
}
Then add appropriate attribute to your controller method, for example:
[Route("YourControllerName/List/{page?}")]
public ViewResult List(int page = 1)
{
...
}
Question mark makes the page parameter optional.
If it's a default controller and action
[Route("")]
[Route("YourControllerName/List/{page?}")]
public ViewResult List(int page = 1)
{
...
}
More about attribute routing can be found here:
http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx
Do I have to route a special route for every action result in a controller, or do you do one route, and have to live by that standard thought the controller? I thought you could make a default route, and then a special route for any instance you wanted. I keep running into a problem where one of my routes will hit my action Results correctly, but then the others no longer work. This code is probably the wrong way, but hence why I am posting it here. PLease try to clarify this for me if you can. I understand that I am suppose to be able to do {controller}/{action}/{id} for example. So that should hit Settings/GetSite/{siteid} for the following
public ActionResult GetSite(int id);
Routes configuration:
routes.MapRoute(
"SettingsUpdateEnviorment",
"{controller}/{action}",
new { controller = "Settings", action = "UpdateProperties" },
new { httpMethod = new HttpMethodConstraint("POST") }
);
routes.MapRoute(
name: "ProfileRoute",
url: "Profiles/{userId}",
defaults: new
{
controller = "Profile",
action = "Index",
}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Settings", // Route name
"Settings/{id}", // URL with parameters
new { controller = "Settings", action = "Index" } // Parameter defaults
);
Controller Code:
public ActionResult Index(int id)
{
return View(model);
}
public ActionResult GetSite(int enviornmentID, string name)
{
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult AddSite(int id)
{
return RedirectToAction("Index", new { id = id });
}
So, the URL works as expected for Settings/1 to hit the Index actionresult Index(int id). Then, when I try to do the ActionResult for GetSite(int enviornmentID, string name) using the following actionLink:
#Html.ActionLink(site.Name, "GetSite", "Settings", new { enviornmentID = Model.Enviorment.EnvironmentID, name = site.Name }, null)
It creates the URL correctly as follows: Settings/GetSite?enviornmentID=1&name=CaseyTesting2, but gives me an error stating that I am trying to send a null value to my Index(int id) actionResult. I thought that since I am using the action name and it's same params, that MVC will figure the route out? Why is this not functioning for me, or what I am doing wrong? Thanks!
I realized what I was doing thanks to this article http://www.itworld.com/development/379646/aspnet-mvc-5-brings-attribute-based-routing. I was mixing up the order, when I had everything else correct. Then I was missing the param names being identical, when everything else was correct. So I kept having minor issues when trying to find the problem out. I also switched to MVC5's attribute routing, and like it much more.
So this is my code that is now working:
RoutConfig
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "ProfileRoute",
url: "Profiles/{userId}",
defaults: new
{
controller = "Profile",
action = "Index",
}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The controller code
[Authorize]
[RoutePrefix("settings")]
[Route("{action=index}")]
public class SettingsController : ZenController
{
[Route("{id:int}")]
public ActionResult Index(int id)
{
return View(model);
}
[Route("GetSite/{sitename:alpha}")]
public ActionResult GetSite(string sitename)
{
return RedirectToAction("Index");
}
Thanks again everyone! Happy coding!