How to set up routing so that Index does show? - c#

So I know google can penalize a site if you have the same content on multiple urls... unfortunately, in MVC this is too common i can have example.com/, example.com/Home/ and example.com/Home/Index and all three urls would take me to the same page... so how do I make sure that whenever Index is in the url, that it redirects to the same without the Index and of course the same thing with the Home

Perhaps this little library may be useful for you.
This library is not very convinient in your case, but it should work.
var route = routes.MapRoute(name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.Redirect(r => r.MapRoute("home_index", "/home/index")).To(route);
routes.Redirect(r => r.MapRoute("home", "/home")).To(route);

The way I handle this is for default pages like Index is to simply create an explicit route for only one of them. I.e. "example.com/People" would be the route for People/Index, and there would be no valid page at the url "/example.com/People/Index".
The Home example is unique in that it has potentially three different URLs. Again in this case I'd simply create a route for "example.com" for that Index action, and not support the other two urls. In other words, you would never link to the other forms of the URL, so their absence should never cause a problem.
We use a Nuget package called AttributeRouting to support this. When you specifiy a GET route for a page, it overrides the defaults for MVC.
Using AttributeRouting usually you'd map the index to [GET("")] but for the special case of Home where you also want to also support the root URL that omits the controller name , I think you'd also add an additional attribute with IsAbsoluteUrl:
public class HomeController : BaseController
{
[GET("")]
[GET("", IsAbsoluteUrl = true)]
public ActionResult Index()
{...

So I found a way to do it without any external Library...
In my RouteConfig I had to add these two routes at the top, just below the IgnoreRoute
routes.MapRoute(
"Root",
"Home/",
new { controller = "Redirect", action = "Home" }
);
routes.MapRoute(
"Index",
"{action}/Index",
new { controller = "Redirect", action = "Home" }
);
Then I had to create a new Controller called Redirect and I created a method for each of my other Controllers like this:
public class RedirectController : Controller
{
public ActionResult Home()
{
return RedirectPermanent("~/");
}
public ActionResult News()
{
return RedirectPermanent("~/News/");
}
public ActionResult ContactUs()
{
return RedirectPermanent("~/ContactUs/");
}
// A method for each of my Controllers
}
That's it, now my site looks legit. No more Home, no more Index in my URLs, this of course has the limitation of not being able to accept parameters to any of the Index methods of your Controllers though if it was really necessary, you should be able to tweak this to achieve what you want.
Just an FYI, if you want to pass an argument to your Index Action, then you can add a third route like this:
routes.MapRoute(
name: "ContactUs",
url: "ContactUs/{id}/{action}",
defaults: new { controller = "ContactUs", action = "Index", id = UrlParameter.Optional }
);
This will create a URL like this: /ContactUs/14

Related

ASP.NET MVC enum argument in controller mapping

ASP.NET MVC provides simple templates for controller methods such as Details, and can have something like:
public ActionResult Details(int id)
{
// do something
}
This can be accessed by: http://localhost:port/Controller/Details/id
What I'm trying to do is instead provide a different type like:
public enum MyEnum
{
All,
Pending,
Complete
}
And then I setup my controller method like:
public ActionResult MyMethod(MyEnum myEnum = MyEnum.Pending)
{
// do something
}
This works fine for: http://localhost:port/Controller/MyMethod/ because it uses the default argument.
To specify a different argument I have to do http://localhost:port/Controller/MyMethod?myEnum=All and that works.
I'm wondering, is it possible for me to be able to do http://localhost:port/Controller/MyMethod/All instead of using ?myEnum=All?
Upon trying to do it that way I get a 404 exception which is understandable, but why doesn't this happen for id in Details?
Can I change the MapRoute which is currently: url: "{controller}/{action}/{id}" to allow me to achieve it with my own type?
What I've tried so far:
I only want this route enforcement for one of my schemes such as http://localhost:port/Controller/MyMethod/{ViewType}, I tried this but it doesn't seem to do anything:
routes.MapRoute(
"MyRoute",
"MyController/Index/{MyEnum}",
new { controller = "MyController", action = "Pending" }
);
/Controller/MyMethod/All will actually work. The problem is with the default route, which will consider All to be the id route parameter, which doesn't line up with what your action is using as a parameter. It would actually work fine if your action signature was:
public ActionResult MyMethod(MyEnum id = MyEnum.Pending)
Since it will then bind All to the right thing.
You could add another route for this use-case, but you'll need to be careful that you don't just create another "default" route, which will take over. In other words, you'll have to fix part of the URL:
routes.MapRoute(
"MyCustomRoute",
"Controller/{action}/{myEnum}",
new { controller = "Controller", action = "MyMethod", myEnum = MyEnum.Pending }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Then, by the mere presence of the /Controller/ prefix to the route, it will use your custom route instead, and fill in All for the myEnum param, rather than hitting the default route and try to fill in id.
However, be advised that when using enums as route params, they must be exact matches. So, while /Controller/MyMethod/All will work, /Controller/MyMethod/all will not. To get around this, you'll have to create a custom model binder. I did a quick search and found the following article which may help you in that regard.
You can indeed. Do not change the default route "{controller}/{action}/{id}", but rather add one before the default. This new one needs to be fairly specific:
routes.MapRoute(
"EnumRoute",
"Controller/MyMethod/{myEnum}",
new { controller = "Controller", action = "MyMethod", myEnum = UrlParameter.Optional }
);
What that basically says is "when you see request to literally Controller/MyMethod/whatever, use this controller and that method and pass whatever as parameter of the request". Note that actual controller does not necessary have to be what route says in the url, although it is a good idea to stick to that.

How to distinguish between two routes: one for parameterless action and one for the index action with one parameter?

I have a controller with multiple actions and I set up the following routes:
routes.MapRoute(
name: "MyCustomRoute",
url: "MyTarget/{option}",
defaults: new { controller = "MyTarget", action = "Index", option = "" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The main idea here is to call the Index action of the MyTarget controller as default, passing only the argument in the URL.
The lightweight controller looks like this:
public class MyTargetController : Controller
{
public ActionResult Index(string option)
{ ... }
public ActionResult FirstAction()
{ ... }
public ActionResult SecondAction(param list)
{ ... }
}
The MyCustomRoute is set to map MyWebsite/MyTarget/randomOption to the Index action, passing randomOption as the option parameter. The problem is that this route catches all the other actions too: MyWebsite/MyTarget/FirstAction and MyWebsite/MyTarget/SecondAction (ignore the lack of parameters) are mapped to the Index action and their names are routed as the option parameter.
I don't want to change the URL into something like MyWebsite/MyTarget/Index/randomOption. Is there a clear way for distinguishing between a default action with one parameter and other actions, which may or may not have parameter?
EDIT: the following workarounds can be implemented, balancing the advantages and disadvantages:
all actions except Index can be moved to a helper controller: creates two separate controllers processing the same logic;
a custom route constraint can be created, that checks if the parameter value corresponds to the name of an existing action (except Index): needs a way of storing the names of the actions and needs the list to be updated every time a new action is added to the controller (Reflection might be a better approach).
None of the above workarounds seam to be elegant and without any "special" caring.

Using RouteLink with Areas

In my MVC application I have a default route defined:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new []{ "Demo.Controllers" }
);
I created a new Area called Admin and it added a route to in the AdminAreaRegistration class:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", controller = "Home", id = UrlParameter.Optional }
);
}
In my main _Layout file I tried to do the following:
#Html.RouteLink("Admin", "Admin_default")
It only works in certain cases (such as if I'm already in an Admin page). If I am in the /Home/About section of my site, then the URL gets generated like so:
/Admin/Home/About
If I am in my Index action of the Home controller (in the main area, not admin) then the URL gets generated like so:
/Admin
Why doesn't RouteLink work like I think it should using Areas in MVC?
#Html.RouteLink("Admin", "Admin_default") this route uses the route with the name of Admin_default so it will always use that route to generate the url.
#Html.RouteLink("Admin", "Admin_default", new { controller = "Home", action = "Index" })
When you don't specify stuff like route values most of MVC uses the values that currently exist. In this case since the action and controller values are null it looks at the RouteValues and checks to see if they're there and if they're found they use the values found there instead.
This is sometimes helpful. For instance if you want {id} to be populated with the same value that was used on the page.
Url: /home/edit/1701
#Html.RouteLink("Refresh", "Default") would result in the same exact url - you can specify overrides if you want.
I would also suggest not naming your routes. You can force yourself to do this by passing null to the route name arg when creating it.
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(null,
"Admin/{controller}/{action}/{id}",
new { action = "Index", controller = "Home", id = UrlParameter.Optional }
);
}
You can then use RouteLink like so:
#Html.RouteLink("Admin",
new { controller = "Home", action = "Index", area = "Admin" })
Reply to comment
Wish I could say I came up with the idea. Learned this from Steve Sanderson's MVC book. There are a ton of method overloads that take a route name as a parameter (RouteLink, but also RedirectToRoute and others). Not using route names forces you to use other overloads of those methods, which IMO are more intuitive and easier to "get right".
Naming your routes is not recommended since it creates dependencies in your view towards the existing routes. I suggest using a null value

Home Pages in ASP.NET MVC

I'm trying out ASP.NET MVC, but, after reading a huge tutorial, I'm slightly confused. I understand how Controllers have Actions that URLs are routed to, but how do home pages work? Is the home page its own controller (e.g. "Home") that has no actions? This sounds correct, but how is it functionality implemented without Actions (no Actions means no methods that call the View Engine)?
In other words, my question is this: how are home pages implemented (in terms of Controllers and Views)? Could you please provide sample code?
"Home" page is nothing more than arbitrary Action in a specific Controller which returns a certain View
To set the "Home", page, or better worded, the default page, you need to change the routing info in the Global.asax.cs file:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "NotHome", action = "NotIndex", id = "" } // Parameter defaults
);
Notice the route definition:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "NotHome", action = "NotIndex", id = "" } // Parameter defaults
);
This route is a "catch-all" route, meaning it will take any URL and break it down to a specific controller and action and id. If none or one of the routes are defined, it will use the defaults:
new { controller = "NotHome", action = "NotIndex", id = "" }
This says "If someone visits my application, but didn't specify the controller or action, I'm going to redirect them to the NotIndex action of my NotHome controller". I purposly put "Not" to illustrate that naming conventions of "Default.aspx", "Index.html" don't apply to MVC routes.
The home page would usually equate to the default action/view on the default controller.
So you'd create, for example, a HomeController with an Index action and a corresponding view, then in your route mappings you'd create a default, catch-all route, something like this:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" });
It depends on what you mean by "home page". If you mean the page seen when you go to http://www.yoursite.com (with no page or Controller name) then that is the Index controller, which works like any other except you don't see the name of the controller in the URL.

ASP.Net MVC route mapping

I'm new to MVC (and ASP.Net routing). I'm trying to map *.aspx to a controller called PageController.
routes.MapRoute(
"Page",
"{name}.aspx",
new { controller = "Page", action = "Index", id = "" }
);
Wouldn't the code above map *.aspx to PageController? When I run this and type in any .aspx page I get the following error:
The controller for path '/Page.aspx' could not be found or it does not implement the IController interface.
Parameter name: controllerType
Is there something I'm not doing here?
I just answered my own question. I had
the routes backwards (Default was
above page).
Yeah, you have to put all custom routes above the Default route.
So this brings up the next question...
how does the "Default" route match (I
assume they use regular expressions
here) the "Page" route?
The Default route matches based on what we call Convention over Configuration. Scott Guthrie explains it well in his first blog post on ASP.NET MVC. I recommend that you read through it and also his other posts. Keep in mind that these were posted based on the first CTP and the framework has changed. You can also find web cast on ASP.NET MVC on the asp.net site by Scott Hanselman.
http://weblogs.asp.net/scottgu/archive/2007/11/13/asp-net-mvc-framework-part-1.aspx
http://www.asp.net/MVC/
I just answered my own question. I had the routes backwards (Default was above page). Below is the correct order. So this brings up the next question... how does the "Default" route match (I assume they use regular expressions here) the "Page" route?
routes.MapRoute(
"Page",
"{Name}.aspx",
new { controller = "Page", action = "Display", id = "" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
On one of Rob Conery's MVC Storefront screencasts, he encounters this exact issue. It's at around the 23 minute mark if you're interested.
Not sure how your controller looks, the error seems to be pointing to the fact that it can't find the controller. Did you inherit off of Controller after creating the PageController class? Is the PageController located in the Controllers directory?
Here is my route in the Global.asax.cs
routes.MapRoute(
"Page",
"{Page}.aspx",
new { controller = "Page", action = "Index", id = "" }
);
Here is my controller, which is located in the Controllers folder:
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class PageController : Controller
{
public void Index()
{
Response.Write("Page.aspx content.");
}
}
}
public class AspxRouteConstraint : IRouteConstraint
{
#region IRouteConstraint Members
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return values["aspx"].ToString().EndsWith(".aspx");
}
#endregion
}
register the route for all aspx
routes.MapRoute("all",
"{*aspx}",//catch all url
new { Controller = "Page", Action = "index" },
new AspxRouteConstraint() //return true when the url is end with ".aspx"
);
And you can test the routes by MvcRouteVisualizer

Categories

Resources