Having some trouble with some routes. I don't fully understand the MVC routing system so bear with me.
I've got two controllers, Products and Home (with more to come!).
I want to have the views within the Home controller accessible without having to type Home in the url. Essentially I want to turn www.example.com/home/about into www.example.com/about, however I still want to preserve the www.example.com/products.
Here's what I have so far.
routes.MapRoute( "Home", "{action}", new { controller = "Home" } );
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "home", action = "index", id = UrlParameter.Optional }
);
Now depending on which one is first I can get either one or the other to work, but not both.
I think what you might be looking for is something that that the author of the code below has termed a Root Controller. I have used this myself on a couple sites, and it really makes for nice URLS, while not requiring you to create more controllers that you'd like to, or end up with duplicate URLs.
This route is in Global.asax:
// Root Controller Based on: ASP.NET MVC root url’s with generic routing Posted by William on Sep 19, 2009
// http://www.wduffy.co.uk/blog/aspnet-mvc-root-urls-with-generic-routing/
routes.MapRoute(
"Root",
"{action}/{id}",
new { controller = "Root", action = "Index", id = UrlParameter.Optional },
new { IsRootAction = new IsRootActionConstraint() } // Route Constraint
);
With this defined elsewhere:
public class IsRootActionConstraint : IRouteConstraint
{
private Dictionary<string, Type> _controllers;
public IsRootActionConstraint()
{
_controllers = Assembly
.GetCallingAssembly()
.GetTypes()
.Where(type => type.IsSubclassOf(typeof(Controller)))
.ToDictionary(key => key.Name.Replace("Controller", ""));
}
#region IRouteConstraint Members
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
string action=values["action"] as string;
// Check for controller names
return !_controllers.Keys.Contains(action);
}
#endregion
}
The RootActionContraint alows you to still have other routes, and prevents the RootController actions from hiding any controllers.
You also need to create a controller called Root. This is not a complete implementation. Read the original article here
Have you tried:
routes.MapRoute(
"Home_About",
"About",
new { controller = "Home", action = "About" } );
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "home", action = "index", id = UrlParameter.Optional }
);
Products should still be handled by the Default route, while the first one can handle your About route.
Related
I am working on a ASP.Net MVC project. I have a particular controller action that accepts a date value in the form yyyy/mm/dd. So the URL becomes
http://localhost/MyProject/PublicReview/GetReviews/2012/10/29.
where GetReviews is an action and 2012/10/29 the parameter.
My RouteConfig is as follows:
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 });
}
How should I change the routevalues ? What should be the order of MapRoute values?
I've not tested this, but I'm guessing this will work in your scenario:
routes.MapRoute(
"Reviews", "PublicReview/GetReviews/{year}/{month}/{day}"
{ controller = "PublicReview", action = "GetReviews" };
Note that this will need your GetReviews method to have three properties of "year", "month" and "day". You'll then have to parse them into a DateTime.
Taken from http://www.asp.net/mvc/tutorials/controllers-and-routing/creating-custom-routes-cs which uses "-" for date separators.
As it is not, I have a site where you must come in on a single url and a cookie is set to track which customer you are affiliated with. I want to change this so that certain controllers only use a url like this:
/{friendlyName}/{controller}/{index}/{id}
that friendly name is unique and lets me select the correct customer without using the cookie kludge.
I have controllers: Home, Redirect that I do not want the friendly name part of (and possibly more).
I have a few others that fit this category that I would like to move into their own areas. How can I not include the areas as valid friendly names? For instance, I have a controller that services up content in an iframe called Framed. currently, a url for this looks like /Framed/action/id. I could put this in an area called Framed with a controller the same name as the action, and I should still be able to maintain the same url.
For the controller Error I want the friendly name to be optional
I have other controllers that I want the friendly name to be required: SignIn, SignOut, Account
Once I have the routing, the problem is altering the code so that my redirects maintain the friendlyurl. Any ideas on how to do that?
My problem is just coming up with a good plan of attack on how to change the routing of my site. I must maintain backwards compatibility of some of the urls - namely anything I don't want the friendly url part of, including the controllers I discussed slitting into their own areas. I'm looking for any good suggests on how to lay this out and go about altering the changes.
To accomplish your objectives, you will need a combination of routes and RouteConstraints. Also, you will need to enforce rules that a friendlyName is unique, and is different from the names of any controllers or areas.
The following routes should be sufficient in RegisterRoutes() in Global.asax.cs:
routes.MapRoute(
"WithFriendlyName",
"{friendlyName}/{controller}/{index}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { friendlyName = new MustBeFriendlyName() }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = new MustNotRequireFriendlyName() }
);
The RouteConstraints should look something like this:
using System;
using System.Web;
using System.Web.Routing;
namespace Examples.Extensions
{
public class MustBeFriendlyName : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return true if this is a valid friendlyName
// MUST BE CERTAIN friendlyName DOES NOT MATCH ANY
// CONTROLLER NAMES OR AREA NAMES
var _db = new DbContext();
return _db.FriendlyNames.FirstOrDefault(x => x.FriendlyName.ToLowerInvariant() ==
values[parameterName].ToString().ToLowerInvariant()) != null;
}
}
public class MustNotRequireFriendlyName : IRouteConstraint
{
private const string controllersRequiringFriendlyNames =
"SignIn~SignOut~Account";
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return true if this controller does NOT require a friendlyName
return controllersRequiringFriendlyNames.ToLowerInvariant()
.Contains(values[parameterName].ToString().ToLowerInvariant());
}
}
}
This should get you started.
As far as the URLs generated by your redirects, if the routing is set up correctly, the URL generation should follow, so that the only changes you are likely to need are those to insure {friendlyName} is being passed.
You probably will have to add some additional routes and constraints as you get further into your changes.
Just wanted to add to this, as the optional prefix been biting me for the past couple of days. While I want to use the solution provided by #counsellorben, I also needed to be able to address the routes by the same name, which is impossible when using 2 routes.
It took me some headscratching, but finally the solution actually seemed very simple. I just needed to create an intermediate aggregate route:
public class AggregateRoute : RouteBase
{
private readonly RouteBase[] _routes;
public AggregateRoute(params RouteBase[] routes)
{
_routes = routes;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData routeData = null;
foreach (var route in _routes)
{
routeData = route.GetRouteData(httpContext);
if (routeData != null) break;
}
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData virtualPath = null;
foreach (var route in _routes)
{
virtualPath = route.GetVirtualPath(requestContext, values);
if (virtualPath != null) break;
}
return virtualPath;
}
}
This allows me to do:
routes.Add(
"RouteName",
new AggregateRoute(
new Route("{path}", new MvcRouteHandler()),
new Route("{prefix}/{path}", new MvcRouteHandler())
)
);
Which enables resolving either route by the same name, which is impossible when adding both routes separately:
Url.RouteLink("RouteName", new RouteValueDictionary{
new{path="some-path"}});
Url.RouteLink("RouteName", new RouteValueDictionary{
new{path="some-prefix/some-path"}});
In my HomeController I'm trying to get information using Request.QueryString
string aa = Request.QueryString["aa"];
string bb = Request.QueryString["bb"];
So In the address bar I am expecting something like:
< something >?aa=12345&bb=67890
I created a new route:
routes.MapRoute(
"Receive",
"Receive",
new { controller = "Home", action = "Index" }
);
And I'm trying to use it in this way:
http://localhost:54321/Receive?aa=12345&bb=67890
But I'm getting the following error:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Receive
I think your routing is goofed which is why you are getting a 404. Please look at some tutorials, specifically here: asp.net/mvc/tutorials/asp-net-mvc-routing-overview-cs
Also, like #YuriyFaktorovich says, you really shouldn't be using Request.QueryString, but rather passing those as parameters to your action method
Example in VB:
Function Retrieve(ByVal aa as String, ByVal bb as String) as ActionResult
You can access the Query String values in 2 ways...
grab the values in the controller initialization
use the values in your action
specifying the route with those variables
1 - grab the values in the controller initialization
protected override void Initialize(RequestContext requestContext) {
// you can access and assign here what you need and it will be fired
// for every time he controller is initialized / call
string aa = requestContext.HttpContext.Request.QueryString["aa"],
bb = requestContext.HttpContext.Request.QueryString["bb"];
base.Initialize(requestContext);
}
2 - use the values in your action
public void ActionResult Index(string aa, string bb) {
// use the variables aa and bb,
// they are the routing values for the keys aa and bb
}
3 - specifying the route with those variables
routes.MapRoute(
"Receive",
"Receive/{aa}/{bb}",
new {
controller = "Home",
action = "Index",
aa = UrlParameter.Optional,
bb = UrlParameter.Optional }
);
Use "Receive/" for the url in the route, and don't use Request.Querystring.
You can modify your action to be
public ActionResult Index(string aa, string bb) {...}
The ASP.Net MVC framework will hydrate those items for you.
Your HTTP 404 error is because your new route is very likely in the wrong place. Make sure your new route is before the default route:
routes.MapRoute(
"Receive",
"Receive",
new { controller = "Home", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I have the following ActionMethod in UserController
public ActionResult Index(string id, string name, int? org)
When I navigate to > http://example.com/User , the above action method is invoked. Thats good.
However when I navigate to > http://example.com/User/1 , it can't find the resource. Shouldn't it navigate to the above action method with id = 1 and the rest as null ?
Routing in Global.asax:
context.MapRoute(
"Default",
"/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
You will have to add those other parameters into your routing as well for them to ever get populated.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}/{name}/{org}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, name = UrlParameter.Optional, org = UrlParameter.Optional } // Parameter defaults
);
You can then navigate to http://yourdomain/User/Index/1
As name and org are optional you can also pass these in when you want
http://yourdomain/User/Index/1/fred
http://yourdomain/User/Index/1/fred/44
You should navigate to http://mysite.com/User/Index/1 instead of http://mysite.com/User/1
I have the following route ( it's the first in my global.asax )
routes.MapRoute(
"AdminCompany", // Route name
"{controller}.aspx/{action}/{companyId}/{id}", // URL with parameters
new { controller = "Home", action = "Index", companyId = "", id = "" } // Parameter defaults
);
if i navigation to
"Order/DisplayAdmin/2/79000180" it resolves correctly
However if i do the following
Html.ActionLink("View", "DisplayAdmin", new {companyId = Model.CompanyId, id = order.OrderNumber }, new { #class = "button add" })
it displays
/Order.aspx/DisplayAdmin/39068760?companyId=0
which also works, but isn't so pretty :)
Here is my Controller Method
public ActionResult DisplayAdmin(int companyId, [DefaultValue(0)]int id, [DefaultValue(0)] int orderItemStatusId)
{
var viewModel = DisplayAdminViewModel(companyId, id, _statusResponses);
return View(viewModel);
}
Am i calling ActionLink the wrong way? how do i get the nice Urls?
only thing I can think of that is happening is that its falling back to the Default route, I did a copy paste of both your route and the html.ActionLink and it works perfectly for me displaying it like "/Order.aspx/DisplayAdmin/39068760/45456", i did replicate the same fault like you get if the naming isn't the same in the route and action link.
Use the overload of ActionLink that has a RouteValueDictionary argument.
It appears that you are currently using the overload with the "object" argument and it's going to a workable, but not as clean, url.