I have the following Controller
namespace ExampleProject.Areas.Plugins.Default.Admin.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
}
I have the following custom route in my PluginsAreaRegistration.cs
context.MapRoute(
"Plugins_Default_Admin",
"Plugins/Default/Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces: new[]{
"ExampleProject.Areas.Plugins.Default.Admin.Controllers"
}
);
So when I call the url "/Plugins/Default/Admin/Home/Index", I will get to the ActionResult in the controller Above.
What I want achieve is to call the Index ActionResult using Html.Action in a different View
Example:
#Html.Action("Index", "Home", new {area = "Plugins" })
I'm not sure how I can point the the specific home controller that I want? I have multiple HomeControllers in the Plugins Area, all with different routes.
Error I'm getting is "Multiple types were found that match the controller named 'Home'."
Related
I am creating a project in C# MVC and was using actions. Due to the requirements, now I am using Route to hide the controller name and display just the page name.
route config
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Law",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Law", action = "Home", id = UrlParameter.Optional }
);
controller 1 (to access this : http://localhost:17920/dashboard) and (http://localhost:17920/alert)
public class LawController : Controller
{
[Route("dashboard")]
[ActionName("Home")]
public ActionResult Home()
{
return View();
}
[Route("alert")]
[ActionName("alert-list")]
public ActionResult AlertList()
{
return View();
}
controller 2 (to access this : http://localhost:17920/list)
public class ListController : Controller
{
[Route("list")]
[ActionName("list-of-return")]
public ActionResult listOfReturn()
{
return View();
}
What I am trying is when I enter this http://localhost:17920 as a default URL, then http://localhost:17920/dashboard should be displayed by default.
Thanks.
You need to define RoutePrefix over the Controller like below. Also update your Route("dashboard") to Route("Home") because that is your default action name on route configuration.
[RoutePrefix("Law")]
public class LawController : Controller
{
[Route("Home")]
[ActionName("Home")]
public ActionResult Home()
{
return View();
}
// Other action methods
}
Please also refer https://devblogs.microsoft.com/aspnet/attribute-routing-in-asp-net-mvc-5/ for more details.
Edit As per edit in your question it is more clear what you want. From your existing code you just need to add one more Route over LawController's Home action as below, so it could match http://localhost:17920/ & http://localhost:17920/dashboard to that action method.
public class LawController : Controller
{
[Route("")]
[Route("dashboard")]
[ActionName("Home")]
public ActionResult Home()
{
return View();
}
[Route("alert")]
[ActionName("alert-list")]
public ActionResult AlertList()
{
return View();
}
I've got this route configuration:
routes.MapRoute(
"routeB",
"routeB/{action}/{id}",
new { controller = "Sample", action = "IndexB", id = UrlParameter.Optional });
routes.MapRoute(
"routeA",
"routeA/{action}/{id}",
new { controller = "Sample", action = "IndexA", id = UrlParameter.Optional });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And my Sample controller contains these Action methods:
public ActionResult IndexA(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexA()
{
return RedirectToAction("Confirmation");
}
public ActionResult Confirmation()
{
return View("Confirmation");
}
public ActionResult IndexB(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexB()
{
return RedirectToAction("Confirmation");
}
If I land on the localhost/RouteA page and make a POST (via a button) it redirects me to localhost/RouteB/Confirmation.
How can I get the page to redirect to the RouteA/Confirmation page?
Thanks.
There are 2 issues here. As others have pointed out, your HTTP POST needs to be corrected. Since you are sharing a single view for 2 different actions, the simplest way to do that is to set the actionName parameter to null. This tells MVC to use the action name from the current request.
#{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; }
<h2>Index</h2>
#using (Html.BeginForm(null, "Sample", new { id = "OrderForm" }))
{
#Html.AntiForgeryToken()
<button type="submit" id="orderFormBtn">Extend my discount.</button>
}
The second issue is that the RedirectToAction call is ambiguous between routeA and routeB when generating the URL. Since the first match always wins, the URL you are redirecting to is always the top one in your configuration.
You can fix this problem by using RedirectToRoute to specify the route name (in addition to your existing matching criteria) explicitly.
public class SampleController : Controller
{
public ActionResult IndexA(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexA()
{
return RedirectToRoute("routeA", new { action = "Confirmation" });
}
public ActionResult Confirmation()
{
return View("Confirmation");
}
public ActionResult IndexB(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexB()
{
// Note that changing this one to RedirectToRoute is not strictly
// necessary, but may be more clear to future analysis of the configuration.
return RedirectToRoute("routeB", new { action = "Confirmation" });
}
}
Your problem is quite simple.
Take a look to your controller, the methods IndexA and IndexB, returns the same view; (from your comments)
#{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; }
<h2>Index</h2>
using (Html.BeginForm("IndexB", "Sample", new { #id = "OrderForm" })) {
#Html.AntiForgeryToken()
<button type="submit" id="orderFormBtn">Extend my discount.</button>
}
When you click on submit, you always do a post to IndexB
There are a lot of ways to correct such a simple error, for example you simple could use 2 different views IndexA and IndexB and change
using (Html.BeginForm("IndexB", "Sample", new { #id = "OrderForm" })) {
to
using (Html.BeginForm("IndexA", "Sample", new { #id = "OrderForm" })) {
The problem is that both your routeA and routeB routes will work for creating a link for an IndexA action in SampleController. As a result, BeginForm will just short-circuit and pick the first route that will work, which may or may not be the "correct" one you're looking for. To differentiate the routes, generate them via the route name:
#using (Html.BeginRouteForm("routeA", new { #id = "OrderForm" })) {
However, that will only ever let you get the "default" routeA route, i.e. /routeA/. There's no way to specify a different action other than the default one for the route.
For more flexibility, you can employ attribute routing, which will allow you to give a custom route name to each action, which you can then use to get the URL for that action.
Short of that, you will need to differentiate the two routes in some way, so that it's not ambiguous which should be used when generating a URL. That's often difficult to do with standard routing, which is why attribute routing is the much better approach if you're going to diverge from simply using the default route for everything.
Alternatively, you can restructure your project to keep the same URL structure, but make it much easier to differentiate routes. By employing areas, you can then specify the area where the route should be created from. For example, assuming you had RouteA and RouteB areas, you could then do:
#using (Html.BeginForm("IndexB", "Sample", new { area = "RouteA", #id = "OrderForm" })) {
That would then mean having a SampleController in each area, but you can employ inheritance to reuse code.
Currently I'm trying to implement some sub-controller mechanism. I've the following structure:
/Controller/Admin/UserController.cs
/Views/Admin/User/Index.cshtml
I'm registering the route like this:
routes.MapRoute(
name: "Admin",
url: "Admin/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
My controller looks like this:
public class UserController : Controller
{
// GET: Administration
public ActionResult Index()
{
return View("~/Views/Admin/User/Index.cshtml"); // <---
}
}
My problem is, that this only works when I return the exact view. When
I pass no View, the automatic resolving of the path does not work. For example this does not work:
public class UserController : Controller
{
// GET: Administration
public ActionResult Index()
{
return View(); // <--- Not working
}
}
Why does this is not working with sub-controller?
Thanks a lot!
I'm having an odd issue with routing in a basic ASP/MVC project.
I have a bunch of nav items setup:
<li>Home</li>
<li>Fire</li>
<li>Law Enforcement</li>
<li>Forensics</li>
<li>Reconstruction</li>
They all work fine, except for the third one, labeled law-enforcement.
When I mouse over this item, the URL is: http://localhost:54003/home/law-enforcement
When I mouse over any other item, the URl is: http://localhost:54003/fire
My Controller setup is:
public ActionResult Index()
{
return View();
}
[Route("~/fire")]
public ActionResult Fire()
{
return View();
}
[Route("~/law-enforcement")]
public ActionResult Law()
{
return View();
}
[Route("~/forensics")]
public ActionResult Forensics()
{
return View();
}
[Route("~/reconstruction")]
public ActionResult Reconstruction()
{
return View();
}
And my route config is:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LowercaseUrls = true;
routes.MapMvcAttributeRoutes();
routes.MapRoute("Default", "{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = UrlParameter.Optional}
);
}
When I go to the route the URL specifies, ASP responds with a 404 page not found (as it should). If I go to the route that I know it should be, such as localhost/law-enforcement then the correct View is rendered.
Any ideas why ASP is routing this one particular action incorrectly?
The Razor Url.Action(...) cannot refer to the route defined by the [RouteAttribute] on the controller action; instead it needs to refer to the action's name. So changing my Razor syntax to refer to #Url.Action("law", "home") instead of #Url.Action("law-enforcement", "home") solved the issue.
You can keep your url mapping in either of 2 ways as below:
One way is to decorate your actionresult with attribute as below:
// eg: /home/show-options
[Route("law-enforcement")] //Remove ~
public ActionResult Law()
{
return View();
}
As per docs
otherwise
Just add one more configuration in Route.config file
routes.MapRoute("SpecialRoute", "{controller}/{action}-{name}/{id}",
new {controller = "Home", action = "law-enforcement", id = UrlParameter.Optional}
);
Source
I have seen some similar questions but i found that i have a diferent problem.
I have a MVC4 project with controllers in the base project and 3 areas. Each area contains a controller named AdminController and a method List.
My problem is when i use a link to go to method List always go through the same controller (e.g. AdminController in Users area) but the view returned is correct.
The structure of the project is like this:
Mimbre.Administration (web mvc4 project)
Areas
Contents
Controllers
AdminController
Models
Views
Admin
List.chtml
-ContentsAreaRegistration.cs
Logs
Controllers
AdminController
Models
Views
Admin
List.chtml
LogsAreaRegistration.cs
Users
Controllers
AdminController
Models
Views
Admin
List.chtml
UsersAreaRegistration.cs
Controllers (base project)
Views (base project)
Classes for area registration contains RegisterArea method like this (with apropiate name and namespace according to the current area):
public override void RegisterArea(AreaRegistrationContext context) {
context.MapRoute(
"Contents_default",
"Contents/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new string[] { "Mimbre.Administration.Areas.Contents.Controllers" }
);
}
Global.asax contains method for register routes for base project
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 },
namespaces: new string[]{"Mimbre.Administration.Controllers"}
);
}
In index.chtml (in base project) I created links to List method of each area:
<a href="#Url.Action("List", "Admin", new { area = "Contents"})" >Contents list</a>
<a href="#Url.Action("List", "Admin", new { area = "Logs"})" >Logs list</a>
<a href="#Url.Action("List", "Admin", new { area = "Users"})" >Users list</a>
In generated html these links appear like this:
Contents list
Logs list
Users list
I think these links are correct, the problem is that any link clicked always takes me through method List in AdminController of Users area, method List in AdminController of another areas are never picked.
This problem could be solved by renaming the name of the controller, unique name for each area, but i really need keep the same controller name in all areas, any idea???
Thans in advance!!!
Finally I could simulate your problem. The only way that I could have the same output as you (doens't matter what area you're trying to access, but reaching same users controller and responding with right view) is when you register the Area with the wrong controllers namespaces.
/Admin/Home/List route:
namespace SandboxMvcApplication.Areas.Admin.Controllers
{
public class HomeController : Controller
{
public ActionResult List()
{
ViewBag.Message = "You've acessed Admin/List";
return View();
}
}
}
/Admin/Home/List view:
#{
ViewBag.Title = "List";
}
<h1>Admin</h1>
<h2>#ViewBag.Message</h2>
Admin Area Registration:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new string[] { "SandboxMvcApplication.Areas.Admin.Controllers" }
);
}
/User/Home/List route:
public class HomeController : Controller
{
public ActionResult List()
{
ViewBag.Message = "You've accessed User/List";
return View();
}
}
/User/Home/List view:
#{
ViewBag.Title = "List";
}
<h1>User</h1>
<h2>#ViewBag.Message</h2>
User Area registration:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"User_default",
"User/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new string[] { "SandboxMvcApplication.Areas.Admin.Controllers" }
);
}
With this configuration, my outputs are:
/Admin/Home/List
Admin
You've acessed Admin/List
/User/Home/List
User
You've acessed Admin/List
Note that the first word works fine (insuring that the right view was rendered but with wrong data comming from list action).
If I correct the user controller namespace in UserAreaRegistration ("SandboxMvcApplication.Areas.User.Controllers"), everything works fine!
Check it out if this could help you or try to explicitly put your namespaces to the right controller. You could also check if the Admins controller are with the right namespace too.
You need to append one more setting to your route registration. This will prevent the DefaultControllerFactory from searching for controller in unintended areas.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[]{"Mimbre.Administration.Controllers"}
).DataTokens["UseNamespaceFallback"] = false;