Longer Route for ActionResult Method - asp.net-mvc

Is it possible in MVC 4 to give a method a longer route than just /{controller}/{method}?
That is, how could I have a method "test", and have it's path be /{controller}/api/test?

You can register additional routes by modifying "RegisterRoutes()" in your project's Global.asax.cs file.
For example:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"YourNewRoute", // Route name
"{controller}/api/{action}", // URL with parameters
new { controller = "YourController", action = "Test" } // Parameter defaults
);
}
Now, if you enter:
YourController/api/Test
(Note the presence of "api" in the path)
it will call:
controller = YourController
action = Test
You can find more information about routes in this tutorial.

Yes it is. Simply define the api/test route before the general /{controller}/{method} so that the api/test route kicks in before the general route.
NOTE: When you define the /api/test, you'll need to specify the default action method.
Also, are you using WebAPI?

Related

Attribute Routing : Object reference not set to an instance of an object

I'm using this codes to Attribute Routing .
my controller :
[RouteArea("Administrator")]
[Route("{action}")]
public partial class HomeController : Controller
{
[HttpGet]
[Route("~/Home/Template/{id}")]
public virtual ActionResult Template(string template)
{
switch (template.ToLower())// error :Object reference not set to an instance of an object..
{
case "main":
return PartialView(Url.Content(MVC.Administrator.Home.Views.Main));
default:
throw new Exception("template not known");
}
}
}
RouteConfig :
internal static class RouteConfig
{
internal static void RegisterRoutes(AreaRegistrationContext context)
{
context.MapRoute(
"Administrator_default",
"Administrator/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { string.Format("{0}.Controllers", typeof(RouteConfig).Namespace) });
}
}
url : http://localhost:22738/home/template/main
how to use Attribute Routing ?
Is there a way to solve the problem? I have no idea
It would behoove you spend some time reading the documenation for attribute routing. See: http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx
First, you haven't enabled attribute routing. You need the following in RouteConfig.cs:
routes.MapMvcAttributeRoutes();
Second, you can't apply [Route] to a controller class. You can use [RoutePrefix] if that's what you're looking for. However, bear in mind, that if you include a param in your route prefix, all your action must accept that param. Although, since you have a param of action here, it's entirely possible that you just don't understand how this works. You don't pass the action name with attribute routing. The action that is hit is determined by the one that has the matching route.
Third, when you define a route on an action, you need only specify the portion of the route not covered by RouteArea or RoutePrefix. Using the tilde (~) says that you want to ignore all set prefixes and define the whole route for the action, if that's what you actually want here, that's fine, but just keep in mind that you don't have to follow the /Controller/Action/{id} convention employed by the default route in RouteConfig.cs. The whole point of attribute routing is to define custom routes easily. If you're going to rely on the default route, you might as well just use that and forget about attribute routing.
Finally, in your route, you're accepting the param, id, but your action doesn't take it. Instead it has its own param of template. The params need to match or either the route will not match or the action won't be able to work.
To summarize, the following is likely what you're looking for:
[RouteArea("Administrator")]
[RoutePrefix("home")]
public partial class HomeController : Controller
{
[Route("template/{template}")]
public virtual ActionResult Template(string template)
{
switch (template.ToLower())// error :Object reference not set to an instance of an object..
{
case "main":
return PartialView(Url.Content(MVC.Administrator.Home.Views.Main));
default:
throw new Exception("template not known");
}
}
}

Glimpse doesn't work with Areas

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.

Attribute Routing not working in areas

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.

Why do you need a route defined for Html.Action?

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

How to put all my Controllers in a own classlibrary

Can I configure MVC to directly look into a class library ("MyApp.Controllers" for example) instead of the foder Controllers?
Yes, you can. The easiest way is to add the namespace of your class library to the routers.MapRoute call in global.asax. This is how one of our configurations look like:
public static void RegisterRoutes(RouteCollection routes, IEnumerable<string> controllerNamespaces)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
controllerNamespaces.ToArray()
);
}
Notice that we use an overload of MapRoute that allows us to supply a string array of controller namespaces.
Another option is to implement a custom IControllerFactory, but that's more work and usually not necessary.
The ASP.NET MVC runtime knows nothing about a folder named 'Controllers', that is just a project structure convention. You can have your controllers on any assembly, on any namespace, and it should work without any special configuration.

Resources