I've recently added Areas to an existing MVC 4 web app. One of my areas has a Home controller, so obviously when I navigate to /MyArea/Home/Index I want to display it's Index view. Initially I was getting the following error:
Multiple types were found that match the controller named 'Home'. 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.
The request for 'Home' has found the following matching controllers:
MyApp.Controllers.HomeController
MyApp.Areas.MyArea.Controllers.HomeController
Researching the issue I found that I needed to add default namespaces to my calls to MapRoutes() and that's stopped the error. Unfortunately I now find that when I go to /MyArea/Home/Index the app actually displays the view for /Home/Index instead - I can't display actions from the Area's Home controller.
Here's my code:
Global.aspx.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
MyApp.RouteConfig
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional},
namespaces: new[] {typeof(MyApp.Controllers.HomeController).Namespace}
);
}
MyApp.Areas.MyArea.MyAreaRegistration
public override void RegisterArea(AreaRegistrationContext context)
{
var ns = typeof(MyApp.Areas.MyArea.Controllers.HomeController).Namespace;
var routeName = AreaName + "_Route";
var routeUrl = AreaName + "/{controller}/{action}/{id}";
var routeDefaults = new {controller = "Home", action = "Index", id = UrlParameter.Optional};
var routeNamespaces = new[] {ns};
context.MapRoute(
name: routeName,
url: routeUrl,
defaults: routeDefaults,
namespaces: routeNamespaces
);
}
Anyone got any bright ideas on how to solve this?
Update:
The problem only occurs when calling an action that exists in both HomeController classes, like Index. Calling an action in the Area's HomeController that does NOT exist in the default HomeController displays the correct view.
Update 2:
Turns out that this was a classic PICNIC error - a simple typo in my code, so the action went looking for a view that did not exist. As such, it went for the first matching view it could find - the one in the "root"
You need to add new { Area = "MyArea" } in the routeValues parameter when you are linking to the one in the MyArea area.
In your RegisterArea function you should update the routeDefaults to also include the area name.
var routeDefaults = new {controller = "Home", action = "Index", area = AreaName, id = UrlParameter.Optional};
<facepalm>
In the process of importing the code for the Area I made a typo in the view's folder name, so MVC wasn't able to find the correct index.cshtml for the area action.
</facepalm>
By the looks of things, when MVC can't find the area action's view it uses the default view instead. Once I'd fixed the name of the folder in Areas\MyArea\Views to match the controller name everything worked as expected.
+1 to both of the folks who answered - your responses were helpful and I've employed both in my updated code
Related
I guess I don't completely understand how urls work with C# projects, in the sense that I don't know how to specify a url to go through the controller and not just return a aspx page.
Say I am trying to get to my project's Index page through a Controller named "ScholarshipController.cs". I would think to hit the Index method/action in this controller, my url would be as follows (my app's name is "TuitionAssistance" fyi):
http://localhost/TuitionAssistance/Scholarship/Index
However, running this url just returns the aspx page named "Index.aspx" located in the "Scholarship" view file without hitting the Controller. Why is this happening, and how do I get it to go through the controller so the Index page, when loaded, will have the appropriate information loaded onto it?
Sorry if this is a dumb question. Any insight would be appreciated. Thanks!
Route.config:
using System.Web.Mvc;
using System.Web.Routing;
namespace ScholarshipTuitionAssistance
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
/* Scholarship */
/* Scholarship */
//routes.MapRoute("TuitionAssistance",
// "tuition/{name}",
// new { controller = "TuitionAssistance", action = "Index", name = "" });
routes.MapRoute(
name: "TuitionAssistance",
url: "{controller}/{action}/{employee_number}",
defaults: new { controller = "Home", action = "TuitionAssistance", employee_number = UrlParameter.Optional }
);
routes.MapRoute(
name: "Scholarship",
url: "{controller}/{action}/{employee_number}",
defaults: new { controller = "Home", action = "Scholarship", employee_number = UrlParameter.Optional }
);
routes.MapRoute(
name: "Details",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Scholarship", action = "Details", id = UrlParameter.Optional }
);
}
}
}
Your route (URL) cannot match anything that actually exists on the filesystem. In your example here, you apparently have a file, [document root]\Scholarship\Index.aspx. As a result, a request for Scholarship/Index will return that file, instead of invoking the ASP.NET MVC machinery to load a controller action.
In MVC ASP.NET, think of those types of links as a way to call your methods in your controller. When that link is accessed, your controller does a bunch of junk and then returns an ActionResult (or other things). This ActionResult, for the sake of this explanation, is the markup that is written in the corresponding view file. Controller - >index() will return the view called index under views - > controller. If you want to pass information to your view, you will pass a model that has all of your information in it to the view from your index controller (return View(MyFancyModel)). The view will have a razor line at the top such as: #model The.Namespace.Wherever.my.model.is
The scaffolded controllers and views in Visual Studio for the index page specifically, only pass a list of the items in the corresponding database.
I'm currently using HashRouter and it works really well. However I would like to be able to use the # on sub routes as well for linking to paragraphs. For example /details#Summary. As a benefit I will also get cleaner URLs and if needed I can get some SEO.
Works and gives correct results on refresh/direct link.
<HashRouter>
<App />
</HashRouter>
Works but gives 404 on refresh/direct link.
<BrowserRouter>
<App />
</BrowserRouter>
I understand that the problem here is my routing in .Net and I need to change it. What do I need to do? I have a default route but it does not get hit.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
First remove the standard routes.MapRoute that is shown above and then add this:
routes.MapRoute("Client", "{*anything}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Now any route will render your default action.
Optional:
If you have a controller with attribute routing, example:
[RoutePrefix("Home")]
public HomeController : Controller {
//GET Home/Index
[HttpGet]
[Route("Index")]
public ActionResult Index() {
return View();
}
}
You also need to add:
routes.MapMvcAttributeRoutes();
The thing is that when you change that, asp.net keeps trying to match a route from for details.
What you need to do is create a route that matches all paths, so that it returns the default one, eg: home/index
This is the route I use:
routes.MapRoute(
"Default",
"{*url}",
new { controller = "Home", action = "Index" });
That will give control to the browser to math the paths after '/'
We have several controllers we would like grouped in a subfolder (Admin) on our site. We would have the main pages be at the root level. But for these pages we would like to have our site path be something like this:
www.domain.com/Admin/{controller}/{action}/{id}
I've set the RouteConfig.cs file like this:
routes.MapRoute
(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Submission", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute
(
name: "Admin",
url: "Admin/{controller}/{action}/{id}",
defaults: new { controller = "SystemSecurity", action = "Index", id = UrlParameter.Optional }
);
I've set up one of the controllers like this:
[RoutePrefix("Admin/SystemSecurity")]
public class SystemSecurityController : Controller
{
private MkpContext _db = new MkpContext();
// GET: SystemSecurity
public ActionResult Index()
{
var roles = _db.Role.Select(r => r);
return View(roles.ToList());
}
}
In our solution the path to the controller is: \Controllers\Admin\SystemSecurityController.cs
The path to the view is: \Views\Admin\SystemSecurity\Index.cshtml
But we get the 'Resource cannot be found' error message.
I've also tried it with no RoutePrefix, and also with RoutePrefix("Admin").
If I put the view here: \Views\SystemSecurity\Index.cshtml
and navigate with this path: www.domain.com/SystemSecurity/Index
the page loads, so I know the controller and page are working.
What am I doing wrong?
I found out about MVC Areas. (http://www.philliphaydon.com/2011/07/mvc-areas-routes-order-of-routes-matter/)
By adding an Area to my project (Right click on Project's name, then Add - Area) I am able to better group my code.
Many of the pages I found either don't mention Areas or when the do, it's in passing, but they don't explain them. Hopefully this will help somebody else.
I have a domain "http://www.abc.com". I have deployed an ASP.net MVC4 app on this domain. I have also configured a default route in RouteConfig.cs as shown below
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "MyApp", action = "Home", id = UrlParameter.Optional }
);
The above mapping ensures that anyone attempting to visit "http://www.abc.com" is automatically shown the page for "http://www.abc.com/MyApp/Home"
Everything works as expected but the address bar in the browser shows "http://www.abc.com" instead of "http://www.abc.com/MyApp/Home". Is there any way to force the browser to show the complete URL including the controller and Action?
One option would be to set your default route to a new controller, maybe called BaseController with an action Root:
public class BaseController : Controller
{
public ActionResult Root()
{
return RedirectToAction("Home","MyApp");
}
}
and modify your RouteConfig to point to that for root requests:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Base", action = "Root", id = UrlParameter.Optional }
);
You'll need to do some kind of url rewriting. Probably the quickest way is to add a RewritePath call to your BeginRequest in Global.asax. In your case it'd be something like this:
void Application_BeginRequest(Object sender, EventArgs e)
{
string originalPath = HttpContext.Current.Request.Path.ToLower();
if (originalPath == "/") //Or whatever is equal to the blank path
Context.RewritePath("/MyApp/Home");
}
An improvement would be to dynamically pull the url from the route table for the replacement. Or you could use Microsoft URL Rewrite, but that's more complicated IMO.
Just remove the default parameters, it's been answered here:
How to force MVC to route to Home/Index instead of root?
I have a request controller that is getting out of hand, and I want to divide the actions on several controllers while maintaining a clean URL. I'm trying to experiment with routing, but without success. I've read some examples and tutorials on routing, but, though I understand the examples, nothing seems to apply to my case, and I feel non the wiser. What I want is for the URL Requests/Approval to be handled on my ApprovalController instead of my RequestController, so I wrote the following.
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(
"Approval",
"Request/{controller}/{action}",
new { controller = "Approval", action="Index", id = "" }
);
}
But it's not working. Why? I have a folder in the my Views called Approval, and in there I have a file called Index.cshtml. How should I code the MapRoute?
Edit
I added all the routes I've got
You need to swap the two MapRoute statements, like so:
routes.MapRoute(
"Approval",
"Request/Approval/{action}",
new { controller = "Approval", action="Index", id = "" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
The reason it is currently not working is because the first statement ('Default' route name) is getting matched before the second one is even evaluated.
In addition (as noted in my above example,) you need to remove '{controller}' in the Approval route and replace with 'Approval'... unless you specifically want the URL /Request/{ANY controller}/{action} to go through, which I doubt. From your question it seems you only want /Request/Approval/ to go to your Approval controller.
Don't forget to keep the Default route at the bottom, so as to match your other controllers and actions. It serves as a catch-all should no other matches exist.
The order you map your routes matters. Move the second route before the default route.
You will still have a problem though, as any thing /request/something will look for the SomethingContoller. To fix this, change your route to this:
routes.MapRoute(
"Approval",
"Request/Approval/{action}",
new { controller = "Approval", action="Index", id = "" }
);