So I am creating a site with two bindings
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
name: "DefaultAdmin",
url: "Admin/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
The idea is I want to have EndUsers to access the normal pages (/account/login etc etc ) but have a admin only portion of the site (with a different layout) for admin users.
The question is kinda two fold :-
In terms of controllers it looks like MVC just looks in the Controllers folder, is there a way to seperate out AdminControllers and Regular controllers to keep things organised?
I would like to have a separate "master view" appear for the admin than the regular, currently I'm just using _layout.cshtml from _start.cshtml but I'd like to be able to use _layout.cshtml and _adminLayout.cshtml without prefixing every view with the name of the view (if not then I can live with this one easily enough).
Any help would be apprechited.
You can place controllers anywhere in the assembly as long as they are derived from System.Web.Mvc.Controller class they will be identified by the routing logic.
You could look at "Areas" in MVC to help out with your specific need.
It sounds like Areas in MVC is what you are looking for.
Related
I have two files called FilmController. One in the controller folder which displays my database data, and one in a folder called API which allows users to view data in json format.
My question is, in my nav bar i have an action link item that linked to the controller folder film file before i created the API one. Now it doesn't no which one to target. Is there anyway to target a specific one.
<li>#Html.ActionLink("Films", "Index", "Film")</li>
I want this to direct to the controller/film file.
You cannot use the ActionLink helper to target a specific Controller class.
However you can create a second route definition in your RouteConfig.cs. Let this route point to another namespace. Then put your API code in this namespace:
routes.MapRoute(
"API",
"api/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "MyMvcApp.Api" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
For those who have similar issue having two controllers with the same name in different folder, consider using Areas in your ASP NET MVC project and put each of these controllers to a different area. Then if you use #Html.ActionLink("Name", "Action", "Controller") in your view it will always choose the controller based on the area you are in, and if you want to have a link to a controller from another area, you can use #Html.ActionLink("Name", "Action", "Controller", new { area = "AreaName" }, null).
Sounds like you are using a standard controller as an api controler. One should be of type Controller, and the other ApiController, then they can both exist with the same name. #Html.ActionLink will only route to Controllers.
public class FilmController : ApiController
I have been trying to give options to users like Facebook to add their company name in the URL:
http://localhost:50753/MyCompany/Login
I have tried different URLs, but it didn't work.
routes.MapRoute(
name: "Default",
url: "{companyName}/{controller}/{action}",
defaults: new { controller = "Login", action = "Index"}
);
routes.MapRoute(
name: "Login",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional }
);
Now when I add this route to get it work, all of my AJAX requests start fails and those that succeed represent HTML rather than JSON. What I have noticed is that because of this route, my page gets reload again.
Can someone help me figure out how can it be done using MVC routing (if it's possible, or if I'm thinking in the wrong way)?
The problem you are having is due to the fact that both of these routes will match all URLs that have 1, 2, or 3 segments defined (because the controller and action have default values). Since routes are executed in order from the top route to the bottom route, your top route will always match and your bottom route never will match (except for the home page).
Since the top route always matches, URLs that assume that the first segment is the controller and the second segment is the action will fail because you are putting these values into the companyName and controller route keys, respectively.
For this to work as you expect, you need to make a route constraint that is aware of all of the company names.
routes.MapRoute(
name: "Default",
url: "{companyName}/{controller}/{action}",
defaults: new { controller = "Login", action = "Index"},
constraints: new { companyName = "Company1|Comany2|Company3" }
);
Note that you could implement IRouteConstraint so you could pull the values to match from a cached database model instead of hard-coding them into the configuration. See this post to learn how to create a custom route constraint.
Or, as Andy mentioned, you can make the match unique by specifying 1 or more segments of the URL explicitly.
url: "{companyName}/Login"
The idea is there must be some way to make the first route you defined not match in certain cases.
Alternatively, you could implement RouteBase, but you would only need to if you require much more control over the matching process than this simple scenario.
The problem is there is no way to distinguish between your two routes. For example /a/b/c could be the Default route with company = a, controller = b, action = c or it could be the Login route with controller = a, action = b, id = c.
To solve this you'll need to design your routes, including the ones for AJAX, so that there is no way two routes could have the same URL. In your example you could just drop the /{id} from the login route as it isn't needed. Also specify the URL more specifically and put it before the default. This would give you something like
routes.MapRoute(
name: "Login",
url: "{companyName}/Login",
defaults: new { controller = "Login", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{companyName}/{controller}/{action}",
defaults: new { controller = "Login", action = "Index"}
);
In this case both /MyCompany/Login and /MyCompany/Login/Index would go to the login page. However MyCompany/Home/Index would go to controller = Home, action = Index.
Personally, I tend to remove the default route altogether so I can specify the URLs I want rather than have them all as /controller/action. That gives you more control but does mean specifying each route individually.
I created an MVCApplication and it works in a directory on my server like this:
http://www.mywebsite.com/MyApp/
When I use RedirectToAction like this;
return RedirectToAction("Index", "Home");
It goes to;
http://www.mywebsite.com/Home/Index
But I want to redirect to;
http://www.mywebsite.com/MyApp/Home/Index);
How can I resolve it?
Edit---
My routeconfig is like this;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
These redirects follow your route configuration. The default route is {siteUrl}/{controller}/{action}, which is why when you give it the "Index" action and the "Home" controller, you get the URL you're seeing.
You need to specify a new route similar to this:
MapRoute("CustomRoute", "MyApp/{controller}/{action}";
You can then redirect to it like this:
RedirectToRoute("CustomRoute", new {Controller = "Home", Action = "Index" });
EDIT to clarify for the comments -
It is also possible to solve this using IIS to make MyApp an application. This is the right solution only if MyApp is the only value you ever want at the beginning of your route for this site. If, for example, you sometimes need MyApp/{controller}/{action} and other times you need OtherSubfolder/{controller}/{action}, you need to use the routes as I have outlined above (or use areas) instead of just updating IIS.
I am assuming you want the solution using MVC, so that is what is here, but you should also consider the IIS application if that's the right solution for you.
I m trying to use subfolders within the Controllers folder:
The structure looks like this:
Controllers (Folder)
LoginController.cs
WelcomeController.cs
Settings (Folder)
UsersController.cs
I've several problems.
When I perform a return RedirectToAction("Index", "welcome") from my LoginController, the url looks like http://mywebsite.local/settings/welcome
I thought I will get a 404 error..
How to make the redirection launches http://mywebsite.local/welcome and get a 404 error when I launch http://mywebsite.local/settings/welcome
Do I really need to use Areas?
This is my RouteConfig.cs
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "settings",
url: "settings/{controller}/{action}/{id}",
defaults: new { controller = "Users", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional }
);
Do I really need to use Areas?
No, but you're trying to re-invent the wheel - creating a structure a bit like Areas. I'd recommend you go with Areas, it will make your life easier.
Area Hype:
I wouldn't recommend applying Areas just because you can.
That would be like "giving a man a hammer and suddenly everything's a nail".
In this case we have a cool feature called "Areas" and (without knowing the underlying architecture)
others recommended it the instant someone asks about sub-folders.
Areas weren't designed for the sole purpose of giving you an extra Route Parameter.
The "Areas" original intent is to give you a clear SoC (Separation of Concerns).
For Example:
Say you have two separate web application experiences and have decided to roll them under one Solution.
Your call-center may need to look up detailed information and enter in data on individual customers, while your managers and executives will peruse higher-level reporting and rarely enter in data.
In this scenario it may make sense to split your business logic into "Reporting" and "CallCenter" Areas.
Sub-Directories:
In MVC, the Sub-Folders you use under "Controllers" are Ignored when it comes to Routing.
SubFolders are perfectly fine with how the Questioner is using them to organize his Controllers.
Adding a SubFolder name to his URL makes for a more human-readable URL too.
He just made a mistake in the exclusivity of his first Mapping Route.
The problem is it was matching on everything.
Just because you have "settings/" in your MapRoute doesn't mean it will apply only to incoming URL's.
MVC will use your MapRoute Logic to figure how you would like to write your URL's too!
The Fix (Option 1 - Use MapRoute):
routes.MapRoute(
name: "Settings",
url: "Settings/{controller}/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional },//Remove the Default Controller as we want to explicitly require it and constrain it. - 08/26/2018 - MCR.
constraints: new { controller = "Users|Admins" }//If you have other Controllers under a SubFolder (say we also had AdminsController.cs), then simply Pipe-Delimit them. - 08/26/2018 - MCR.
);
This is the way I've chosen to go, but if you want tighter control, you could use the Option below instead.
The Fix (Option 2 - Use Route-Attributes):
First, make sure you enable this feature by adding routes.MapMvcAttributeRoutes():
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();//Add this BEFORE MapRoute for Route-Attributes to work.
Next, Decorate your Controller with the following Attributes:
(You may even include Default/Optional Values, just like in your MapRoute.)
[RoutePrefix("Settings/Users")]//Add your ParentFolder and Include your Controller name too.
[Route("{action=Index}/{id?}")]//You need this if you are using the RoutePrefix Attribute. Without this, you will need to define "[Route]" above every Action-Method. - 08/26/2018 - MCR.
public class UsersController : Controller
Note:
If you use Route-Attributes instead of MapRoute, then you will not be able to hit the
Controller without the "Settings" Prefix.
With the Custom and Default MapRoutes, you could have accessed your controller either way.
By decorating your Controller with these Attributes, you now force it to only use this exact path.
This may be what you want, but if you start IIS Express from Visual Studio on one of your Views,
then it will not find it because Visual Studio does not know to add the RoutePrefix for you.
I say this, so you are not discouraged when you start debugging and think it doesn't work.
See this link for more information about Attribute-Routing:
https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/
The folder structure of your controllers here has very little relevance to what you are seeing. You could maintain that structure and accomplish your goal if the route would work.
However, you've specified two matching routes that can be used to encode the same action, and it is simply giving the first one priority. See, routes don't just work forwards, MVC uses them in reverse, too, to process your ____Action() statements. You could specify a named route (e.g. "settings") in your RedirectToAction("Index", "welcome"), by using RedirectToRoute instead, or manually specify all the routes that have prefixes in your route table. but why start your project off with intentionally conflicting routes like this?
As Joe R said, Areas will make your life easier. Why? Mainly because there is an additional built-in route parameter to do exactly what you want. Really, the question becomes "why avoid Areas?"
Looks like the answer not answering the question, because IMHO, we use Area when we need mini program under our main program,
ie. Forum, (Comprehensive) Blog, Marketplace (under our main site) either forum.mainsite.com or mainsite.com/forum
So you DONT need Area in your case
Solutions :
FYI, routing are something that have nothing to do with your architecture / structure / foldering in your applications.
In Example your ControllerName is SettingsUsersController
routes.MapRoute(
name: "settings",
url: "settings/users/{action}/{id}",
defaults: new { controller = "SettingsUsers", action = "Index", id = UrlParameter.Optional }
);
in your case, you can fix your routing like this (this is for making sure you have pretty url but still simple Controller structure):
routes.MapRoute(
name: "settings",
url: "settings/{controller}/{action}/{id}",
defaults: new { controller = "SettingsUsers", action = "Index", id = UrlParameter.Optional }
);
Your ControllerName would be SettingsUsersController
SettingsUsersController.cs
public class SettingsUsersController : Controller
{
public ActionResult Index()
{
return View("~/Views/SettingsUsers/Index.cshtml", db.YourDBName.ToList());
}
}
And why 404? I think because you are not "routing" correctly your View, you should do make subfolder like this under your Views Folder Views/SettingsUsers/Index.cshtml
I have created a controller with an Index action. All of my other Actions return the views just fine...but for some reason I have to specify the full url to have the Index view return. It's almost like my Routes aren't working correctly.
For instance, to go to the properties page, you have to go to /Properties/Index instead of just /Properties/. My routes are as follows. Any help would be greatly appreciated!
routes.MapRoute(
name: "Index",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
For security reasons in ASP.NET in general, you can't have a "Properties" path. C# projects all come with a Properties folder by default and ASP will ignore it when accessed directly to prevent file access to it.