Routing in ASP.NET MVC, showing username in URL - c#

I'm trying to make a route so I can show the username in the URL like this:
http://localhost1234/john
Here Is my routeconfig:
routes.MapRoute(
name: "users", // Route name
url: "{username}", // URL with parameters
defaults: new { controller = "Home", action = "Index", username = "" } // Parameter defaults
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here is my HomeController:
public ActionResult Index(string username = "Test")
{
return View();
}
First of all, the URL Is not changed. When I set username = "Test" inside my route-config, the URL is not changed.
Second, I can't navigate to my other controllers. If I change the URL to http://localhost123/Welcome, nothing happens. It should redirect me to a new page.
What am I doing wrong here?
If I change the order of the routes, I can navigate to other pages, but the username Is not displayed In the URL.
I have googled and all of the answers on this subject says that I should use a route like the one above.

On its own, your routing will not work because if the url was .../Product meaning that you wanted to navigate to the Index() method of ProductController, it would match your first route (and assume "Product" is the username. You need to add a route constraint to your roue definitions that returns true if the username is valid and false if not (in which case it will try the following routes to find a match).
Assuming you have a UserController with the following methods
// match http://..../Bryan
public ActionResult Index(string username)
{
// displays the home page for a user
}
// match http://..../Bryan/Photos
public ActionResult Photos(string username)
{
// displays a users photos
}
Then you route definitions need to be
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "User",
url: "{username}",
defaults: new { controller = "User", action = "Index" },
constraints: new { username = new UserNameConstraint() }
);
routes.MapRoute(
name: "UserPhotos",
url: "{username}/Photos",
defaults: new { controller = "User", action = "Photos" },
constraints: new { username = new UserNameConstraint() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Test", action = "Index", id = UrlParameter.Optional }
);
}
public class UserNameConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
List<string> users = new List<string>() { "Bryan", "Stephen" };
// Get the username from the url
var username = values["username"].ToString().ToLower();
// Check for a match (assumes case insensitive)
return users.Any(x => x.ToLower() == username);
}
}
}
If the url is .../Bryan, it will match the User route and you will execute the Index() method in UserController (and the value of username will be "Bryan")
If the url is .../Stephen/Photos, it will match the UserPhotos route and you will execute the Photos() method in UserController (and the value of username will be "Stephen")
If the url is .../Product/Details/4, then the route constraint will return false for the first 2 route definitions and you will execute the Details() method of ProductController
If the url is .../Peter or .../Peter/Photos and there is no user with username = "Peter" then it will return 404 Not Found
Note that the the sample code above hard codes the users, but in reality you will call a service that returns a collection containing the valid user names. To avoid hitting the database each request, you should consider using MemoryCache to cache the collection. The code would first check if it exists, and if not populate it, then check if the collection contains the username. You would also need to ensure that the cache was invalidated if a new user was added.

You need to categorize the url for different section of your website so that url pattern matching mechanism go smooth. For example in your case put a category 'profile' or anything other. Now your request url look like http://localhost1234/profile/john and route will be
routes.MapRoute(
name: "users", // Route name
url: "Profile/{username}", // URL with parameters
defaults: new { controller = "Home", action = "Index" } // Parameter defaults
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
For more information follow link Routing in MVC

Related

Returning a 404 when the URL does not contain Index

I am having trouble with routing in MVC. I have created a controller for my contact page, but unless I specify the route as /contact/index it will return a 404. I cannot see why it can't find the View with just /contact in the URL. My RouteConfig looks fine to me.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "T",
url: "T/{action}",
defaults: new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "Holding",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Holding", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The only reason I can see it not finding its View is because of the new Route I have configured to display a site holding page. Interestingly /t does display the 'demo' homepage, so I can't see why it doesn't like just /contact.
This S.O article told me that I could fix the problem by giving it its own MapRoute but I shouldn't have to do all that?
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Holding()
{
return View();
}
}
public class ContactController : Controller
{
// GET: Contact
public ActionResult Index()
{
return View();
}
}
It must be something silly, but I can't work it out.
You have route conflicts
/contact would match
routes.MapRoute(
name: "Holding",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Holding", id = UrlParameter.Optional }
);
But since contact controller has no Holding action you will get a 404 Not Found
And since it matched the Holding route it wont go on to the next Default route as first match wins.
The added route is too general so it will get a lot of false matches.
Based on the controllers shown, the added route is not needed. the holding path would still match the default route template. So it can actually be removed altogether.

Since I wrote a route for a specific Action of a Controller, do I need to write a route for all Actions inside the Controller?

I'm writing few routes for my MVC application. I have the following routes for my application:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Pages", action = "Index", id = UrlParameter.Optional }
);
The route above is used when I want to access default values like:
www.servicili.com/budget/edit/1
www.servicili.com/professional/view/234
But, I create the following route for a specific purpose:
routes.MapRoute(
name: "Perfil",
url: "{UsuApelido}",
defaults: new { controller = "Perfil", action = "Index"}
);
the route above, is used to access the URL profile of a "plumber" for example:
www.servicili.com/MarkZuckberg
the profile details are on the controller Perfil and Action Index, however, since I wrote this route, all other actions aren't working.
For example: If I try to access the Index action inside another controller, it redirect to Index of Perfil.
--
The question is: Since I wrote a route for a specific Action of a Controller, do I need to write a route for all Actions inside the Controller?
To solve your problem try like this,
First define constraint,
public class PlumberUrlConstraint: IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var db = new YourDbContext();
if (values[parameterName] != null)
{
var UsuApelido = values[parameterName].ToString();
return db.Plumbers.Any(p => p.Name == UsuApelido);
}
return false;
}
}
Define two routes, put "Default" route at 2nd position
routes.MapRoute(
name: "Perfil",
url: "{*UsuApelido}",
defaults: new { controller = "Perfil", action = "Index"},
constraints: new { UsuApelido = new PlumberUrlConstraint() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Pages", action = "Index", id = UrlParameter.Optional }
);
Now if you have an 'Index' action in 'Perfil' Controller, you can get plumber name like this,
public ActionResult Index(string UsuApelido)
{
//load the content from db with UsuApelido
//display the content with view
}
Hope this help.

Different landing page with MVC 4

I'm using MVC 4 and having problems with the landing page.
I have two kinds of users (let's call the FooUser and BarUser)
Each of the users has it's own landing page:
Foo/Index and Bar/Index
Once user logs in, I can identify whether he is Foo or Bar and redirect him to the relevant page.
But I still have a problem and that is when a user opens the main page. In this case the user doesn't perform a login action (since he is logged in from the previous session) so I can't redirect him to the relevant page.
Is there a way to set conditional defaults? something like:
(Any other ideas are most welcome)
if (IsCurrentUserFooUser()) //Have no idea how to get the current user at this point in the code
{
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Foo", action = "Index", id = UrlParameter.Optional });
}
else
{
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Bar", action = "Index", id = UrlParameter.Optional });
}
It might be worth considering if you really need a new controller for different users. Why not just return a different view and do some logic in the controller. This would be my preferred route as it's less overhead then dynamically calculating routes.
Routes are mapped when the application starts so it won't be able to do conditional ones. You could use a dynamic routes which are processed per request so you can do some logic to see if that route matches.
Note: return null at any point in the dynamic route to cancel it and make it invalid for that request.
public class UserRoute: Route
{
public UserRoute()
: base("{controller}/{action}/{id}", new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
//You have access to HttpContext here as it's part of the request
//so this should be possible using whatever you need to auth the user.
//I.e session etc.
if (httpContext.Current.Session["someSession"] == "something")
{
rd.Values["controller"] = "Foo"; //Controller for this user
rd.Values["action"] = "Index";
}
else
{
rd.Values["controller"] = "Bar"; //Controller for a different user.
rd.Values["action"] = "Index";
}
rd.Values["id"] = rd.Values["id"]; //Pass the Id that came with the request.
return rd;
}
}
This could then be used like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("UserRoute", new UserRoute());
//Default route for other things
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

ASP.NET MVC ActionLink not selecting correct controller

Question background:
I'm trying to pass an variable - in this case an int 'productId' variable' in the url to a controller and action method specified in the ActionLink method.
The issue:
My routes are set as follows:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Login", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "ProductDetailHandler",
url: "{controller}/{action}/{productId}",
defaults: new { controller = "Product", action = "ProductDetail", productId = UrlParameter.Optional }
);
My 'ActionLink' in my 'Products.cshtml' view - which is returned by a controller called 'HomePageController' is set as follows:
#Html.ActionLink("Product", "ProductDetail", new { productId = (ViewBag.data as List<LoginTest.Models.HomePageItem>).First().Id })
The controller that receives the passed 'productId' along with its action method is set as follows:
public class ProductController : Controller
{
public ActionResult ProductDetail(int productId)
{
//logic
return View();
}
}
This is the issue, when looking at the URL it is shown to be redirecting to the 'HomePage' controller:
If someone could tell me why my ActionLink is not going to the Product controller that would be great.
EDIT:
This is the 'Homepage' view that I click a button to redirect me to 'product/productDetail/productId'
My route now just features the 'Default example':
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Login", id = UrlParameter.Optional }
);
The Action Link is now:
#Html.ActionLink("Product", "ProductDetail", "Product", new { id = (ViewBag.data as List<LoginTest.Models.HomePageItem>).First().Id })
The 'Product/ProductDetail' controller and action method now looks like:
public class ProductController : Controller
{
public ActionResult ProductDetail(int id)
{
string hold;
return View();
}
}
This still is giving me the the incorrect URL, as shown, note the 'productId' is now showing as 'length'?
Since the link is on a page rendered by HomePageController the default is to use that controller in the route. You need to use the overload that accepts a controller name
#Html.ActionLink("Your link text", "ProductDetail", "Product", new { id = 1 }, null)
As a side note, your original route table would have created /Product/ProductDetail?productId =1 with this overload because it matches the default route which is the first route in your table (the order of routes is important). In order to have /Product/ProductDetail/1, either reverse the order of the routes or just change the parameter of ProductDetail to int id rather than int productId
Make sure you are using an overload that has controllerName in it, as shown in the following screenshot.
When I remove routeValues: null, it uses a different overlaod which has routeValue as third paramater.
Try this:
routes.MapRoute(
name: "ProductDetailHandler",
url: "Product/{action}/{productId}",
defaults: new { controller = "Product", action = "ProductDetail", productId = UrlParameter.Optional }
);
routes.MapRoute
(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Not sure what you are trying to do with the Login controller. Maybe you can put log-in on your Home page or redirect the Home/Index to Login.
Also you can specify the default namespace if it doesn't find your controller:
routes.MapRoute(
name: "ProductDetailHandler",
url: "Product/{action}/{productId}",
defaults: new { controller = "Product", action = "ProductDetail", productId = UrlParameter.Optional },
new string[] { "MyProject.Controllers" }
);
routes.MapRoute
(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new string[] { "MyProject.Controllers" }
);

Routing to index with id in ASP.NET MVC 4

I'd like to maintain ASP.NET MVC 4's existing controller/action/id routing with default controller = Home and default action = Index, but also enable controller/id to route to the controller's index method as long as the second item is not a known action.
For example, given a controller Home with actions Index and Send:
/Home/Send -> controller's Send method
/Home -> controller's Index method
/Home/Send/xyz -> controller's Send method with id = xyz
/Home/abc -> controller's Index method with id = abc
However, if I define either route first, it hides the other one. How would I do this?
Do the specific one first before the default generic one. The order matters.
routes.MapRoute(name: "single", url: "{controller}/{id}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { id = #"^[0-9]+$" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home",
action = "Index",
id = UrlParameter.Optional }
);
In case, that the list of your actions (e.g. Send) is well known, and their (action) names cannot be the same as some ID value, we can use our custom ConstraintImplementation:
public class MyRouteConstraint : IRouteConstraint
{
public readonly IList<string> KnownActions = new List<string>
{ "Send", "Find", ... }; // explicit action names
public bool Match(System.Web.HttpContextBase httpContext, Route route
, string parameterName, RouteValueDictionary values
, RouteDirection routeDirection)
{
// for now skip the Url generation
if (routeDirection.Equals(RouteDirection.UrlGeneration))
{
return false; // leave it on default
}
// try to find out our parameters
string action = values["action"].ToString();
string id = values["id"].ToString();
// id and action were provided?
var bothProvided = !(string.IsNullOrEmpty(action) || string.IsNullOrEmpty(id));
if (bothProvided)
{
return false; // leave it on default
}
var isKnownAction = KnownActions.Contains(action
, StringComparer.InvariantCultureIgnoreCase);
// action is known
if (isKnownAction)
{
return false; // leave it on default
}
// action is not known, id was found
values["action"] = "Index"; // change action
values["id"] = action; // use the id
return true;
}
And the route map (before the default one - both must be provided), should look like this:
routes.MapRoute(
name: "DefaultMap",
url: "{controller}/{action}/{id}",
defaults: new { controller = string.Empty, action = "Index", id = string.Empty },
constraints: new { lang = new MyRouteConstraint() }
);
Summary: In this case, we are evaluating the value of the "action" parameter.
if both 1) action and 2) id are provided, we won't handle it here.
nor if this is known action (in the list, or reflected...).
only if the action name is unknown, let's change the route values: set action to "Index" and action value to ID.
NOTE: action names and id values need to be unique... then this will work
The easiest way is to simply create two Action methods in your Controller. One for Index and one for Send and place your string id parameter on both. Since you cannot have duplicate or overloaded action methods, that solves that problem. Your Index method will now handle both index or blank paths where the id is present or not (null) and process your views that way. Your Send method will do the exact same as Index. You can then route, process, or redirect how you like, depending on if id is null. This should work with no changes to RouteConfig.cs:
public ActionResult Index(string id) {if (id == null) Do A else Do B}
public ActionResult Send(string id) {if (id == null) Do A else Do B}
I had this struggle for a long time, and this was the simplest solution.
I don't think there is a solution for your requirements, as you have two competing routes. Maybe you can define something specific (in case you don't have any other controllers).
routes.MapRoute(
name: "Send",
url: "{controller}/Send/{id}",
defaults: new { controller = "Home", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Home",
url: "Home",
defaults: new { controller = "Home", action = "Index" }
routes.MapRoute(
name: "Index",
url: "{controller}/{id}",
defaults: new { controller = "Home", action= "Index",
id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
This what worked for me:
1) In RouteConfig, I put this to be the first line:
routes.MapRoute(name: "single", url: "{controller}/{lastName}",
defaults: new { controller = "LeaveRequests", action = "Index" });
2) In my controller:
public ViewResult Index(string lastName)
{
if (lastName == null)
{
return //return somthing
}
else
{
return //return something
}
}
3) when you call the controller
http://localhost:34333/leaveRequests/Simpsons
it will give you all requests for Simpsons.
if you call http://localhost:34333/leaveRequests/
it will give all requests
this is working for me
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default1",
url: "{id}",
defaults: new { controller ="Home", action ="Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller ="Home", action ="Index", id = UrlParameter.Optional }
);

Categories