MVC 4.0 Routing "Area" Prefix - asp.net-mvc

I Guess my question is very stuppied, but i could not find any awenser or link that worked fine, so.
The point is, I want my url to reflect my folder strcuture. Witch is:
~/Controllers/Admin/
~/Controllers/Supply/
My routeconfig look like this:
routes.MapRoute(
name: "Admin",
url: "Admin/{controller}/{action}",
namespaces: new [] {"Web_Intranet.Controllers.Admin"}
);
routes.MapRoute(
name: "Supply",
url: "Supply/{controller}/{action}",
namespaces: new [] {"Web_Intranet.Controllers.Supply"}
);
The problem is that if you type on the brower url the following:
localhost:4342/admin/users/showall
OR
localhost:4342/supply/users/showall
Both will work! I assume that the routing checks wheter you pass a valid "controller" and "action" and chooses the first one that matches the condition.
How can I create a valid routing config that will not work if the corresponding prefix doesn´t match?

Here's the configuration you should add for each Area;
~/Areas/Admin/AdminAreaRegistration.cs
using System.Web.Mvc;
namespace MyApp.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
Make sure that Route_Name is unique

Related

MVC routes with static Prefix

I have two routes in my routeConfig files as follow.
Route with admin prefix which handles request for admin part
default Route without a prefix, for which I have added a datatoken to map routes in candidate Area
routes.MapRoute(
name: "admin",
url: "Admin/{controller}/{action}/{id}",
defaults: new { controller = "Account", action = "Login", id = UrlParameter.Optional },
namespaces: new[] { "abc.namespace1" }
);
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Account", action = "Login", id = UrlParameter.Optional },
namespaces: new[] { "abc.namespace2" }
).DataTokens.Add("area", "Candidate");
But the problem is when i type in a url localhost/MyApp/Admin/Home/Index
it is hitting the controller in abc.namespace1 (which is expected) and localhost/MyApp/Home/Index also hitting Home controller inside abc.namespace1 instead of HomeController inside abc.namespace2 in candidate Area.
What i want to do here is handle all routes with Admin prefix with controllers inside abc.namespace1 and all routes without any prefix with controllers inside abc.namespace2 which is my candiate Area.
regards
I believe this might have something to do with the way you have specified your namespaces. The namespace must be for where the controller classes reside.
The pattern is typically <namespace of area>.<area name>.<controller namespace>
For example, in a project with an area named "Admin", the namespace must be:
"MvcMusicStore.Areas.Admin.Controllers"
In my experience, the conventions for how areas are set up are pretty strict. You should not set up the route in an AreaRegistration rather than the root of your project in order to get it to work.
public class CandidateAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Candidate";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Candidate_default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Login", id = UrlParameter.Optional },
new string[] { "<project name>.Areas.Candidate.Controllers" }
);
}
}
Areas are convention-based. If you deviate too far from the anticipated conventions, they simply don't function.

#Html.ActionLink is not linking to current area by default

I have 2 areas Admin and FrontEnd (in that order).
When I am in a view in my FrontEnd area, ActionLink always points to the Admin area:
#Html.ActionLink("Checkout", "Address", "Checkout")
Will be http://localhost:53600/admin/Checkout/Address but the Checkout controller is in my FrontEnd area.
I know I can solve this by specifying a routedata object in the action link and setting area = "FrontEnd" but I don't want to. I want the ActionLink helper to default to my current route.
Is this possible?
All the questions I've read on actionlink are people asking how to link to another area which indicates it defaults to the current area for them. Am I alone with this issue?
Edit, these are my routes which you can see are tied to the correct namespace:
Admin
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Administration_default",
"admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", area = "Administration", id = UrlParameter.Optional },
new[] { "CC.Web.Areas.Administration.Controllers" }
);
}
FrontEnd
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new {controller = "Home", action = "Index", area = "FrontEnd", id = UrlParameter.Optional},
new[] {"CC.Web.Areas.FrontEnd.Controllers"}
);
}
Areas should be registered in classes deriving from AreaRegistration overriding the method void RegisterArea(AreaRegistrationContext context), see the msdn.
The AreaRegistrationContext defines its own methods to register area routes that will add the required dataTokens for the areas that are used when generating links and urls:
public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
{
if (namespaces == null && Namespaces != null)
{
namespaces = Namespaces.ToArray();
}
Route route = Routes.MapRoute(name, url, defaults, constraints, namespaces);
route.DataTokens[RouteDataTokenKeys.Area] = AreaName;
// disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
// controllers belonging to other areas
bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
route.DataTokens[RouteDataTokenKeys.UseNamespaceFallback] = useNamespaceFallback;
return route;
}
It also looks like the FrontEnd shouldn't be an area, so you could just have the standard MVC controllers and views (instead of the fronteand area) with an extra Admin area:
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Administration";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Administration_default",
"admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "CC.Web.Areas.Administration.Controllers" }
);
}
}
Remember that you should be calling AreaRegistration.RegisterAllAreas(); at the beggining of your main RegisterRoutes method invoked on app start.

ASP.NET MVC routing parameters not working with areas

I'm currently playing around with areas and routing within them. What I'm trying to achieve is to be able to have a URL that looks like this;
PracticeAdmin/Practice/[Practice Name]
which I would then be able to add things like Edit and Delete to the end of.
I have achieved this in the past when not working with areas by adding this annotation to the action
[Route("PracticeAdmin/Practices/{practiceName}")]
public ActionResult Details(string practiceName)
this would produce the URLs that I would like. The problem I am having is that when I am trying to do this when using areas I get links that look like this;
PracticeAdmin/Practices?practiceName=Practice1
which is not what I am looking for.
The code that I am using to try and produce this with is
PracticeAdminAreaRegistration.cs
using System.Web.Mvc;
namespace TrainingPortal.Areas.PracticeAdmin
{
public class PracticeAdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "PracticeAdmin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"PracticeAdmin_default",
"PracticeAdmin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "TrainingPortal.Areas.PracticeAdmin.Controllers" }
);
}
}
}
RouteConfig.cs
namespace TrainingPortal
{
public class RouteConfig
{
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 },
namespaces: new[] { "TrainingPortal.Controllers" }
);
}
}
}
I have called MapMvcAttributeRoutes here which I believe should mean that the routes are registered even within areas. I have also tried putting the necessary code within PracticeAdminAreaRegistration to do the same thing with no effect.
PracticeAdminController.cs
namespace TrainingPortal.Areas.PracticeAdmin.Controllers
{
public partial class PracticesController : Controller
{
private TpContext db = new TpContext();
// GET: PracticeAdmin/Practices
public virtual ActionResult Index()
{
return View(db.Practices.ToList());
}
[Route("PracticeAdmin/Practice/{practiceName}")]
public virtual ActionResult Details(string practiceName)
{
if (string.IsNullOrWhiteSpace(practiceName))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Practice practice = db.Practices.FirstOrDefault(m => m.PracticeName.ToLower() == practiceName);
if (practice == null)
{
return HttpNotFound();
}
return View(practice);
}
...
Obviously it carries on with other methods but they all follow the same approach as this one.
Index.cshtml snippet
#Html.ActionLink("Delete", MVC.PracticeAdmin.Practices.Delete(item.PracticeName))
#Html.ActionLink("Delete2", "Delete", new { practiceName = item.PracticeName })
Within PracticeAdminArea/Views/Practices/Index.cshtml I have tried using both T4MVC and the normal ActionLink approach which generate exactly the same link (unsurprisingly).
Summary
I have no idea why the Routes I have specified don't appear when trying to create an ActionLink in an area, so I was wondering whether anyone is able to point me in the direction of how I would be able to fix this and get the URL to look how I would like it to?
After a bit of playing around I managed to get it working. The way that I ended up fixing it was by calling AreaRegistration.RegisterAllAreas() from the RouteConfig.RegisterRoutes() after having called MapMvcAttributeRoutes()
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "TrainingPortal.Controllers" }
);
}
}
As you're not allowed to call this method twice (or ASP.NET gets rather upset with you having registered the same route names twice) I removed the call to AreaRegistration.RegisterAllAreas() from Global.asax leaving it looking like this;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Unfortunately this alone didn't solve the problem, I also had to make a couple modifications to the Controller as well. The modifications were to add the RoutePrefix and RouteArea attributes to the Controller like this;
[RouteArea("PracticeAdmin")]
[RoutePrefix("Practice")]
public partial class PracticesController : Controller
{
This had the added benefit that when specifying the route for a particular action through the Route attribute you didn't have to specify those parts any more, so originally an action's signature would have looked like this;
// GET: PracticeAdmin/Practices/{practiceName}/Members
[Route("PracticeAdmin/Practices/{practiceName}/Members")]
public virtual ActionResult Members(string practiceName)
{
it would now look like this;
// GET: PracticeAdmin/Practices/{practiceName}/Members
[Route("{practiceName}/Members")]
public virtual ActionResult Members(string practiceName)
{
After making all those changes, the website is behaving as expected.

ASP MVC Rounting, Remove /Areas/ Prefix from uri

By default Areas have a /Area/ routing prefix. For instance a Blog area would be :
/Areas/Blog/Blog/Show/myId
or
/Areas/{area}/{controller}/{action}/{id}
The result is a really ugly and redundent uri for the web application. What I would like is something along the lines of :
/Blog/Show/myId
or
/{area / controller}/{action}/{id}
How can I achieve this ?
The reason I am using areas is because my 'blog' area is not a standard MVC application, but, a single-page application. It has a different project structure than the rest of the app, and so, I would like to just partition it into its own area.
Edit : source for Area Registration
public class BlogAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Blog";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Blog_default",
"Blog/{action}/{id}",
new { controller ="blog", action = "Index", id = UrlParameter.Optional }
);
}
}
Edit Source for Routeing
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Edit
After speaking /u/Eric Philips I have come up with a solution. The solution was rework my file structure to MVC compatible. Once I setup a controller and moved my index.cshtml page into a valid location, /Blog/ worked fine.
public class BlogController : Controller
{
// GET: Blog/Blog
public ActionResult Index()
{
return View();
}
}

MVC Routing and Areas - how to construct a route to match action on default controller in area?

I've got a simple MVC4 web site containing an area (called "User") containing a Controller called "HomeController".
On this controller there are two action methods: Index and Details:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Details(int id)
{
return View();
}
}
The "UserAreaRegistration.cs" class is as follows:
public class UserAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "User";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"User_default",
"User/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
context.MapRoute(
"User_default_no_contoller",
"User/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
}
}
There's also a Controller that's not in an area called "HomeController":
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
RouteConfig.cs (for non-area routes) is as follows:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Controllers" }
);
}
}
I am able to access the "homepage" via either
/
/Home
/Home/Index
I'm also able to get to the "Index" action in my "User" area
/User
What I cannot do is work out how to get to the other method on my Controller in the Area called "Details":
/User/Details/5 does not work, it returns a 404
I also notice that trying to access the Index method explicitly is also not routed correctly:
/User/Index does not work, it returns a 404
What am I doing wrong?
Is it possible to get to any action method other than "Index" on a controller that's the "default" controller in an area?
There are too many defaults on your first route, so it's swallowing everything up.
the URI /User/Details/5 matches the User_default route (Controller: "Details" and Action: "5").
the URI /User/Index also matches the first route (Controller: "Index" and the default Action: "Index")
The way to approach routes is
put the most selective route first
and set only the minimum default values necessary
use route constraints to ensure that the MVC router doesn't attempt to match parameters incorrectly (ie, give route values intended for int parameters a numeric constraint)
put the most general route (ie, User_default) last (as a catch-all for URIs that didn't match any more specific route)
I'd try setting up the area routes like this.
context.MapRoute(
"User_default_no_contoller",
"User/{action}/{id}",
defaults: new { controller = "Home", id = UrlParameter.Optional },
constraints: new { id = #"\d+" },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
context.MapRoute(
"User_default",
"User/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
Another suggestion to overcome this "Multi-defaults" problem is to separate using Namespaces.
In your example my suggestion is to have the Class UserAreaRegistration in the namespace "Areas.User" . Subsequently you would have to put your HomeController in the User Area in the Namespace "Areas.User.Controllers"
It works for me and keeps the code clean.
Peter
Thanks for the answer dbaseman. It helped me look at my own routing problem. I am not sure if I was running into the same issue but that is what it seems like.
routes.MapRoute("Tag", "Tag/{tag}", new { controller = "Blog", action = "Tag" });
routes.MapRoute("Category", "Category/{category}", new { controller = "Blog", action = "Category" });
routes.MapRoute("Post", "Archive/{year}/{month}/{day}/{title}", new {controller = "Blog", action = "Post"});
routes.MapRoute("Action", "{action}", new { controller = "Blog", action = "Posts" });
When I had the last route with the "Action" parameters above any of the other routes the ones below "Action" would not be caught. Moving it to the bottom of the RouteConfig allowed all the ones above it to be hit successfully now.

Resources