Unable to understand the Asp.net MVC routing - asp.net-mvc-3

I have added the following route to my global.asax file :-
routes.MapRoute(
"Admin_Route",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "PriceCompare.Admin.Controllers" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "PriceCompare.Controllers" }
);
The admin controllers i.e. ManageCatsController, ManageBrandsController, etc. reside in PriceCompare.Admin.Controllers namespace and other general controllers reside in PriceCompare.Controllers namespace.
The problem is that i am able to visit all the controllers by adding Admin/ in front of them, irrespective of whether they are in PriceCompare.Admin.Controllers namespace.
Also, I am able to visit admin controllers directly without prefixing Admin/.
Why is this happening. Am i misunderstanding the routing behaviour.

You need to register your Admin area.
When I have registered routes for areas I've always done it like this:
Inside App/Areas/Admin folder create an AdminAreaRegistration.cs file with this in it...
using System.Web.Mvc;
namespace AppName.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin",
"Admin/{controller}/{action}/{id}",
new { controller="Home", action = "Index", id = UrlParameter.Optional },
new string[] { "AppName.Areas.Admin.Controllers" }
);
}
}
}
Now do this in Global.asax:
protected void Application_Start()
{
// Add this next line
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Add any other stuff (like IoC or whatever)
}
And only register your normal routes in RegisterRoutes in Global.asax, like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "AppName.Controllers" }
);
}
I would also recommend keeping the Area part inside your Admin namespace (so calling it PriceCompare.Areas.Admin.Controller in your case) as it will make life a lot easier later on when.
Please try this and let me know if it works :-)

Related

ServiceRoute in ASP.NET MVC with areas intercepts ActionLink to Home

I have an MVC 3 application with areas, and I am exposing a service from a specific area and controller. The routing to this service is defined inside the AreaRegistration like this
public class AreaAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "Area"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.Add(
new ServiceRoute("Area/Controller/Service",
new NinjectServiceHostFactory(), typeof(MyService)));
// ....
}
}
In my Global.asax.cs I only define a default route
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
In my _Layout.chshtml I have a link to my home page, where I give an empty area, and I expect it to find the Index action in the HomeController in the Controllers folder at the top (outside the Areas folder):
#Html.ActionLink("Home", "Index", "Home", new { area = "" }, null)
For some reason this ActionLink renders as
~/Area/Controller/Service?action=Index&controller=Home
If I comment out the ServiceRoute, the same ActionLink points to ~/ which is what I expect.
Any ideas how to fix this routing issue? The only workaround I have found is to use this instead:
Home
We were having this exact same problem. The order of route registration appears to be the issue, in that routes from areas will be registered before routes from the global.asax code.
To fix this issue, allowing the URL route to the service as well as preventing the postback from being targeted at the service URL try moving the ServiceRoute addition into the Global.asax.cs after the other route is registered.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
context.Routes.Add(
new ServiceRoute("Area/Controller/Service",
new NinjectServiceHostFactory(), typeof(MyService)));
}
This worked for us, but does of course come at the overhead of putting code pertaining to the area in the main project.

Custom route does not work in ASP.net MVC 3

I am trying to implement my custom route in ASP.net MVC 3 but I get this error:
The resource cannot be found.
global.asax
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"mycontroller", // Route name
"{controller}/{name}", // URL with parameters
new { controller = "MyController", action = "Search" } // Parameter defaults
);
}
MyController.cs
public class MyController : Controller
{
public ActionResult Search(string name)
{
return Content(name);
}
}
Try this instead:
routes.MapRoute(
"mycontroller", // Route name
"mycontroller/{name}", // URL with parameters
new { controller = "My", action = "Search", } // Parameter defaults
);
MyController won't be found because you don't have a controller named MyControllerController. By virtue of inheriting from Controller the convention will be looking for the URL token + "Controller".
Cheers.
Try changing it to
routes.MapRoute(
"mycontroller", // Route name
"{controller}/{name}", // URL with parameters
new { controller = "My", action = "Search", } // Parameter defaults
);
Please register it in Global.asax file

ASP.net MVC routing with optional first parameter

I need to provide following functionality for one of the web sites.
http://www.example.com/[sponsor]/{controller}/{action}
Depending on the [sponsor], the web page has to be customized.
I tried combination of registering the routes with Application_Start and Session_Start but not able to get it working.
public static void RegisterRoutes(RouteCollection routes, string sponsor)
{
if (routes[sponsor] == null)
{
routes.MapRoute(
sponsor, // Route name
sponsor + "/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
}
Also, the default behavior without [sponsor] should also function.
Can someone please let me know if it is technically feasible to have an optional first parameter in the MVC3 URL. If yes, please share the implementation. Thank you.
Updated Code
After making the changes as suggested by Sergey Kudriavtsev, the code works when value is given.
If name is not provided then MVC does not route to the controller/action.
Note that this works only for the home controller (both and non-sponsor). For other controllers/actions, even when sponsor parameter is specified it is not routing.
Please suggest what has to be modified.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"SponsorRoute",
"{sponsor}/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"NonSponsorRoute",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional, sponsor = string.Empty }
);
}
Action Method
public ActionResult Index(string sponsor)
{
}
In your case sponsor should not be treated as a constant part of URL, but as a variable part.
In Global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.MapRoute(
"SponsorRoute",
"{sponsor}/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"NonSponsorRoute",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional, sponsor=string.Empty }
);
...
}
In your controllers, for example, HomeController.cs:
namespace YourWebApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index(string sponsor)
{
// Here you can do any pre-processing depending on sponsor value, including redirects etc.
}
...
}
}
Note that type of this parameter will always be System.String and the name of route template component {sponsor} must exactly match the name of action parameter string sponsor in your controllers.
UPD: Added second route for non-sponsor case.
Please note that such setup will complicate your logic, because you might confuse different urls, for example URL
http://www.example.com/a/b/c
could be matched by both routes: first one will have sponsor=a, controller=b and action=c; second one will have controller=a, action=b and id=c.
This situation can be avoided if you specify more strict requirements to URLs - for example, you may want IDs to be numerical only. Restrictions are specified in fourth parameter of routes.MapRoute() function.
Another approach for disambiguation is specifying separate routes for all of your controllers (usually you won't have much of them in your app) before generic route for sponsors.
UPD:
Most straightforward yet least maintainable way to distinguish between sponsor and non-sponsor routes is specifying controller-specific routes, like this:
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.MapRoute(
"HomeRoute",
"Home/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, sponsor=string.Empty }
);
routes.MapRoute(
"AccountRoute",
"Account/{action}/{id}", // URL with parameters
new { controller = "Account", action = "Index", id = UrlParameter.Optional, sponsor=string.Empty }
);
...
routes.MapRoute(
"SponsorRoute",
"{sponsor}/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
...
}
Note that here all controller-specific routes must be added before SponsorRoute.
More complex yet more clean way is implementing RouteConstraints for sponsor and controller names as described in answer from #counsellorben.
In my case, I've resolved this issue using the following two routers:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "MultiCulture",
url: "{culture}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { culture = new CultureConstraint(CultureFactory.All.Select(item => item.UrlPrefix).ToArray()) }
).RouteHandler = new MultiCultureMvcRouteHandler();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
}
}
Where CultureConstraint class looks like below:
public class CultureConstraint : IRouteConstraint
{
private readonly string[] values;
public CultureConstraint(params string[] values)
{
this.values = values;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary routeValues, RouteDirection routeDirection)
{
string value = routeValues[parameterName].ToString();
return this.values.Contains(value);
}
}
And MultiCultureMvcRouteHandler like this:
public class MultiCultureMvcRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
var culture = CultureManager.GetCulture(requestContext.RouteData);
if (culture != null)
{
var cultureInfo = new CultureInfo(culture.Name);
Thread.CurrentThread.CurrentUICulture = cultureInfo;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);
}
return base.GetHttpHandler(requestContext);
}
}
In addition to adding a second route before the default route, as Sergey said in his answer, you also must add a RouteConstraint to the initial route, to enforce that the {sponsor} token is the name of a valid sponsor.
You can use the RouteConstraint in this answer: Asp.Net Custom Routing and custom routing and add category before controller
Remember that you must also enforce a rule that a sponsor name cannot be the same as any of your controller names.
i will show you in simple example you don't have to change in Route.config.cs
only you have to do in Route.config.cs just put in
Optional URI Parameters First and Default Values
Route.config.cs
routes.MapMvcAttributeRoutes();
Controller
[Route("{Name}/Controller/ActionName")]
public ActionResult Details(string Name)
{
// some code here
return View();
}
Results
localhost:2345/Name/controllername/actionname/id(optional)

Routing - Area Controller/View with parameter

Super simple MVC site with an Area to handle mobile devices. All of my Area routing works fine with the exception of a view that expects a parameter.
In the "normal" site I have a view video page that expects a parameter.
mysite.com/Video/123456
This works perfectly. After fighting this for a bit in my Area for the mobile content I have even gone down to using the exact same code/markup in my Controller and View. So I would expect that the following URL:
mysite.com/Mobile/Video/123456
Would resolve properly. It doesn't. I get a 404 (not found). If I take the parameter off:
mysite.com/Mobile/Video
It resolves properly.
I am sure this must be something I am doing wrong in the routing. Below is the appropriate section from my global.asax. Any help would be appreciated.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Video", // Route name
"Video/{id}", // URL with parameters
new { controller = "Video", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "mysite.Controllers.VideoController" }
);
routes.MapRoute(
"NewsItem", // Route name
"NewsItem/{id}", // URL with parameters
new { controller = "NewsItem", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "mysite.Controllers.HomeController" }
);
routes.MapRoute(
"Mobile", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { area = "Mobile", controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "mysite.Areas.Mobile.Controllers.HomeController" }
);
routes.MapRoute(
"Mobile/Video", // Route name
"Mobile/Video/{id}", // URL with parameters
new { area = "Mobile", controller = "Video", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "mysite.Areas.Mobile.Controllers.VideoController" }
);
}
SteveInTN, you cannot have the same registration in both, Global.asax and MobileAreaRegistration.cs.
You only need to have Mobile Registration on MobileAreaRegistration.cs and call AreaRegistration.RegisterAllAreas() in Application_Start before RegisterRoutes(RouteTable.Routes).
If you want url like mysite.com/Mobile/Video/123456:
The mobile route registration should be in the format {controller} / {id}, like video route.
Registration in Global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Video", // Route name
"Video/{id}", // URL with parameters
new { controller = "Video", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "mysite.Controllers.VideoController" }
);
//newsitem route
}
Registration on MobileAreaRegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Mobile_default",
"Mobile/{controller}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
Looks like your Route name should not contain / since it may conflict with routing? When I do routing I make sure the names are unique and use underscores to represent separators like so : text_text. Not sure if this will work, worth a try though.

.NET MVC 3 trying to get RedirectToAction to follow the format of {controller}/{action}/{id}/{GUID}

I have to be missing something obvious here.
I would like to ensure all RedirectToAction follow the format of {controller}/{action}/{id}/{GUID} (e.g. http://www.mysite.com/report/edit/23/0975a566-983a-4414-962c-0ab1a921e89d
Global.asax.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
routes.MapRoute(
"Custom", // Route name
"{controller}/{action}/{id}/{GUID}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, GUID = UrlParameter.Optional} // Parameter defaults
);
}
I am using the following in the controller:
return RedirectToAction("edit", "report", new { id = id, GUID = getGUIDFromId(id) });
However, I just get the following result:
http://www.mysite.com/report/edit/23?0975a566-983a-4414-962c-0ab1a921e89d
I have had a good search on this but I've found nothing about this particular issue (probably because it is obvious).
Many thanks in advance
Just reverse the order of your route definitions:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Custom",
"{controller}/{action}/{id}/{GUID}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional, GUID = UrlParameter.Optional }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Remember that the order in which you define your routes is important as they are evaluated in this same order by the routing engine. So you should always place specific routes before more general ones.

Resources