I have an api using web api 2 and I am trying to create help docs within an Area so that an incoming request like ...api/people.help will route to the people controller and people view and serve up the html. I am struggling with the route mapping for the area after refactoring the code. Previously, I had routes like this:
public override void RegisterArea(AreaRegistrationContext context) {
context.MapRoute(
name: "Help",
url: "{action}.help",
defaults: new { area = "help", controller = "default" }
);
All the methods were in the default controller and this worked. Now, I need a separate controller for each resource (eg people, schedules etc) but can't get the routes to work. I would appreciate help, I am very new to this. How do I get each help request to map to the controller with the same action name?
Wouldn't it simply be something similar to:
public override RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
name: "Help",
url: "api/{controller}.help",
defaults: new { area = "help" }
);
}
What you have in your post is a default for the name of the controller, in this case, it's name will always be default. Instead, what you're looking for is that when someone routes to your controller name suffixed with .help, it'll route to a path akin to api/help/people, which will end up calling a default action (in MVC) such as index.cshtml or the default action for a GET request to the controller (for WebAPI).
So, you want to set the default area to help as shown above. You also want to set the default action that should execute on the provided controller.
Update: To answer question in comment
For MVC, you can have an action method whose name matches what the controller name will be in the URL:
public class PeopleController : Controller
{
[HttpGet] // Not strictly necessary, but just want to stress this is GET
public ActionResult People()
{
// Do stuff in your action method
}
}
The only problem is, your action method will be different for each controller, and so unknowable for route registration purposes. Therefore, you should maybe have just a default Help action:
public class PeopleController : Controller
{
[HttpGet]
public ActionResult Help()
{
// Do stuff
}
}
Then you can have the following route:
public override RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
name: "Help",
url: "api/{controller}.help",
defaults: new { area = "help", action = "Help" }
}
You could take this one step further and provide a Help method in a custom base controller class:
public class MyBaseController : Controller
{
public virtual ActionResult Help()
{
// Do default behavior stuff, if appropriate
}
// If you don't have any "default" behavior, you could make the method abstract:
// public abstract ActionResult Help();
}
public class PeopleController : MyBaseController
{
public override ActionResult Help()
{
// Do stuff.
}
}
Update to further answer OP's question in comments
So, now the OP is saying: "but I want my view to have the same name as my controller." Ok, that should be no problem:
public class PeopleController : MyBaseController // if you're using a base class
{
public override ActionResult Help()
{
return ViewResult("People");
}
}
So, you can have a view with any name you want. But if the view's name differs from the name of the action method, then when returning (say) a ViewResult, you'll need to specify the name of the view to return.
Having said all that, the default folder structure for views in ASP.Net is Areas/{AreaName}/Views/{Controller}/{viewname}.{cs|vb}html. And here, {viewname} is by default assumed to be the action method name, but doesn't have to be when, as above, explicitly telling MVC which view to return (in the example above, People.cshtml).
HTH.
Related
I know how to send data to controller's action method as parameter from URL. Here I wonder how can I send data from URL to controller's field?
public MyAwesomeController : Controller {
public string SectionCode { get;set; }
}
and let's define Routes :
routes.MapRoute(
name : "AwesomeRouter",
url : "{code}/{action}",
defaults: new {controller = "MyAwesome", action = "Index", /* What to do here?*/}
);
I want SectionCode be filled with the {code} from URL. Is it possible to implement?
Yes it is, you can create inherited class from basic Controller class and override OnActionExecuting method where you can read url, route or any form data and store them in session or directly fill any field you need. Then create an inherited class of your controller.
public class MyAwesomeController : MyControllerBase
{
public ActionResult Index()
{
//this.SectionCode is available populated here
return View();
}
}
public class MyControllerBase : Controller
{
public string SectionCode
{
get;
private set;
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
this.SectionCode = filterContext.RequestContext.RouteData.Values["code"].ToString();
base.OnActionExecuting(filterContext);
}
}
Each time you hit any action in this controller using route definition you provided, field will be automatically populated. But when you will have more than one route defined they it could get easily into conflicts eg. when code will match to any controller name. Normal website should not work this way.
Your route should look like this:
routes.MapRoute(
name: "AwesomeRouter",
url: "{code}/{action}",
defaults: new { controller = "MyAwesome", action = "Index" }
);
The code should then be passed on to the action as a parameter. I am storing it in the view bag for explanation purposes:
public class MyAwesomeController : Controller
{
public ActionResult Index(string code)
{
ViewBag.Code = code;
return View();
}
}
Using this URL then:
http://somehost/4567/Index
If you access the Viewbag property in your view:
#ViewBag.Code
You should see:
4567
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.
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.
I have 2 actions in one controller
public ActionResult DoSomething()
{
...
}
public ActionResult SoSomethingAgain()
{
...
}
I would like to have both requests go to the same action.
perhaps an alias....
[ie. SoSomethingAgain]
public ActionResult DoSomething()
{
...
}
What is the proper approach?
If I'm reading this correctly you can just do:
public ActionResult DoSomething()
{
...
}
public ActionResult SoSomethingAgain()
{
return DoSomething();
}
In SoSomethingAgain just do this:
return DoSomething();
The only other option you have would be to build a specific Route for that controller when you setup routes at the start of the application. That's going to be a lot more work than it's worth.
If SoSomethingAgain is the called action then the two earlier answers will run the code that is inside DoSomething, but the controller action and context is still SoSomethingAgain. This means that a return View() statement inside the DoSomething would look for a SoSomethingAgain view.
Similarly the pipeline would use filters defined on SoSomethingAgain, and not those on DoSomething. You can see this if you put an [Authorize] filter on DoSomething. If you hit the DoSomething action you will be prompted to log in, but if you hit the SoSomethingElse action you will not be prompted.
Maybe that's what you want, maybe not. If it isn't and you want to have both a DoSomething url and a SoSomethingElse url, but both running the same code, then get rid of the SoSomethingElse controller action, and add a custom route (before the default route).
routes.MapRoute(
name: "SoSomethingAgainRoute",
url: "{controller}/SoSomethingAgain/{id}",
defaults: new { controller = "Home", action = "DoSomething", id = UrlParameter.Optional }
);
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() {}
}