ASP.NET MVC Routing Question - asp.net-mvc

I must be dense. After asking several questions on StackOverflow, I am still at a loss when it comes to grasping the new routing engine provided with ASP.NET MVC. I think I've narrowed down the problem to a very simple one, which, if solved, would probably allow me to solve the rest of my routing issues. So here it is:
How would you register a route to support a Twitter-like URL for user profiles?
www.twitter.com/username
Assume the need to also support:
the default {controller}/{action}/{id} route.
URLs like:
www.twitter.com/login
www.twitter.com/register
Is this possible?

What about
routes.MapRoute(
"Profiles",
"{userName}",
new { controller = "Profiles", action = "ShowUser" }
);
and then, in ProfilesController, there would be a function
public ActionResult ShowUser(string userName)
{
...
In the function, if no user with the specified userName is found, you should redirect to the default {controller}/{action}/{id} (here, it would be just {controller}) route.
Urls like www.twitter.com/login should be registered before that one.
routes.MapRoute(
"Login",
"Login",
new { controller = "Security", action = "Login" }
);

The important thing to understand is that the routes are matched in the order they are registered. So you would need to register the most specific route first, and the most general last, or all requests matching the general route would never reach the more specific route.
For your problem i would register routing rules for each of the special pages, like "register" and "login" before the username rule.

You could handle that in the home controller, but the controller method would not be very elegant. I'm guessing something like this might work (not tested):
routes.MapRoute(
"Root",
"{controller}/{view}",
new { controller = "Home", action = "Index", view = "" }
);
Then in your HomeController:
public ActionResult Index(string view) {
switch (view) {
case "":
return View();
case "register":
return View("Register");
default:
// load user profile view
}
}

OK I haven't ever properly tried this, but have you tried to extend the RouteBase class for dealing with users. The docs for RouteBase suggest that the method GetRouteData should return null if it doesn't match the current request. You could use this to check that the request matches one of the usernames you have.
You can add a RouteBase subclass using:
routes.Add(new UserRouteBase());
When you register the routes.
Might be worth investigating.

i think your question is similar to mine. ASP.NET MVC Routing
this is what robert harvey answered.
routes.MapRoute( _
"SearchRoute", _
"{id}", _
New With {.controller = "User", .action = "Profile", .id = ""} _
)

Here is an alternative way to standar route registration:
1. Download RiaLibrary.Web.dll and reference it in your ASP.NET MVC website project
2. Decoreate controller methods with the [Url] Attributes:
public SiteController : Controller
{
[Url("")]
public ActionResult Home()
{
return View();
}
[Url("about")]
public ActionResult AboutUs()
{
return View();
}
[Url("store/{?category}")]
public ActionResult Products(string category = null)
{
return View();
}
}
BTW, '?' sign in '{?category}' parameter means that it's optional. You won't need to specify this explicitly in route defaults, which is equals to this:
routes.MapRoute("Store", "store/{category}",
new { controller = "Store", action = "Home", category = UrlParameter.Optional });
3. Update Global.asax.cs file
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoutes(); // This do the trick
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
How to set defaults and constraints? Example:
public SiteController : Controller
{
[Url("admin/articles/edit/{id}", Constraints = #"id=\d+")]
public ActionResult ArticlesEdit(int id)
{
return View();
}
[Url("articles/{category}/{date}_{title}", Constraints =
"date=(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])")]
public ActionResult Article(string category, DateTime date, string title)
{
return View();
}
}
How to set ordering? Example:
[Url("forums/{?category}", Order = 2)]
public ActionResult Threads(string category)
{
return View();
}
[Url("forums/new", Order = 1)]
public ActionResult NewThread()
{
return View();
}

Related

MVC route constraint custom attribute

I'd like to setup a custom route constraint that would allow me to decorate a controller with an attribute so that I don't have to pass in string to the route and remember to update it with new controller names.
I thought I could setup use the IRouteConstraint but I can't seem to get the attribute. Perhaps I'm just missing something obvious here.
routes.MapRoute("test",
"foo/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = new TestConstraint()}
);
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
public class TestConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
return false;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class CustomConstraintControllerAttribute : Attribute
{
}
[CustomConstraintController]
public class TestController : Controller
{
public ActionResult Index()
{
return View();
}
}
Edit:
Current Method:
routes.MapSubdomainRoute("Store",
"{store}/{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = UrlParameter.Optional},
new {controller = "StoreHome|Contacts|..." }
);
This route configuration ensures that a url must match subdomain.test.com/GiggleInc/Contacts or subdomain.test.com/GiggleInc/StoreHome.
subdomain.test.com/GiggleInc/StoreHome
MapSubdomainRoute /{store} /{controller}/{action}/{id}
This method requires that each controller that should be used this way must be added to the controller constraint.
As I mentioned in the comments, I want to replace the hard coded strings for an attribute or something similar.
First, thank you for this sweet line of code I didn't know was possible:
constraints: new { controller = "StoreHome|Contacts" }
I didn't know I could filter my MapRoute to a list of Controllers so easily.
Second, you don't need to implement a custom IRouteConstraint for this.
MVC offers the Attribute-Routing you are looking for.
You may even include Default/Optional Values, just like in your MapRoute.
Decorate your Controller Class like so:
[RoutePrefix("{store}/Test")]
[Route("{action=Index}/{id?}")]//Without this, you need to define "[Route]" above every Action-Method.
public class TestController : Controller
{
public ActionResult Index(string store)//Adding "string store" is optional.
{
return View();
}
}
That's it.
Remember to add the "store" Parameter in all your Actions under each Controller (but it is not Required).
Note:
If you use Attributes instead of MapRoute, then you will not be able to hit the Controller without the "store" 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, it will not find it, because Visual Studio doesn't know to add the RoutePrefix for you.
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/
Try this...
public class TestRouteAttribute : RouteFactoryAttribute
{
public TestRouteAttribute(string template) : base(template) { }
public override RouteValueDictionary Constraints
{
get
{
var constraints = new RouteValueDictionary();
constraints.Add("TestConstraint", new TestConstraint());
return constraints;
}
}
}
Then you should be able to decorate your action methods using [TestRoute]
By the way, would love to know how to accomplish this in asp.net core if anyone knows.

ASP.NET MVC - Attribute Routing not finding area view

I've been searching for answers for this everywhere, but I can't seem to find any. I basically have an MVC application setup and I am using the built in AttributeRouting for my routes.
The folder structure looks like this;
Models
Views
Controllers
Areas
Member
MemberAreaRegistration.cs
Controllers
HomeController.cs
Views
Home
Account.cshtml
And then I wire up my routes in the global.asax like this;
public class Application : System.Web.HttpApplication {
protected void Application_Start(){
AreaRegistration.RegisterAllAreas();
// other web optimization stuff
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
So then, MemberAreaRegistration.cs is simple.
namespace App.Web.Areas.Member {
public class MemberAreaRegistration: AreaRegistration {
public override string AreaName { get { return "Member"; } }
}
public override void RegisterArea( AreaRegistrationContext context){ }
}
And I try to wire it using the attributes...
/areas/member/controllers/homecontroller.cs
// ...
[Route("member/account")]
public ActionResult Account() { return View(); }
// ...
The problem is that this finds the route, but it cannot find the view. I get the following error;
The view 'Account' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
~/Views/Home/Account.aspx
~/Views/Home/Account.ascx
~/Views/Shared/Account.aspx
~/Views/Shared/Account.ascx
~/Views/Home/Account.cshtml
~/Views/Home/Account.vbhtml
~/Views/Shared/Account.cshtml
~/Views/Shared/Account.vbhtml
By all accounts, this should work fine - and if not, I expect the ~/area to at least be in the path it is trying to search. Do I have to wire something additional up to make this function?
I am using ASP.NET MVC 5.0
If I hardcode the absolute path of the view, it works. Obviously this is not a good situation though. I'd prefer it to find the view out of convention. But if I type return View("~/areas/member/views/home/account.cshtml"); I do get the view back - so I know it can access to file and that it is correct.
Here is my RouteConfig.cs per request
RouteConfig.cs
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
// mvc attribute routing allows us to supersede normal routing mechanisms and
// declare our routes a bit more verbosely
routes.MapMvcAttributeRoutes();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "App.Web.Controllers" }
);
}
}
That's because, once you are defining your route as an action's attribute, ASP.NET MVC doesn't know which area it is in, hence it doesn't know where to look for Views.
In the Controller the Account action is in, try to explicitly specify a RouteArea attribute.
I'm writing this off the top of my head, but it should look like:
[RouteArea("Member")]
[RoutePrefix("member")]
public class HomeController: Controller {
[Route("account")]
public ActionResult Account() { return View(); }
}
or, alternatively:
[RouteArea("Member")]
public class HomeController: Controller {
[Route("member/account")]
public ActionResult Account() { return View(); }
}

2 Actions For Same Route in Asp.Net MVC 3

I have route defined as
routes.MapRoute(
"Company", // Route name
"Company/{companyname}", // URL with parameters
new { controller = "Company", action = "CompanyDetail", companyname = UrlParameter.Optional } // Parameter defaults
);
Now the problem is that i have made this route if now i made any request to company controller and pass a parameter it goes to CompanyDetail method , but in one condition i dont want to send to this method i want to send the control to another action CallCompany . How to solve this and note i also need to run both type of request .
you can set it in your controller method:
public ActionResult CompanyDetail(string companyname)
{
if (condition)
{
return RedirectToAction("ActionName", new { companyname = companyname});
}
return View();
}
As I understood your question, you want to realise the following behavior:
There is as set of company names (for example, "test") and they correspond with URL
yourhost/Company/test
They should be routed to CallCompany.
The other URL (such as yourhost/Company/another_company) should be routed to CompanyDetail.
I think, that the best way is to do redirect in CompanyDetail method
public ActionResult CallCompany(string companyname)
{
return View();
}
public ActionResult CompanyDetail(string companyname)
{
IEnumerable<string> myCompanies = GetSpecialCompany();
if (myCompanies.Contains(companyname))
{
return RedirectToAction("CallCompany", new { companyname = companyname });
}
return View();
}
private IEnumerable<string> GetSpecialCompany()
{
throw new NotImplementedException();
}
you should probabaly look into mvc route constraints. that would enable you to forward request on the simillar url to different action depending uopn different parameters which you can programatically set.
for example
routes.MapRoute(
"Product",
"Product/{productId}",
new {controller="Product", action="Details"},
new {productId = #"\d+" }
);
this would only go to controller:Product and action Details in product id is an int
in your case you will have to define the pattern in regex for which request should go to one route and place the second route next to this
so automatically every request which dosent fit the constraint for this route will be handeled by the next one.

How to customize a URL to use a name different than my Controller?

I'm using ASP.NET MVC to develop a website and I need to customize my URL to use a name that is not the name of my Controller.
I want to use this Class/Method names:
public class CompanyController:Controller {
public ActionResult About() {
return View();
}
}
But I want to use the URL http://www.mysite.com/the-company/about-us to access my Controller/Method.
How should I proceed?
Thank you.
Since your question is mainly about controller naming I would (contrary to #Nissan Fan's answer) do at least this generalization, to make routing a bit more flexible and minimize the amount of routes, you'd have to define:
routes.MapRoute(
"CompanyRoute",
"the-company/{action}",
new { controller = "Company", action = "About" }
);
Your controller should of course be written this way:
public class CompanyController : Controller
{
[ActionName("about-us")]
public ActionResult About()
{
return View("About");
}
}
You will use URL Routing:
http://www.asp.net/learn/mvc/tutorial-05-cs.aspx
routes.MapRoute(
"AboutUs", // Route name
"the-company/about-us", // URL with parameters
new { controller = "CompanyController", action = "About" } // Parameter defaults
);

Simple MVC routing problem

I have few pages that quite similar to the others but one of them doesn't work.
When I write 'http://localhost:2265/Segment/' I get annoying error message "Server Error in '/' Application.
The resource cannot be found."
Other pages like 'http://localhost:2265/User/' works very well AND also 'http://localhost:2265/Segment/Create'. So Index of the Segment is the problem. I have used ASP.NET Routing Debugger and on other pages I get correct mappings, but I get this same error message "resource cannot be found" also when using debugger.. I think this indicates that Default route doesn't catch it either..
Any ideas?
Here is my MapRoute commands.
routes.MapRoute(
"Maintenance",
"Maintenance/{action}",
new { controller = "Maintenance", action = "Index", id = "" }
);
routes.MapRoute(
"User",
"User/{action}",
new { controller = "User", action = "Index", id = "" }
);
routes.MapRoute(
"Segment",
"Segment/{action}",
new { controller = "Segment", action = "Index", id = "" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Update:
Thank you for the quick reply!
I removed all routes except the default. It didn't solve the problem, but now the route list is shorter.
I have other classes inside my controller file like this:
public class SegmentFormViewModel
{
}
public class SegmentController : Controller
{
}
public class SegmentFormCreateModel : Segment
{
}
I tried to move it inside controller, but that didn't help either.
Is there any way to debug this problem?
Update:
Here is my controller (without contents of the methods)
public class SegmentController : Controller
{
//
// GET: /Segment/
public ActionResult Index()
{
}
//
// GET: /Segment/Details/5
public ActionResult Details(Guid id)
{
}
//
// GET: /Segment/Create
public ActionResult Create()
{
}
//
// POST: /Segment/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
}
//
// GET: /Segment/Edit/5
public ActionResult Edit(int id)
{
}
//
// POST: /Segment/Edit/5
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection)
{
}
}
To match your /Segment/ route you will need a "SegmentController" controller class to have an "Index" action that accepts GET requests, ie not restricted with [AcceptVerbs(HttpVerbs.Post)].
As Darin has already commented, the default route would handle all the other routes you have added so you don't need the extra routes.
After update:
Your problem is probably more to do with the actions in the Segment controller. It doesn't matter what classes you have in which file. What actions do you have in the Segment controller?
After 2nd update:
Your actions look ok so I suspect the problem is in code you have not listed.
Next steps:
1. Use the router debugger already mentioned.
2. Download the MVC source so you can step through it.
Last resort: Start a brand new project and only add the Segment controller. Then keep adding related code until you find the problem.
Use Phil Haack's RouteDebugger. It'll tell you what was matched, which often clears the problem up pretty quickly.
It's really easy to set up: drop RouteDebug.dll in your /bin folder and make this change to your App Start event:
protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}

Resources