Using Route Attribute with Html.Action C# MVC - c#

I have the following application hierarchy:
Area > Website > HomeController > HeaderAction
Once I decorated my controller action with a route attribute, the #Html.Action in my Layout View stopped working
[RouteArea("Website", AreaPrefix = "Home"), Route("{action}")]
public class HomeController : Controller
{
[Route("~/header", Name = "head")]
public ActionResult Header()
{
WebsiteModel model = new WebsiteModel();
return PartialView(model);
}
}
#Html.Action("head")
Now I have to use AreaRegistration instead
Does anyone know what my problem is?
Thanks in advance

I think your problem is that you defined your route as [Route("~/header", Name = "head")] which make the route relative to the base application path. In your code you use #Html.Action("head") which uses the name of the route you defined instead of the actual route.
Try changing it to
#Html.Action("header")

Related

Controller inheritance and choosing which controller has prevalence

I have a baseproject and different inheriting projects. The base project has controllers I may want to occasionally inherit and override (partially).
Base project:
public virtual ActionResult Index(string filter = "", int page = 1)
Sub project:
public override ActionResult Index(string filter = "", int page = 1)
Now I changed the routeConfig, so the routing is mapped to the logic from the correct namespace.
context.MapRoute(
"Routename",
"AreaName/{controller}/{action}/{id}",
new { controller = "ControllerName", action = "Index", id = UrlParameter.Optional },
new string[] { "ProjectName.Areas.AreaName.SpecificControllers"}
);
However, I want new added routes to be taken from the specific project should they exist there. The ones which are not existant should be taken from the base project's controller. (The specific controller basically starts out empty and will only contains methods for when overriding is desirable). To try and implement this functionality, I added the other project to the routing here:
context.MapRoute(
"Routename",
"AreaName/{controller}/{action}/{id}",
new { controller = "ControllerName", action = "Index", id = UrlParameter.Optional },
new string[] { "ProjectName.Areas.AreaName.SpecificControllers", "ProjectName.Areas.AreaName.GenericControllers"}
);
However, this obviously leads to the following error:
Multiple types were found that match the controller named 'MethodName'. This can happen if the route that services this request ('CRM/{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 'MethodName' has found the following matching controllers:
ProjectName.Areas.AreaName.SpecificControllers.ControllerName
ProjectName.Areas.AreaName.GenericControllers.ControllerName
Is there a way to implement this so that my routing will always look at the specific controller first and only at the generic controller if it cannot find the method in the specific controller?
Generally routing choose the base controller method as far as i know.
There is no direct support to resolve the issue you mentioned in this question.
There are couple of workarounds to resolve this.
Option 1 (My Favourite): Admin on base and Route on inherited controller.
To Use [Area] on the base controller and [Route] on the inherited controllers.
I personally like this approach because it keeps the code inside controller clean.
[Area("Admin")]
AdminBaseController: Controller { }
[Route("Users"))
UserAdminController : AdminBaseController { }
Url would be /Admin/Users/Action
Option 2: Use Specific Route Prefix in derived controller actions
[Route("Admin")]
AdminBaseController: Controller { }
public static string UserAdminControllerPrefix = "/Users";
UserAdminController : AdminBaseController {
[Route(UserAdminControllerPrefix + "/ActionName")]
public void ActionName() { }
}
Formed URL would be /Admin/Users/ActionName
you can choose whichever option which fits your style.
Hope this helps.
Both the approaches mentioned in this answer : ASP.NET Core MVC Attribute Routing Inheritance

ASP.NET MVC - Nesting Routes / Controllers

I have an ASP.NET MVC app. I have seen similar question asked. However, I haven't found a good answer. Essentially, I want to use the following routes:
/admin/users
/admin/users/create
/admin/users/[someId]
/admin/roles
/admin/roles/create
/admin/roles/[someId]
I have the following file structure:
/Controllers
AdminController.cs
/Admin
UsersController.cs
RolesController.cs
/Views
/Admin
Index.cshtml
/Users
Index.cshtml
Detail.cshtml
Create.cshtml
/Roles
Index.cshtml
Create.cshtml
Detail.cshtml
When I run my app, I just get The resource cannot be found.
What am I doing wrong? I set breakpoints, but none of them are being hit. It's like the routes aren't mapping to the controllers. I'm not sure what I need to do though.
You do not need to create sub folders for this to work. Just have 2 controllers(UsersController and RolesController) and you can use attribute routing to define the custom routing pattern you want.
Assuming you have attribute routing enabled
public class UsersController : Controller
{
[Route("admin/users")]
public ActionResult Index() { // to do : Return something }
[Route("admin/users/create")]
public ActionResult Create() { // to do : Return something }
[Route("admin/users/{id}")]
public ActionResult View(int id) { // to do : Return something }
}
Or you can do the RoutePrefix on the controller level.
[RoutePrefix("admin/users")]
public class UsersController : Controller
{
[Route("")]
public ActionResult Index() { // to do : Return something }
[Route("create")]
public ActionResult Create() { // to do : Return something }
[Route("{id}")]
public ActionResult View(int id) { // to do : Return something }
}
You can do the samething for the RolesControllers as well.
You can enable attribute routing in the RegisterRoutes method in RouteConfig.cs file.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes(); //This line enables attribute routing
//Existing default Route definition goes here
}
You may also consider creating an "Admin" area and put your controllers inside that. Areas are the right solution if you want to logically group similar functionality.
If you do not prefer attribute routing ( why not ?) , you an define these custom route patterns in your RouteConfig. The order in you define the route matters.So make sure you define your specific routes before the default generic one.
You can also override your route tables by decorating your action methods with the RouteAttribute class.
For example:
class AdminController
{
[Route("/admin/users/create")]
public ViewResult CreateUser()
{
...
}
}
This has the advantage of separating the method name from the url component.
You can also route multiple URLs to a single method:
class AdminController
{
[Route("/admin/users/{someId:guid}")]
[Route("/admin/users/{someId:guid}/details")]
public ViewResult UserDetails(Guid someID)
{
...
}
}
As mason said, the file structure isn't important in MVC routing.
If you want to use convention (folder) based routing, you could use MvcCodeRouting to do exactly what you have specified here. It uses namespaces by default, so when you add controllers in a hierarchy, it will generate routes in the same hierarchy automatically. No need to apply the [Route] attribute everywhere and setup your routes manually.

How to determine Route Prefix programmatically in asp.net mvc?

I wanted to provide some URL separation for my public/anonymous controllers and views from the admin/authenticated controllers and views. So I ended up using entirely Attribute Routing in order to take more control of my URLs. I wanted my public URLs to start with "~/Admin/etc." while my public URLs would not have any such prefix.
Public Controller (one of several)
[RoutePrefix("Home")]
public class HomeController : Controller
{
[Route("Index")]
public ActionResult Index()
{ //etc. }
}
Admin Controller (one of several)
[RoutePrefix("Admin/People")]
public class PeopleController : Controller
{
[Route("Index")]
public ActionResult Index()
{ //etc. }
}
This allows me to have public URLs such as:
http://myapp/home/someaction
...and admin/authenticated URLs such as:
http://myapp/admin/people/someaction
But now I want to do some dynamic stuff in the views based on whether the user is in the Admin section or the Public section of the site. How can I access this programmatically, properly?
I know I could do something like
if (Request.Url.LocalPath.StartsWith("/Admin"))
...but it feels "hacky." I know I can access the controller and action names via
HttpContext.Current.Request.RequestContext.RouteData.Values
...but the "admin" piece isn't reflected in there, because it's just a route prefix, not an actual controller name.
So, the basic question is, how do I programmatically determine whether the currently loaded view is under the "admin" section or not?
You just need to reflect the RoutePrefixAttribute from the Controller type, and then get its Prefix value. The Controller instance is available on the ViewContext.
This example creates a handy HTML helper that wraps all of the steps into a single call.
using System;
using System.Web.Mvc;
public static class RouteHtmlHelpers
{
public static string GetRoutePrefix(this HtmlHelper helper)
{
// Get the controller type
var controllerType = helper.ViewContext.Controller.GetType();
// Get the RoutePrefix Attribute
var routePrefixAttribute = (RoutePrefixAttribute)Attribute.GetCustomAttribute(
controllerType, typeof(RoutePrefixAttribute));
// Return the prefix that is defined
return routePrefixAttribute.Prefix;
}
}
Then in your view, you just need to call the extension method to get the value of the RoutePrefixAttribute.
#Html.GetRoutePrefix() // Returns "Admin/People"

MVC 2.0 View that starts with a number

I'm using c#, visual studio 2010. I'm new to MVC
I was supplied with a simple HTML page that I converted to an aspx view. I then added the controller.
The view directory is Views/150/Index.aspx. The issue is that when I go to add the controller, the class name is not allowed to be a number.
namespace MyPages.Controllers
{
public class _50Controller : Controller
{
public ViewResult Index()
{
return View("Index");
}
}
}
When I entered the have the controller a number, it automatically changed it from 150 to _50. So I changed it to 150Controller.cs and changed the class name to 150Controller : Controller.
Unfortunately, you can't have a number as a class name, and _50Controller as the class name tries to direct to Views/_50/Index.aspx.
I would simply change the name, however I was specifically asked to have it as a number. I know I can set up a redirect in ISS... but is there another way to do this?
Thanks!
In C# members cannot start with a number. You could use routing in order to achieve that:
For example you could have the following controller:
public class ErrorController : Controller
{
public ActionResult Index()
{
return View("500");
}
}
which could be routed like this:
routes.MapRoute(
name: "Error",
url: "500",
defaults: new { controller = "Error", action = "Index" }
);
Now when you navigate to http://example.com/500 it will be the Index action of ErrorController that will get executed and which will render the 500.aspx view.

How to set this Area up in my ASP.NET MVC Application

I'm trying to setup an Area Route in my ASP.NET MVC application.
I'm also using the nuget package AttributeRouting, not the normal MVC register area routes thingy.
From my understanding, area routes look like this : /area/controller/method
What I'm trying to do is :- /api/search/index
which means:
Area => Api
Controller => SearchController
ActionMethod => Index
.
[RouteArea("Api")]
public class SearchController : Controller
{
[POST("Index")]
public JsonResult Index(IndexInputModel indexInputModel) { .. }
}
But that doesn't create that route. This is what it creates: /api/index
The search controller is missing.
I've had a look the docs and noticed the RoutePrefix so I tried this..
[RouteArea("Api")]
[RoutePrefix("Search")]
public class SearchController : Controller
{
[POST("Index")]
public JsonResult Index(IndexInputModel indexInputModel) { .. }
}
and that actually creates the route /api/search/index.
But why do i need to put the RoutePrefix in there? Shouldn't it be smart enough to already figure out that this is a SearchController and create the 3-segment route?
You don't need to put a RoutePrefix anywhere. It's just there as a refactoring/DRY aid. Consider:
[RouteArea("Api")]
public class SearchController : Controller
{
[POST("Search/Index")]
public ActionResult Index() { }
}
If you had a number of actions, maybe you want them all with the "Search" prefix, so you'd do:
[RouteArea("Api")]
[RoutePrefix("Search")]
public class SearchController : Controller
{
[POST("Index")]
public ActionResult Index() { }
// Other actions to prefix....
}
Shouldn't it be smart enough?
Not to be cheeky, but no. AR was never intended to read all your code for you and magically generate routes. It was intended to keep your URLs top of mind, and to do that you should see your URLs. Not that this is the best or only way of doing things, just that was my intent from the get.
The real reason why it isn't smart enough is that the concept of "Area" has nothing to do with URL. An area is a logical unit. You could expose that logical unit without any route prefix (so it would be hanging off ~/) or you could expose it off "This/Is/A/Prefix".
However, if you want it to be smart enough.... I just released v3.4, which will let you do this (if you want to; don't have to):
namespace Krome.Web.Areas.Api
{
[RouteArea]
[RoutePrefix]
public class SearchController : Controller
{
[POST]
public ActionResult Index() { }
}
}
This will yield the following route: ~/Api/Search/Index. The area comes from the last section of the controller's namespace; the route prefix comes from the controller name; and the rest of the url comes from the action name.
One more thing
If you want to get out a route area url and route prefix rat's nest for individual actions in a controller, do this:
[RouteArea("Api")]
[RoutePrefix("Search")]
public class SearchController : Controller
{
[POST("Index")]
public ActionResult Index() { }
[GET("Something")] // yields ~/Api/Search/Something
[GET("NoPrefix", IgnoreRoutePrefix = true)] // yields ~/Api/NoPrefix
[GET("NoAreaUrl", IgnoreAreaUrl = true)] // yields ~/Search/NoAreaUrl
[GET("Absolutely-Pure", IsAbsoluteUrl = true)] // yields ~/Absolutely-Pure
public ActionResult Something() {}
}

Categories

Resources