Routing is finding the controller in my areas, but not the views - asp.net-mvc

I'm trying to use Maarten Balliauw's Domain Route class to map sub-domains to the areas in an MVC2 app so that I have URLs like:
http://admin.mydomain.com/home/index
instead of:
http://mydomain.com/admin/home/index
So far, I've only had partial success. Execution is being routed to the correct controller in the correct area, but it cannot then find the correct view. I'm receiving the following error:
The view 'Index' or its master was not found. The following locations were searched:
~/Views/AdminHome/Index.aspx
~/Views/AdminHome/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
This indicates to me that MVC is looking for the view only in the root views folder and not the views folder within the Area. If I copy the view from the Area's views folder to the root views folder, the page renders fine. This however, completely defeats the purpose of dividing the APP into Areas.
I'm defining the route for the area as:
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "Admin"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.Add(
"Admin_Default"
, new DomainRoute(
"admin.localhost"
, "{controller}/{action}/{id}"
, new { controller = "AdminHome", action = "Index", id = UrlParameter.Optional }
));
}
}
I'm confused as to why it is finding the controller within the Area fine, but not the view.

OK, I figured it out. After downloading the MVC 2 source code and adding it to my solution as outlined here, I stepped through the MVC code. I found that Routes within areas implement the IRouteWithArea interface. This interface adds an 'Area' property to the RouteData which, not surprisingly, contains the area's name. I modified the DomainRoute class so to implement this interface and added a couple of overloaded constructors that took this additional parameter, and it now works exactly as I wanted it to.
The code for registering my route now looks like this:
context.Routes.Add(
"Admin_Default"
, new DomainRoute(
"admin.mydomain"
,"Admin"
, "{controller}/{action}/{id}"
, new { controller = "AdminHome", action = "Index", id = UrlParameter.Optional }
));

If you have share controller names between your areas and your default routes, and it looks like you do, you may need to identify namespaces when you call MapRoute.
For example, if the top-level namespace of your web application is Web, the RegisterRoutes method in Global.asax.cs file would look something like this:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
null,
new string[] { "Web.Controllers" }
);
and then the RegisterArea moethod of AdminAreaRegistration.cs would look something like this:
context.MapRoute(
"Admin_Default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
null,
new string[] { "Web.Areas.Admin.Controllers" }
);

Related

Area routing, why is the area name needed?

I have a project with 2 areas. Its does work but I am a newbie to this and I want to understand why.
I have an Area called LogonArea
context.MapRoute(
"LogonArea_default",
"LogonArea/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
Why is the 'LogonArea/' part needed? Shouldn't it be able to find the controller without it?
When I tried removing it I could still reach controllers with that Area but strangely I couldn't reach other areas while on that page.
If this is really necessary how could I mask it so the Area wasn't visible in the url?
thanks
If you remove /LoginArea/ from the area route registration, it will be able to find your controller (as long as you don't have any conflicting controller names such as HomeController in the main section and HomeController in the area).
It's mainly there for your convenience. If you have an Admin area, everything in your site will be accessible via /Admin/{controller}. It's mostly just an organizational thing.
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
I created a single controller called FooController in this project, and I was able to go to the url /Foo to reach it without needing to go to /Admin/Foo
When you create a link to a controller outside of the area you need to specify which area it's in (or specify that there is no area):
#Html.ActionLink("Go Home", "Index", "Home", new { area = "" }, null)

ASP.NET MVC4 Searching for controller in wrong area

I'm using default MVC routing setup:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have area defined as:
public class AdministrationAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Administration";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Administration_default",
"Administration/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
And I have a controller in that area:
namespace XXX.Areas.Administration.Controllers
{
public class CountryController : Controller
{
public ActionResult Index()
{
///
}
}
}
When I type
/Administration/Country
it works good as it is desired.
When I type
/Country
action still gets invoked, though view is not found so I get an error.
Why is MVC accepting
/Country
as valid route? I don't have another CountryController in non-area zone.
Add NameSpace in the Global.asax file for the default Route.
var route = routes.MapRoute(
"Default", // Route name
"{controller}/{action}", // URL with parameters
new { controller = "Home", action = "Index" }, // Parameter defaults,
new[] { "YourNameSpace.Controllers" }
);
Add NameSpace in the AreaRegistration class present in your Area
public class MyArea : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"test",
"Test/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "MyProjectNameSpace.Controllers" }
);
}
}
Explanation
I have a following area in my application. So the below highlighted section is out concerned Controller. ok.
Figure -1
I typed Url : http://localhost:2474/ActionFilterAttribute/index
Before moving toward the destination. I will show you some how I initialized my test. I added a Reference of RoureDebugger. You can get the Dll from this location. Then I added a line of code in my Global.asax file under Application_Start Handler.
RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
So, finally using the above mentioned Url, I started debugging the application. Finally I saw the below picture.
Figure -2
Question
action still gets invoked, though view is not found so I get an error.
Answer
So if you pay attention to the above highlighted Route, well, this is the Default Route. This pattern is matched with Url as mentioned above. But View will not be found in this case and that's the reason your Controller Action Method is Invoked.
Before moving to the next part that why did I get 404. I will show you some test I did in my sample application.
I created a class which derives from ActionFilterAttribute like below. This contains only one Override that's called OnResultExecuting. This Handler executes before executing the View corresponding to particular Action
The purpose of Creating this class is just to verify what is happening with RouteData and DataTokens.
public class MyActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult != null)
{
var razorEngine = viewResult
.ViewEngineCollection
.OfType<RazorViewEngine>()
.Single();
var viewName = !String.IsNullOrEmpty(viewResult.ViewName) ?
viewResult.ViewName :
filterContext.RouteData.Values["action"].ToString();
var razorview = razorengine
.FindView
(
filtercontext.Controller.ControllerContext,
viewname,
viewResult.MasterName,
false
).View as RazorView;
}
base.OnResultExecuting(filterContext);
}
}
Ok. Let's come back to the original question. Why did I get 404?
Your Controller will be picked up by the default base route {controller}/{action} before it checks the Area Route and therefore will look for the View in the Root/Views instead of in the Area/views.
To examine this, I set the debugger in the Action Method of Controller inside the Area and found that that there is no DataToken Information when the Requested url is without Area Name. let's see more details about DataToken in Debug Mode
Figure -3
If you pay attention to the ControllerContext, I enumerated the DataTokens, which is showing no key/Value. It's because no view is found pertaining to that controller under the Root Directory
How can you say that the currently located Directory is Root Directory? Proof is below
Figure -4
There is no Namespace or any Area mentioned in the RouteData values. right?
Now let's move to the RouteData that matched the pattern which is containing the Area Name. So, this time my Url is : http://localhost:2474/mypractise/ActionFilterAttribute/index and below is the RouteData matched by URLRoutingModule
Figure -5
Please pay attention to the highlighted section, this time the Route matched belongs to AreaName pattern and matched value is false for the Default Route which belongs to some RouteData at Root Directory. Right?
My final details for the DataTokens in case of above mentioned requested Url. You can see the Namespace details and Area details this time.
Figure -6
Conclusion :
When the Controller is inside the Area and your DataTokens are not showing the information for Area , NameSpace and UseNameSpaceFallback informations. That means you will get 404. As mentioned in Figure-4, your requested Url was correct, so you got the DataTokens and As mentioned in Figure 3, DataTokens were not shown because the requested Url does not contains the Area Name and despite of the fact that you have got the RouteData as mentioned in Figure 2 because it's a default Url Pattern. Finally try to execute the third line of code in OnResultExecuting. It will show null because View is not found.
Hope this explanation will help you.
Check it. Modify the default route in your Global.ascx.cs file like so.
var route = routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "APPLICATION_NAMESPACE.Controllers.*" }
);
route.DataTokens["UseNamespaceFallback"] = false;
EDIT:
My apologies. It seemed like you didn't want it to do this as well as know why.
You are running into the fact that the default routing will look for anything that is a controller. Even if it's in an Area. You can overcome this default behavior by simply adding the namespaces parameter to the route and specify what the default routing should be looking for with controllers.
The solution that I provided above is merely a fix if you wanted to not serve the view of an area outside the area itself.
There is a great article on why this is occurring here.

MVC4 routing 2 areas containing controllers with the same name

I have 2 areas in an mvc4 application and I have registered the namespace for each of the areas.
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Intergration_default",
"Intergration/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
constraints: null,
namespaces: new[] { "WebApplication.Areas.Intergration.Controllers" }
);
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Vend_default",
"Vend/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional},
constraints: null,
namespaces: new[] { "WebApplication.Areas.MyController.Controllers" }
);
I can access Intergration/MyController however when I try accessing MyController I get an error
Multiple types were found that match the controller named 'mycontroller'. This can happen if the route that services this request ('{controller}/{action}/{id}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
What am I doing wrong? do I need to do something extra in the global.asax
Based on the code / description you provided, it sounds like it could be a couple of things:
You have a controller name collision in the root controllers namespace (i.e., in the Controllers folder in the root of the project, not in an area) with another area with no constraint.
More likely, your second area registration for Vend has what looks like an incorrect namespace. Instead of WebApplication.Areas.MyController.Controllers it should be WebApplication.Areas.Vend.Controllers. I bet that there's a controller in your root controllers namespace that shares a controller name with something in the Vend area.

ASP.NET MVC3 Area controller accessible from global routes?

Perhaps I do not understand correctly how MVC Areas work, but this has got me a little confused.
Add an Area called "MyArea" using right-click "Add Area" in Visual Studio on the MVC3 project
Create a controller for MyArea: "AnArea" with matching view in the MyArea area.
Add "controller = "AnArea" to context.MapRoute's defaults parameter in MyAreaAreaRegistration.RegisterArea method.
So at this point if you start the application and navigate to /MyArea/ it should load the AnArea controller with it's matching view. If you navigate to /MyArea/AnArea, it will show the same result.
But, if you navigate to /AnArea/, the controller is still found and the following error message is displayed:
The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/anarea/Index.aspx
~/Views/anarea/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/anarea/Index.cshtml
~/Views/anarea/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml
Is this the correct behaviour? I would have thought an area's controller could only be accessed via it's own area and not globally.
Whenever I create an project with areas, I change my Default route as follows:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // defaults
null, // constraints
new string[] { "MyApplication.Controllers" } // namespaces
);
The final parameter limits the default route to the controllers in the MyApplication.Controllers namespace. This insures that the Default route is limited to actions outside of any areas.
UPDATE
After a deep dive into the code, I discovered where the issue arises, and have a solution. Change your Default route to the following:
routes.Add(
"Default",
new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
),
null,
new RouteValueDictionary(
new {
Namespaces = new string[] { "MyApplication.Controllers" },
UseNamespaceFallback = false
}
),
new MvcRouteHandler()
)
);
The key is in adding the UseNamespaceFallback token. This will prevent the Default route from looking into any other namespaces.
This is unexpected behavior, and it was a problem I was unaware of which affects a project I am working on. I will list it as an issue at aspnet.codeplex.com. I would not call this a bug, but the behavior definitely appears to breach the convetions for MVC routing.
You have to apply a namespace restriction in both area and general route.
In global.asax.cs you should edit RegisterRoutes method just like this
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 },
new string[] { "MyProject.Controllers" }
);
}
That will restrict "//" only to the namespace "MyProject.Controllers"
But also you´ll have to apply the namespace restriction to the Area to restrict "//" only to the namespace "MyProject.Areas.MyArea.Controllers"
For that you´ll have to edit "RegisterArea" method of "MyAreaAreaRegistration.cs" like below ("MyAreaRegistration.cs" is located at "/MyProject/Areas/MyArea" folder ) :
//Some default code stuff
...
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"MyArea_default",
"MyArea/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new string[] { "MyProject.Areas.MyArea.Controllers" }
);
}
Hope it helps!!
You seem to be navigating to /AnArea whereas your area is called MyArea so you should navigate to /MyArea/. Here's how the area route registration looks like:
context.MapRoute(
"MyArea_default",
"MyArea/{controller}/{action}/{id}",
new { controller = "AnArea", action = "Index", id = UrlParameter.Optional }
);
AnArea is the name of the controller, not the area. If you want to navigate to some controller of this area you should always prefix your url with MyArea which is the name of the area.

MVC Areas - View not found

I have a project that is using MVC areas. The area has the entire project in it while the main "Views/Controllers/Models" folders outside the Areas are empty barring a dispatch controller I have setup that routes default incoming requests to the Home Controller in my area.
This controller has one method as follows:-
public ActionResult Index(string id)
{
return RedirectToAction("Index", "Home", new {area = "xyz"});
}
I also have a default route setup to use this controller as follows:-
routes.MapRoute(
"Default", // Default route
"{controller}/{action}/{id}",
new { controller = "Dispatch", action = "Index", id = UrlParameter.Optional }
);
Any default requests to my site are appropriately routed to the relevant area. The Area's "RegisterArea" method has a single route:-
context.MapRoute(
"xyz_default",
"xyz/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
My area has multiple controllers with a lot of views. Any call to a specific view in these controller methods like "return View("blah");
renders the correct view. However whenever I try and return a view along with a model object passed in as a parameter I get the
following error:-
Server Error in '/DeveloperPortal' Application.
The view 'blah' or its master was not found. The following locations were searched:
~/Views/Profile/blah.aspx
~/Views/Profile/blah.ascx
~/Views/Shared/blah.aspx
~/Views/Shared/blah.ascx
It looks like whenever a model object is passed in as a param. to the "View()" method [e.g. return View("blah",obj) ] it searches for the view
in the root of the project instead of in the area specific view folder.
What am I missing here ?
Thanks in advance.
Solved ! A couple of my "RedirectToAction" calls were not specifying the area name explicitly in the routeobject collection parameter of that method. Weird though, that that is required even though the controllers Redirecting are all in the same area. Also, the HtmlActionLinks work fine when I don't specify the new {area="blah"} in its routeobject collection, so I wonder why the controller action calls to RedirectToAction() need that even though both the calling and the called controller actions are all within the same area.
If you use instead of
context.MapRoute(
"xyz_default",
"xyz/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
use
context.MapRoute(
"xyz_default",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
in your
xyzAreaRegistration.cs
then you don't need to explicitly specify your area in any link...
Add the RouteArea attribute on the Controller class so MVC knows to use the "XYZ" Area for the views (and then you can set the AreaPrefix to empty string so routes do not need to start with "XYZ").
[RouteArea("Xyz", AreaPrefix = "")]
public class XyzController : Controller
{
...
}
If this is a routing problem, you can fix it by registering your area routes first. This causes the routing engine to try matching one of the area routes, before matching a root route:
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
If I force an error by renaming one of my views folders in my areas application, I get a different error than yours:
The view 'Index' or its master was not found. The following locations
were searched:
~/Areas/xyz/Views/Document/Index.aspx
~/Areas/xyz/Views/Document/Index.ascx
~/Areas/xyz/Views/Shared/Index.aspx
~/Areas/xyz/Views/Shared/Index.ascx
...and then the usual root view folders..
..which is the pattern of subdirectories it would search if it thought it was in an area.
Check the generated code at MyAreaAreaRegistration.cs and make sure that the controller parameter is set to your default controller, otherwise the controller will be called bot for some reason ASP.NET MVC won't search for the views at the area folder
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"SomeArea_default",
"SomeArea/{controller}/{action}/{id}",
new { controller = "SomeController", action = "Index", id = UrlParameter.Optional }
);
}
For those who are looking for .net core solution please use
app.UseMvc(routes =>
{
routes.MapRoute(
name : "areas",
template : "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
});`
If you have some code in main project and some code in areas use the following code.
app.UseMvc(routes => {
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Note make sure you have areas defined in your controller
[Area("Test")]
public class TestController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
I just had the same problem and solved it by setting the ascx's 'Build Action' property to 'Embedded Resource'.
Try this code. Do changes in Area Registration File...
context.MapRoute(
"YourRouteName", // Route name //
"MyAreaName/MyController/{action}", // URL with parameters //
new {
controller = "MyControllerName",
action = "MyActionName", meetId = UrlParameter.Optional
}, // Parameter defaults
new[] { "Your Namespace name" }
);

Resources