Scenario: I have a Forms area in my ASP.NET MVC 5 site.
I'm trying to redirect to the Details Action which uses a custom route defined using the new Attribute Routing feature.
The RedirectToAction:
return RedirectToAction("Details", new { slug });
The action I'm redirecting to:
[HttpGet]
[Route("forms/{slug}")]
public ActionResult Details(string slug)
{
var form = FormRepository.Get(slug);
...
return View(model);
}
I would expect a redirect to http://localhost/forms/my-slug but instead the app is redirecting me to http://localhost/Forms/Details?slug=my-slug.
This means that the attribute routing is not working.
How can this be solved?
I have added the routes.MapMvcAttributeRoutes(); line to my RouteConfig:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And here's my Application_Start():
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
You are probably combining convention based routing with attribute routing, and you should register your areas after you map the attribute routes.
The line
AreaRegistration.RegisterAllAreas();
should be called AFTER this line:
routes.MapMvcAttributeRoutes();
The explanation (from https://devblogs.microsoft.com/aspnet/attribute-routing-in-asp-net-mvc-5/):
If you are using both Areas with route attributes, and areas with convention based routes (set by an AreaRegistration class), then you need to make sure that area registration happen after MVC attribute routes are configured, however before the default convention-based route is set. The reason is that route registration should be ordered from the most specific (attributes) through more general (area registration) to the mist generic (the default route) to avoid generic routes from “hiding” more specific routes by matching incoming requests too early in the pipeline.
When you create a blank asp.net mvc website, add an area and start using attribute routing, you will encounter this problem because the "Add Area" action in visual studio adds the RegisterAllAreas call in your Application_Start, before the route configuration..
Alternative solution
Perhaps you do not intend to keep using convention based routing and prefer to only use attribute routing.
In this case you can just delete the FormsAreaRegistration.cs file.
Moving the AreaRegistration.RegisterAllAreas() to RouteConfig.cs wasn't enough for me. I also needed to use the AreaPrefix parameter for the RouteArea attibute:
//Use the named parameter "AreaPrefix"
[RouteArea("AreaName", AreaPrefix = "area-name-in-url")]
[RoutePrefix("controller-name-in-url")]
public class SampleController : Controller
{
[Route("{actionParameter}")]
public ActionResult Index(string actionParameter)
{
return View();
}
}
Edit: At some point, I came across a sample solution from Microsoft that nicely showed how to handle attribute routing. It also showed some nice examples of how to translate a SelectList into an array of input[type="radio"] items as well as doing the same with an array of input[type="checkbox"] items (if I recall). This sample solution is probably a better answer to this question--as well as giving some good examples on displaying radio buttons and checkbox items. If anyone knows of this sample solution, please add a comment with a link to it.
Related
I have an existing MVC project. I added a new area and this area will be web api. My web api routes are not being hit and since routing is still a frustrating thing for me I'm sure I'm doing something wrong.
In my route provider I thought this was all I had to do...
public void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
}
In my controller:
[RoutePrefix("api/nav")]
public partial class NavigationController : ApiController
{
[HttpGet]
[Route("MainDash")]
public DashboardModel GetDashItems()
{ ...}
}
My MVC area is defined as MyArea,
So in fiddler the following Get request return 404.
/MyArea/api/nav/MainDash
/api/nav/MainDash
Do I still have to define old fashioned conventional routes as well?
When I register my route I do set a default template...but the attribute routing should override...???
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"MyArea_default",
"MyArea/{controller}/{action}/{id}",
new {controller="Home", action = "Index", area = "MyArea", id = "" },
new[] { "Testing.Nav.Controllers" }
);
}
I really don't think this has anything to do with it but I am at a loss for getting my routes figured out.
TIA
Update
Forgot this controller is also NOT in the controllers folder. It is in a folder called Api.
I tried /api/api/nav/GetDashIyems but still didn't work. But I wonder do I need to add the Api folder to the route table as an area to search...???
I've setup a site on our webserver using IIS8. The URL is something link ABC.mycompany.com. When I go to ABC.mycompany.com, I get a not found error:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.17929
If I go to ABC.mycompany.com/Home/InstrumentList, it shows the the correct page I want to start on. How do I get it to start at this route?
In Visual Studio 2013, I have Home/InstrumentList set as my start action and that works fine. I've looked at several examples where they reference default and index pages, but my application doesn't have any of these. Also, there are references to .aspx pages, but there are no .aspx pages in my app, just .cshtml Views and .cs Controllers. I also have seen things about adding routes, but I'm unsure where to put them in global.asax.cs (in ApplicationStart? outside it? in a different method? just alone?). I also don't know what to change to work for mine (change index to InstrumentListing or leave it alone). Nothing has worked for me so far.
Here's one of my attempts using global.asax.cs where I tried to add a register route method:
namespace GPC
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
WebApiConfig.Register(GlobalConfiguration.Configuration);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default",
"{controller}/{action}/{Filtre}",
new { controller = "Home", action = "Index", Filtre = UrlParameter.Optional });
}
}
}
Here are some of the sites I've used, but nothing has worked for me:
http://weblog.west-wind.com/posts/2013/Aug/15/IIS-Default-Documents-vs-ASPNET-MVC-Routes
cannot set Default route for MVC project
http://weblogs.asp.net/imranbaloch/editing-routes-in-mvc
I'd appreciate if someone could show me how to get ABC.mycompany.com/Home/InstrumentList to show when the user goes to ABC.mycompany.com.
I'm not sure, but your RegisterRoutes method should be in RouteConfig class that by default should be in App_Start foulder of MVC project. This is not a method in MvcApplication class that located in Global.asax.cs as i see in your question.
Then you should only change RegisterRoutes method like this to set different default controller action:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default",
"{controller}/{action}/{Filtre}",
new { controller = "Home", action = "InstrumentList", Filtre = UrlParameter.Optional });
}
I'm new to Asp.Net MVC, due to some issues I've been having with Routes I discovered Glimpse and thought I'd give it a go, but I couldn't get the Glimpse.axd config page up, constantly getting a HTTP 404.
I made a new project and tried that, it worked fine. Through trial and error I eventually found that the problem is in the AreaRegistration file for my areas, if I do this:
context.MapRoute(
"SomeArea_default",
"Area/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
It works, I can get to the page, but if I leave out the area part
context.MapRoute(
"SomeArea_default",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
then it doesn't.
I have previously asked whether that part was optional and I thought it was:
Area routing, why is the area name needed?
I'm putting it back in to get things working, whats the actual reason behind this? Should that always be there or is this a Glimpse bug?
What you are describing is actually normal behavior and has everything to do with the order in which routes are being registered.
Let me explain it, based on a green field project as the one you created to pinpoint the problem.
When you create a new MVC project and you add a Test area for instance, then you'll find the following types and methods inside your project:
A TestAreaRegistration class defined as:
public class TestAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "Test"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Test_default",
"Test/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
An Application_Start method inside your global.asax defined as:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
A RouteConfig class inside the App_Start folder defined as:
public class 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 }
);
}
}
Now it is just a matter of reading through the startup code with regards to registering the routes for the application:
First the areas are being registered AreaRegistration.RegisterAllAreas();
That call will result in TestAreaRegistration.RegisterArea(...) being called which will register the route Test/{controller}/{action}/{id}
Then the call to RouteConfig.RegisterRoutes(RouteTable.Routes) will be made which will register the default {controller}/{action}/{id} route and also registers a route to ignore .axd requests.
now it is important to keep in mind that first all routes defined by the Test area will be checked and then the ones defined by the RouteConfig in case it is not a Test area related route
Now what does this mean when you request /glimpse.axd with this default setup ?
The route that will match is the route that is ignored inside the RouteConfig because the routes that will be handled by your Test area must start with Test/ which is clearly not the case for /glimpse.axd
but if you remove the Test/ part from the Test/{controller}/{action}/{id} route registration inside the Test area registration, then the route that will match the /glimpse.axd request will now be handled by the routes defined by the Test area as it will basically act as a catch all, and since it will not find a match a 404 is returned.
TL;DR
Now you can solve this in 2 ways:
Add context.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); to the top of the TestAreaRegistration.RegisterArea method and the route will be ignored like it is in the default situation
Switch the AreaRegistration.RegisterAllAreas(); with the RouteConfig.RegisterRoutes(RouteTable.Routes) so that the RouteConfig routes will be matched first
Personally I would go for option 1 as it has the least impact.
I can access Ping.HTML and Ping.ASPX but when I try to access the view from my MVC (4.0) project (deployed to the same server, the bogus one, by F5), I get 404.
It's a vanilla project created from the template for MVC 4 with a very default view and controller (no model).
Hints on how to resolved it? I'm out of ideas...
EDIT
My RouteConfig.cs is like this
public class 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 }
);
}
}
Controllers folder contains only one, single file called ÄDefault1Controller.cs*. It only does this:
public class Default1Controller : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Test()
{
return View();
}
}
EDIT
The exact URLs typed in (besides the server name alone, of course) are:
> http://localhost:49642/Index
> http://localhost:49642/Index.aspx
> http://localhost:49642/Home/Index
> http://localhost:49642/Home/Index.aspx
> http://localhost:49642/Default/Index
> http://localhost:49642/Default/Index.aspx
Based on the information you've given, it sounds like a routing problem. The URL you are requesting isn't firing a controller.
EDIT
MVC works by convention, so by naming your controller Default1Controller the matching URL would start with /Default1.
In the example you've given, you can only access the Test() method by navigating to http://localhost:49642/Default1/Test, which will return the view typically located at /Views/Default1/Test.aspx (or /Views/Default1/Test.cshtml for razor-based views).
Please check out the routing overview at ASP.NET for more information about how the route table maps to controllers and actions. I should point out that the link is for the older versions of MVC, but you should get the idea.
Let me know if I can help further.
Matt
I have created an otherwise empty ASP.NET MVC 3 application with 2 controllers, HomeController and OtherController.
HomeController.cs looks like this:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
Index.cshtml looks like this:
#Html.Action("Index", "Other")
And, of course, Othercontroller.cs:
public class OtherController : Controller
{
[ChildActionOnly]
public ActionResult Index()
{
return Content("OK!");
}
}
So far, so good. I run the app, and it tells me everything is OK!
Now, I take the default RegisterRoutes from Global.asax.cs:
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
);
}
And I crumple it up, so that no routes match OtherController:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "", new { controller = "Home", action = "Index" });
}
Now, when I run the page, it crashes with this error message:
System.InvalidOperationException: No route in the route table matches the supplied values.
Source Error:
Line 1: #Html.Action("Index", "Other")
I specified the controller name and action name in the call to .Action. No URLs are being generated, and no requests are being made. Why does routing even need to get involved?
I think that this blog post will help you understand a little more:
http://blogs.charteris.com/blogs/gopalk/archive/2009/01/20/how-does-asp-net-mvc-work.aspx.
Essentially, routing is involved in determining which controller to 'fire up' to handle the request and appropriate action to invoke based on the parameters that you sent and the MVCRouteHandler uses those to make a decision. Just because you tell it which controller in your action does not make it magically ignore the routing table, go straight to that controller class and bypass all the other MVC goodness that happens in the back-end. Remember, these #HTML.Action methods can take a whole lot of overloads which could influence which route in the routing table to use (think URL structure for instance).
The MVC paths are not to static content and as such, have to be parsed through the URLRoutingModule which uses the routing table to decide on what to do. Since you have no matching route - you get an error.
EDIT
In my diatribe, I did not actually address your final statement. You're right, no URL was generated, but a request to the application was generated. HTML.Action still will use routing to determine what controller, action, area, parameters to use. I think it be fair to say in simple terms that it's like generating an ActionLink and clicking it for you.
Routing got involved by using Html.Action on a controller. When the engine couldn't find the "Other" HtmlHelper with an action of "Index" it defaulted to executing that path which means passing through routing. No route matched so it threw an error.
InvalidOperationException The required virtual path data cannot be found.
http://msdn.microsoft.com/en-us/library/ee721266.aspx
The solution for me was as follows:
I had the following line that was giving me an error:
#{Html.RenderAction("ActionName", "ControllerName");}
Adding a thrid parameter solved the error:
#{Html.RenderAction("ActionName", "ControllerName", new { area = string.Empty });}