MVC 5 - Default Area Route - Id parameter mistaken by Action name - asp.net-mvc

I've read all the questions in Stackoverflow that I could find about this subject that were not so old, and till now I haven't find a solution to my simple case.
I just created 2 controllers in an Area, and I am using the default auto generated area routes.
When I try to access by:
http://localhost:57969/FieldProduction/CustomerProduction/1
I have an exception and with the help of Glimpse information I see that the "1" is been interpreted as the action and not as a parameter....
I When I try to access one of the area controllers with:
http://localhost:57969/FieldProduction/CustomerProduction/?1
The default action "Index" is used, but the contructor variable int? Id is not beeing filled.
Action:
public async Task<ActionResult> Index(int? id)
Default Area Controller Route:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"FieldProduction_default",
"FieldProduction/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
How can I fix this?

I am leaving this question open in order for possible other solutions, but at the moment I was able to solve my problem by writing a custom route in the areaRegistration
//**NEW ROUTE**
context.MapRoute(
"FieldProduction_CustomerProduction",
"FieldProduction/CustomerProduction/{id}",
new { action = "Index", controller = "CustomerProduction", id = UrlParameter.Optional }
);
//**ORIGINAL ROUTE**
context.MapRoute(
"FieldProduction_default",
"FieldProduction/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);

Your routing thinks that /1 is action name (but You know this)
You have two options:
1st one: add id parameter like this:
http://localhost:57969/FieldProduction/CustomerProduction?id=1
2nd: Use attribute routing in this controller.
attribute routing
There is 3rd option, You can add regex to Your routing, to identifiy that action is never int.

Related

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.

Routing is finding the controller in my areas, but not the views

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" }
);

Routing in ASP.NET MVC 2.0

I'm looking to make a really simple route in my ASP.NET MVC 2.0 website. I've been googling for help but all the examples I can find are for really complex routing.
Basically I want all the pages in my Home Controller to resolve after the domain as opposed to /Home/
For example I want http://www.MyWebsite.com/Home/LandingPage/
To become http://www.MyWebsite.com/LandingPage/
But only for the Home controller, I want the rest of my controllers to function as normal.
I thought about creating a controller for each and just using an index, but we need lots of landing pages for our marketing like this and it would quickly make the site loaded with controllers for a single page each, which is less than ideal.
One way to do this would be to have a separate route for each landing page. Another way would be to have a single route with a constraint that matches each landing page (and nothing else).
routes.MapRoute(
"LandingPage1"
"landingpage1/{id}",
new { controller = "home", action = "landingpage", id = UrlParameter.Optional } );
routes.MapRoute(
"LandingPage2"
"landingpage2/{id}",
new { controller = "home", action = "landingpage2", id = UrlParameter.Optional } );
Note that you could probably do this with a bit of reflection as well (untested).
foreach (var method on typeof(HomeController).GetMethods())
{
if (method.ReturnType.IsInstanceOf(typeof(ActionResult)))
{
routes.MapRoute(
method.Name,
method.Name + "/{id}",
new { controller = "home", action = method.Name, id = UrlParameter.Optional } );
}
}
The RouteConstraint solution would be similar except that you'd have a single route with a custom constraint that evaluated whether the appropriate route value matched one of the methods on the HomeController and, if so, replaced the controller and action with "home" and the matched value.
routes.MapRoute(
"LandingPage",
"{action}/{id}",
new { controller = "home", action = "index", id = UrlParameter.Optional },
new LandingPageRouteConstraint()
);
public LandingPageRouteContstraint : IRouteConstraint
{
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
// simplistic, you'd also likely need to check that it has the correct return
// type, ...
return typeof(HomeController).GetMethod( values.Values["action"] ) != null;
}
}
Note that the route per page mechanism, even if you use reflection, is done only once. From then on you do a simple look up each time. The RouteConstraint mechanism will use reflection each time to see if the route matches (unless it caches the results, which I don't think it does).
I think you are missing the default route.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
So, when you type www.mywebsite.com, the controller, action, and id parameters would have the following values:
controller : Home
action: Index
id : ""

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