Simple MVC routing problem - asp.net-mvc

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);
}

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.

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
);

MVC 2 AreaRegistration Routes Order

I noticed that in MVC 2 Preview 2, AreaRegistration is loading the routes for each area in an arbitrary order. Is there a good way to get one before the other?
For example, I have two areas - "Site" and "Admin". Both have a "Blog" controller.
I would like the following:
/admin/ --> go to Admin's Blog controller
/ --> go to Site's Blog controller.
The problem is that it is loading the site's route first, so it is matching {controller}/{action}/{id} instead of admin/{controller}/{action}/{id} when I go to the url "/admin/". I then get a 404, because there is no Admin controller in the "Site" area.
Both areas default to the "Blog" controller. I realize I could simply put site/{controller}/... as the url, but I would rather have it at the root if possible. I also tried keeping the default route in the global RegisterRoutes function, however, it is then not sent to the "Sites" area.
Thanks in advance!
Aside from what Haacked said, it is very much possible to order area registrations (and thus their routes). All you have to do is register each area manually, in whatever order you want. It's not as sleek as calling RegisterAllAreas() but it's definitely doable.
protected void Application_Start() {
var area1reg = new Area1AreaRegistration();
var area1context = new AreaRegistrationContext(area1reg.AreaName, RouteTable.Routes);
area1reg.RegisterArea(area1context);
var area2reg = new Area2AreaRegistration();
var area2context = new AreaRegistrationContext(area2reg.AreaName, RouteTable.Routes);
area2reg.RegisterArea(area2context);
var area3reg = new Area3AreaRegistration();
var area3context = new AreaRegistrationContext(area3reg.AreaName, RouteTable.Routes);
area3reg.RegisterArea(area3context);
}
Another option is to take the code for RegisterAllAreas(), copy it into your own app, and build your own mechanism for determining the order. It is quite a bit of code to copy if you want all the fancy caching logic that the built-in method does, but your app might not even need that.
Currently it's not possible to order areas. However, I think it makes sense to try and make each area as independent from other areas as possible so the order doesn't matter.
For example, instead of having the default {controller}/{action}/{id} route, maybe replace that with specific routes for each controller. Or add a constraint to that default route.
We are mulling over options to allow ordering, but we don't want to overcomplicate the feature.
I make this solution:
AreaUtils.cs
using System;
using System.Web.Mvc;
using System.Web.Routing;
namespace SledgeHammer.Mvc.Site
{
public static class Utils
{
public static void RegisterArea<T>(RouteCollection routes,
object state) where T : AreaRegistration
{
AreaRegistration registration =
(AreaRegistration)Activator.CreateInstance(typeof(T));
AreaRegistrationContext context =
new AreaRegistrationContext(registration.AreaName, routes, state);
string tNamespace = registration.GetType().Namespace;
if (tNamespace != null)
{
context.Namespaces.Add(tNamespace + ".*");
}
registration.RegisterArea(context);
}
}
}
In global.asax:
Utils.RegisterArea<SystemAreaRegistration>(RouteTable.Routes, null);
Utils.RegisterArea<ClientSitesAreaRegistration>(RouteTable.Routes, null);
//AreaRegistration.RegisterAllAreas(); do not dublicate register areas
No requred changes to generated area registration code.
I also use custom constrant in routes to filter routes by type of domain in request (system domain or user site).
This is my area registrations as example:
namespace SledgeHammer.MVC.Site.Areas.System
{
public class SystemAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "System"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"System_Feedback",
"Feedback",
new { controller = "Feedback", action = "Index" }
);
context.MapRoute(
"System_Information",
"Information/{action}/{id}",
new { controller = "Information", action = "Index", id = UrlParameter.Optional }
);
}
}
}
namespace SledgeHammer.MVC.Site.Areas.ClientSites
{
public class ClientSitesAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "ClientSites"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"ClientSites_default",
"{controller}/{action}/{id}",
new { controller = "Site", action = "Index", id = UrlParameter.Optional },
new { Host = new SiteInGroups("clients") }
);
}
}
}
For reference,
In MVC3 (don't know about MVC2) when you just want to map root to a specific area/controller you could simply use a global route.
Just remember to specify the namespace/area.
routes.MapRoute(
"CatchRoot", "",
new { controller = "SITEBLOG-CONTROLLER-NAME", action = "Index"}
).DataTokens.Add("area", "SITE-AREA-NAME");

ASP.NET MVC Routing Question

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();
}

Resources