#Html.ActionLink is not linking to current area by default - asp.net-mvc

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.

Related

Incorrect URL after changing routing in Area

I am having trouble with rendering URL addresses after changing routing.
Routing Configuration
public class AccountArea : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Account",
"Account/{controller}/{action}/{id}",
new { controller = "User", action = "Index", id = UrlParameter.Optional },
namespaces: new[] {
"Application.Controllers.Account"
}
);
}
public override string AreaName => "AccountArea";
}
public class FrontArea : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Front",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] {
"Application.Controllers.Front"
}
);
}
public override string AreaName => "FrontArea";
}
and RouteConfig:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
}
Controllers
// on folder Controllers/Front/
namespace Application.Controllers.Front
class HomeController : Controller
{
ActionResult Index(){ ... }
}
// on folder Controllers/Account/
Application.Controllers.Account
class UserController : Controller
{
ActionResult Index(){...}
}
I have changed routing because I want to separate the controllers into 2 subfolders, Front and Account.
This configuration allows me to divide the application into 2 parts, differentiated by using URL address.
localhost/Home/Index [namespace: Application.Controllers.Front]
localhost/Account/User/Index- [namespace: Application.Controllers.Account]
after entering the address to the browser
localhost/Home/Index - will start method Index from HomeController
localhost/Account/User/Index - will start method Index from UserController
A problem appears when on URL address localhost/Home/Index and I try rendering URL address belonging to localhost/Account/User/Index.
When I use #Url.Action("Index","User") address will be rendered, but without the "Account" prefix in the URL.
I receive: /User/Index
But, I expect: /Account/User/Index
And vice versa, on localhost/Account/User/Index I can't render correct URL address belonging to localhost/Home/Index.
This problem I can solve using Url.RouteUrl like
#Url.RouteUrl("Account", new {controller = "User", account = "Index"} )
but, this requires route name, which I don't want to provide.
When using Area routes, all of the UrlHelper URL resolution methods (such as ActionLink or Url.Action) will use the same area by default.
Linking from Front Area to Front Area
#Url.Action("Index", "User")
To specify to go to a different area, you must explicitly specify the area in the route values that are passed in to Url.Action.
Linking from Front Area to Account Area
#Url.Action("Index", "User", new { area = "Account" })
The same goes for links generated inside of the Account area. You will need to explicitly override the current area name with Front.
Linking from Account Area to Front Area
#Url.Action("Index", "User", new { area = "Front" })
this requires route name, which I don't want to provide.
Option 1 - Use Constants for Areas
Make a set of constants that you use for all of your area strings in the application.
public static class Areas
{
public const string Front = "Front";
public const string Account = "Account";
}
And use like
#Url.Action("Index", "User", new { area = Areas.Front })
Option 2 - Use T4MVC
There is a package called T4MVC that generates constants for your project so you can replace routing and URL generation without using magic strings.
#Url.Action(MVC.Areas.Front.User.Index)
Option 3 - Use Extension Methods
Another option would be to build your own extension methods to replace Url.Action, Html.ActionLink, RedirectToAction, etc.
#Url.ActionToFront("Index", "User")
This is a lot of action methods to create, though.

I added an area called home, moved my controller and view to it and now I can't connect to the index view

I already have controllers and views in folders under the project name. I added an Area folder and then an area inside it and called it Home and then moved my controller and index view into it. But when I connect to the index I get an error and it looks like the path where it's looking for the index is the old path, how do I change this to the new path?
Here is what I created
In 'HomeAreaRegistration' I see this under RegstrationArea
public class HomeAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Home";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Home_default",
"Home/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
But when I run the application in IE, here is what I see in the browser! It looks like it's looking for the index.cshtml in the old path location, not the new path location in the new area 'Home'
It looks like the route engine is looking in the wrong location. So here is what my RouteConfig.cs file looks like.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Even when I try 'https://localhost:44301/Home/Index.cshtml' it throws a HTTP 404 error.
The 404 error shows the main problem itself: default routing and view engine search cannot found default Index.cshtml view file in Views directory on your project (i.e. ProjectName/Views/Index.cshtml pointed by route ~/Views/Home/Index).
First, create a class to include view location search for your custom area like this example:
public class CustomView : RazorViewEngine
{
public CustomView()
{
MasterLocationFormats: new[]
{
"~/Areas/Home/Views/{0}.cshtml",
"~/Areas/Home/Views/{1}/{0}.cshtml"
}
ViewLocationFormats: new[]
{
"~/Areas/Home/Views/{0}.cshtml",
"~/Areas/Home/Views/{1}/{0}.cshtml"
}
PartialViewLocationFormats = ViewLocationFormats;
FileExtensions = new[]
{
"cshtml"
};
}
}
Then, include all areas and your custom view engine into Global.asax:
protected void Application_Start()
{
// register all area locations
AreaRegistration.RegisterAllAreas();
// clear default view engine
ViewEngines.Engines.Clear();
// add your custom view engine here
// the custom view engine should loaded before default view engine (e.g. Razor)
ViewEngines.Engines.Add(new CustomView());
ViewEngines.Engines.Add(new RazorViewEngine());
}
If you have RouteConfig class on App_Start directory, make sure RegisterAllAreas has included before default route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Additionally, add namespace of the controller name when required or the above solution still doesn't work:
public class HomeAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Home";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Home_default",
"Home/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "ProjectName.Areas.Home.Controllers" }
);
}
}
NB: Create Home directory under Views if you want to follow route convension ~/Areas/Views/Home/Index, and put Index.cshtml file into it.
References:
How to set a Default Route (To an Area) in MVC
How to register areas for routing
your area folder structure look like this
Reister your area in Global.asax
AreaRegistration.RegisterAllAreas();
and try with this url
http://localhost:44301/Home/Home/Index
you have to do below corrections in your solution:
1) Add a Folder Home in Views and place index.cshtml in it.
Folder structure for view must be: Home(Area name) > Views > Home (same name as controller) > index.cshtml (as shown in picture)
2)change namespace of your Homecontroller to (Solution name).Areas.Home.Controllers
3)Also you have to refer following route pattern for area:
localhost/AreaName/Controller/Action
which in your case:
https://localhost:44301/Home/Home/Index
Hope this might solve your problem

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.

ASP.NET MVC `Html.ActionLink` between "Areas"

I have added a new Area to my MVC3 project and I am trying to link from the _Layout page to the new Area. I have added an Area called 'Admin' that has a controller 'Meets'.
I used the visual studio designer to add the area so it has the correct area registration class etc, and the global.asax file is registering all areas.
However, when I use the following 2 action links in a page in the root, I run into a few problems:
#Html.ActionLink("Admin", "Index", "Meets", new { area = "Admin" }, null)
#Html.ActionLink("Admin", "Index", "Meets", new { area = "" }, null)
When clicking both links, I am taken to the Meets controller in the Admin area, where the application then proceeds to throw an error saying it cannot find the Index page (even though the Index page is present in the Views folder in the Area sub-directory.
The href for the 1st link looks like this:
http://localhost/BCC/Meets?area=Admin
And the href for the 2nd link looks like this:
http://localhost/BCC/Meets
Also if I hit the link that I expect to be created:
http://localhost/BCC/Admin/Meets
I just get a resource cannot be found error. All very perplexing! I hope someone can help...
Strange indeed. Steps that worked perfectly fine for me:
Create a new ASP.NET MVC 3 application using the default Visual Studio template
Add an area called Admin using Visual Studio designer by right clicking on the project
Add new Controller in ~/Areas/Admin/Controllers/MeetsController:
public class MeetsController : Controller
{
public ActionResult Index()
{
return View();
}
}
Add a corresponding view ~/Areas/Admin/Views/Meets/Index.cshtml
In the layout (~/Views/Shared/_Layout.cshtml) add links:
#Html.ActionLink("Admin", "Index", "Meets", new { area = "Admin" }, null)
#Html.ActionLink("Admin", "Index", "Meets", new { area = "" }, null)
Run the application.
Rendered HTML for the anchors:
Admin
Admin
As expected the first link works whereas the second doesn't.
So what's the difference with your setup?
Another option is to utilize RouteLink() instead of ActionLink(), which bypasses the area registrations altogether:
ActionLink version:
Html.ActionLink("Log Off", "LogOff", "Account", new { area = "" }, null)
RouteLink version:
Html.RouteLink("Log Off", "Default",
new { action = "LogOff", controller = "Account" })
The second parameter is a "Route Name" which is registered in Global.asax.cs and in various 'AreaRegistration' subclasses. To use 'RouteLink' to link between different areas, you only need to specify the correct route name.
This following example shows how I would generate three links to different areas from a shared partial, which works correctly regardless of which area I am 'in' (if any):
#Html.RouteLink("Blog", "Blog_default",
new { action = "Index", controller = "Article" })
<br/>
#Html.RouteLink("Downloads", "Download_default",
new { action = "Index", controller = "Download" })
<br/>
#Html.RouteLink("About", "Default",
new { action = "Index", controller = "About" })
Happy coding!
I figured this out - I created a new test project and did exactly the same thing I was doing before and it worked...then after further inspection of all things route-related between the two projects I found a discrepancy.
In the global.asax file in my BCC application, there was a rogue line of code which had inexplicably appeared:
public static void RegisterRoutes(RouteCollection routes)
{
// Problem here
routes.Clear();
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
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
As you can see where my comment is, at some time or other I had placed the routes.Clear() call at the beginning of RegisterRoutes, which meant after I had registered the Areas in Application_Start, I was then immediately clearing what I had just registered.
Thanks for the help...it did ultimately lead to my salvation!
Verify that your AdminAreaRegistration class looks like this:
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 { action = "Index", id = UrlParameter.Optional }
);
}
}
and that you have this in Global.asax.cs:
protected void Application_Start()
{
... // ViewEngine Registration
AreaRegistration.RegisterAllAreas();
... // Other route registration
}
I solved this problem by doing the following.
In my Global.asax.cs, I have
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
}
protected void Application_Start()
{
//Initialise IoC
IoC.Initialise();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
In my PublicAreaRegistration.cs (Public Area), I've got
public class PublicAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Public";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute("Root", "", new { controller = "Home", action = "Index" });
context.MapRoute(
"Public_default",
"Public/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
, new[] { "<Project Namespace here>.Areas.Public.Controllers" }
);
}
}
In my AuthAreaRegistration.cs (Area for Restricted access), I've got
public class AuthAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Auth";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Auth_default",
"Auth/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And finally, my links in my *.cshtml pages would be like
1) #Html.ActionLink("Log Off", "LogOff", new{area= "Public", controller="Home"})
or
2) #Html.ActionLink("Admin Area", "Index", new {area= "Auth", controller="Home"})
Hope this saves someone hours of research! BTW, I'm talking about MVC3 here.
Kwex.
This might not be the case for most of the developers but I encountered this problem when I added a my first area and did not build my solution. As soon as I build my solution the links started to populate correctly.

Asp.Net MVC Ninject and Areas

I have a site that uses Ninject for dependency injection and I have Routing defined within a Bootstrapper class like so:
public void RegisterRoutes()
{
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
Routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
Routes.MapRoute(
null,
"{pageTitle}",
new { controller = "Home", action = "Page", pageTitle = "Index" }
);
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
I have added an Area to the project but the default AdminAreaRegistration is not registering the root
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Where or how do I register the Area with Ninject?
The sample project comming with the source code has now an area. Take a look at it. https://github.com/ninject/ninject.web.mvc/zipball/master
are you calling RegisterAllAreas()?
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
note it must be called before RegisterRoutes().
Have you resolved this issue?
I have the problem where my NinjectControllerFactory is not resolving urls that reference controllers defined in areas. I get the following message:
The IControllerFactory 'myWebSite.WebUI.Infrastructure.NinjectControllerFactory' did not return a controller for the name 'admin'.
If I move the controller to the root Controllers folder, it will resolve the url.

Resources