MVC4 hyphen in URL - asp.net-mvc

So i'm trying to employ the use of hyphens in my URLs for readability but so far haven't found a way to make it work with mvc4.
I'll start with the code in the controller:
public ViewResult Index()
{
ViewBag.URL = Functions.fetchURL();
return View();
}
[HttpPost]
public ActionResult Index(LogonModel model, string returnUrl)
{
//omitted to save space
return View(model);
}
public ActionResult Forgot_Login_Info()
{
return View();
}
[HttpPost]
public ActionResult Forgot_Login_Info(RetrievePasswordViewModel model)
{
//omitted to save space
return View();
}
So I'm using underscores for the name of the actions as seen with "Forgot_Login_Info" and that is the name of the view as well. For testing I also manually created a view called "Forgot-Login-Info"
In my global.asax.cs file i simply have this line for routes
RouteConfig.RegisterRoutes(RouteTable.Routes);
which is the standard routing line with new projects for MVC4 and this is hooked to the "RouteConfig.cs" file and in that file here is the code I have, which I found in another question on this site.
public class RouteConfig
{
public class HyphenatedRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-","_");
requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
return base.GetHttpHandler(requestContext);
}
}
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 }
);
routes.Add(
new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Default", action = "Index", id = UrlParameter.Optional }),
new HyphenatedRouteHandler())
);
}
}
Lastly here is the code for the "Index" view, which is where I have a link to the "Forgot_Login_Info".
<div class="centerBlockItem" style="width:450px; text-align:center">
#Html.ActionLink("Forgot Password?", "Forgot-Login-Info")
#Html.ActionLink("Forgot Password?", "Forgot_Login_Info")
</div>
Now I have the link 2 ways, 1 using underscore and the other using hyphen. According to what i've read in regards to the code I found for this purpose, Both links should open the "Forgot_Login_Info" view and not the "Forgot-Login-Info" view. But what I'm getting instead is the 2nd link works fine and the action name matches the view files name. But when I click on the first link, I get a 404 error because the system can't find the "Forgot-Login-Info" view file, even though I had manually created a file with that name for testing.
One more thing to note, the code in my routeconfig file, In the answer I had found it suggested I comment out the default route code lines and just leave:
routes.Add(
new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Default", action = "Index", id = UrlParameter.Optional }),
new HyphenatedRouteHandler())
);
I had done this too and got the same results. Any ideas where I'm screwing things up?

Ok I figured out what I was doing wrong, I had this listed under the root routebundle.cs file but i was trying to make this work in an area, once I moved this code to the area registration file, it worked like a charm. If anyone needs my full code listing just ask and i'll post it all up.

How is this helping SEO when the crawlers can still access the underscore version of the controller, thereby duplicating content. Struggling with this right now.

Related

MVC ActionLink advice

I'm just starting out with .NET, and am building a test application. I currently have the homepage set using a DefaultController, and an Index() action method. This works as expected, and the homepage is simple www.domain.com.
I have created 2 new pages (Terms and Privacy) under the same DefaultController, using Terms() and Privacy() action methods.
I want to be able to browse to these with the URL as www.domain.com/terms and www.domain.com/privacy.
When i use a <li>#Html.ActionLink("Terms of Service", "Terms", "Default")</li> it works, but it takes me to the URL at www.domain.com/Default/privacy.
Should i be creating seperate controllers for each of these pages, or am I using the #html.ActionLink helper incorrectly? I have previously used <li>Privacy Policy</li> but I understand this isn't best practice?
Also, is there a way to force links as lowercase?
My Controller Code:
public class DefaultController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Terms()
{
return View();
}
public ActionResult Privacy()
{
return View();
}
}
If these were in the HomeController I don't believe you'd have the same problem. However, I think you can get around this by using the RouteConfig file:
routes.MapRoute(
name: "Default",
url: "{Action}/{Id}",
defaults: new { controller = "Default", Action = "Index", Id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Generic",
url: "{controller}/{Action}/{Id}",
defaults: new { Action = "Index", Id = UrlParameter.Optional }
);
I believe that what you want to do is hide the controller name in the url. If that is the case, your question is answered here:
ASP.NET MVC - Removing controller name from URL
You can use Attribute routing to give specific routes to endpoints.
[Route("terms")]
public ActionResult Terms()
{
return View();
}
[Route("privacy")]
public ActionResult Privacy()
{
return View();
}
In your RouteConfig.cs you must enable attribute routing with the following line of code: routes.MapMvcAttributeRoutes();
Now any urls generated with #Url.Action() or #Html.ActionLink should generate URLS as domain.com/privacy and domain.com/terms

MVC5 Url.Action not returning correct URL

I'm trying to use a Kendo Grid for a list of objects on my model, but the url's generated by the .Create() etc. methods are not generating the url correctly.
It doesn't appear to be just Kendo though because even in my controller using Url.Action() generates the wrong url.
// POST: Assessment/Create
[HttpPost]
[ValidateAntiForgeryToken]
[Route("eForms/Assessment/Create")] // <-- Tried with and without this
public ActionResult Create(AssessmentPoco model)
{
var x = Url.Action(("Allergy_Read", "Assessment");
}
//POST: Assessment/Allergy_Read
[HttpPost, ActionName("Allergy_Read")]
[Route("AllergyRead", Name = "Allergy_Read")]
public ActionResult Allergy_Read([DataSourceRequest] DataSourceRequest request, AssessmentAllergiesSection model) //, int id)
{
return Json(new[] { model }.ToDataSourceResult(request, ModelState));
}
Expected: eForms/Assessment/Allergy_Read
Actual: /?action=Allergy_Read&controller=Assessment
Route config:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//web forms default
routes.MapPageRoute(
routeName: "WebFormDefault",
routeUrl: "",
physicalFile:"~/default.aspx");
routes.MapRoute(
name: "API",
url: "eforms/api/{controller}/{action}/{id}",
defaults: new {controller="Customer", action="GetCustomers", id = UrlParameter.Optional}
);
////mvc default
routes.MapRoute(
name: "Default",
url: "eforms/{controller}/{action}/{id}",
defaults: new { controller = "IncidentReports", action = "Search", id = UrlParameter.Optional }
);
}
Not sure what else could be at fault here (besides my brain), any ideas?
Clarifications (from comments):
We are using Areas
Global.asax is calling RegisterRoutes (also tried turning it off no change)
Update:
This project is a newly added MVC project to an existing ASP.Net WebForms app. I updated the Route config because I was using looking at the wrong one.
You're using routeAttribute, so put a name inside it and use Html.RouteLink or Url.RouteUrl instead of Url.Action().
Example:
[Route("menu", Name = "mainmenu")]
public ActionResult MainMenu() { ... }
Usage in View:
Main menu
I tried this code in controller:
public class HomeController : Controller
{
[Route("AllergyRead", Name = "Allergy_Read")]
public ActionResult Allergy_Read()
{
return View();
}
}
And:
#Html.RouteLink("Allergy Read", "Allergy_Read")
Give me the right route to action. I can't figure out why your implementation isn't working.

Html.Action() results in "No route in the route table matches the supplied values"

This issue has been discussed many times, but I haven't found a resolution for my particular case.
In one of my Umbraco (6) views I am calling a controller method by using
#Html.Action("Index", "CountryListing");
This results in the "no route in the route table" exception.
I have been fiddling around with the RegisterRoutes method to no avail. I wonder if it is even used as the site still functions when I empty the RegisterRoutes method. This is what it looks like now:
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 }
);
}
I have also tried adding an empty "area" to the call like this
#Html.Action("Index", "CountryListing", new {area: String.Empty});
I am using #Html.Action statements in other places and they DO work, so I do understand why some of them work and others don't, but the main problem now is getting my country listing action to work.
You can solve this by doing the following
Make sure your Controller is extending Umbraco's SurfaceController
Name it YourName*SurfaceController*
Add the [PluginController("CLC")] 'annotation' (I am from Java) to your controller. CLC stands for CountryListController. You can make up your own name of course.
Add the PluginController name (CLC) to the Html.Action call as a "Area" parameter.
My controller:
[PluginController("CLC")]
public class CountryListingSurfaceController : SurfaceController
{
public ActionResult Index()
{
var listing = new CountryListingModel();
// Do stuff here to fill the CountryListingModel
return PartialView("CountryListing", listing);
}
}
My partial view (CountryListing.cshtml):
#inherits UmbracoViewPage<PatentVista.Models.CountryListingModel>
#foreach (var country in Model.Countries)
{
<span>More razor code here</span>
}
The Action call:
#Html.Action("Index", "CountryListingSurface", new {Area= "CLC"})
you can use null instead of using String.empty
#Html.Action("Index", "CountryListing",null);
if you are using area, you have to override RegisterRoutes for every area
public override string AreaName
{
get
{
return "CountryListing";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"CountryListing_default",
"CountryListing/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
and i recommend you take a look at the same problem:
https://stackoverflow.com/a/11970111/2543986

Adding new pages to MVC 3 project results in "Resource not found" error

I have an existing MVC 3 project I've been working on for a while. Today I decided to add a new page. I added a new view, hooked it up with an actionresult on an existing controller but no dice. I get a 404. I thought maybe something was wonky with the view so I did a response.write in the controller (to take the view out of the equation). No dice. I tried creating a new controller. Same thing. I tried moving an existing page. Same thing. All existing pages work, but I can't add new ones or move existing ones. My keyboard is covered in hair. Please help.
My global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Home",
"",
new { controller = "Home", action = "Index" }
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
The view I'm trying to add is /Views/Restaurants/Nearby.
The controller is /Controllers/RestaurantsController and the ActionResult is:
public ActionResult Nearby()
{
return View();
}
Start by removing the following useless line from your route definitions:
routes.MapRoute(
"Home",
"",
new { controller = "Home", action = "Index" }
);
so that you only have the default route setup:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Now in order to access your new controller action you would navigate to /Restaurants/Nearby. This will render the Nearby action on the Restaurants controller:
public class RestaurantsController: Controller
{
public ActionResult Nearby()
{
return View();
}
}
which obviously would execute the ~/Views/Restaurants/Nearby.cshtml view.
I know this is an old thread but I have found many many 'Resource Not Found' solutions which did not help me. After some time I worked out my issue. Hopefully it will help someone else.
The problem was that I had been messing about with deploying my application, so my project happened to be set to a 'Release' configuration. I had also altered the release configuration to compile to a different folder.
So I think when I was hitting 'Run' in the toolbar, it was picking up an older DLL and running from that.
I only spent three hours on this so I'm happy.
You could try adding the Route Debugger NuGet package IIRC,
Get-Package routedebugger
If needed, change your web.config entry to activate the route debugger for all requests:
<add key="RouteDebugger:Enabled" value="true" />
Then you should build and run your project, and navigate to /Restaurants/Nearby/. The resulting diagnostics should tell you where your routing issue (if any) lies. If you are still having trouble, edit your post with the route debugging info and we'll see what we can do!

ASP.NET MVC `Html.ActionLink` between "Areas"

I have added a new Area to my MVC3 project and I am trying to link from the _Layout page to the new Area. I have added an Area called 'Admin' that has a controller 'Meets'.
I used the visual studio designer to add the area so it has the correct area registration class etc, and the global.asax file is registering all areas.
However, when I use the following 2 action links in a page in the root, I run into a few problems:
#Html.ActionLink("Admin", "Index", "Meets", new { area = "Admin" }, null)
#Html.ActionLink("Admin", "Index", "Meets", new { area = "" }, null)
When clicking both links, I am taken to the Meets controller in the Admin area, where the application then proceeds to throw an error saying it cannot find the Index page (even though the Index page is present in the Views folder in the Area sub-directory.
The href for the 1st link looks like this:
http://localhost/BCC/Meets?area=Admin
And the href for the 2nd link looks like this:
http://localhost/BCC/Meets
Also if I hit the link that I expect to be created:
http://localhost/BCC/Admin/Meets
I just get a resource cannot be found error. All very perplexing! I hope someone can help...
Strange indeed. Steps that worked perfectly fine for me:
Create a new ASP.NET MVC 3 application using the default Visual Studio template
Add an area called Admin using Visual Studio designer by right clicking on the project
Add new Controller in ~/Areas/Admin/Controllers/MeetsController:
public class MeetsController : Controller
{
public ActionResult Index()
{
return View();
}
}
Add a corresponding view ~/Areas/Admin/Views/Meets/Index.cshtml
In the layout (~/Views/Shared/_Layout.cshtml) add links:
#Html.ActionLink("Admin", "Index", "Meets", new { area = "Admin" }, null)
#Html.ActionLink("Admin", "Index", "Meets", new { area = "" }, null)
Run the application.
Rendered HTML for the anchors:
Admin
Admin
As expected the first link works whereas the second doesn't.
So what's the difference with your setup?
Another option is to utilize RouteLink() instead of ActionLink(), which bypasses the area registrations altogether:
ActionLink version:
Html.ActionLink("Log Off", "LogOff", "Account", new { area = "" }, null)
RouteLink version:
Html.RouteLink("Log Off", "Default",
new { action = "LogOff", controller = "Account" })
The second parameter is a "Route Name" which is registered in Global.asax.cs and in various 'AreaRegistration' subclasses. To use 'RouteLink' to link between different areas, you only need to specify the correct route name.
This following example shows how I would generate three links to different areas from a shared partial, which works correctly regardless of which area I am 'in' (if any):
#Html.RouteLink("Blog", "Blog_default",
new { action = "Index", controller = "Article" })
<br/>
#Html.RouteLink("Downloads", "Download_default",
new { action = "Index", controller = "Download" })
<br/>
#Html.RouteLink("About", "Default",
new { action = "Index", controller = "About" })
Happy coding!
I figured this out - I created a new test project and did exactly the same thing I was doing before and it worked...then after further inspection of all things route-related between the two projects I found a discrepancy.
In the global.asax file in my BCC application, there was a rogue line of code which had inexplicably appeared:
public static void RegisterRoutes(RouteCollection routes)
{
// Problem here
routes.Clear();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
As you can see where my comment is, at some time or other I had placed the routes.Clear() call at the beginning of RegisterRoutes, which meant after I had registered the Areas in Application_Start, I was then immediately clearing what I had just registered.
Thanks for the help...it did ultimately lead to my salvation!
Verify that your AdminAreaRegistration class looks like this:
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
and that you have this in Global.asax.cs:
protected void Application_Start()
{
... // ViewEngine Registration
AreaRegistration.RegisterAllAreas();
... // Other route registration
}
I solved this problem by doing the following.
In my Global.asax.cs, I have
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
}
protected void Application_Start()
{
//Initialise IoC
IoC.Initialise();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
In my PublicAreaRegistration.cs (Public Area), I've got
public class PublicAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Public";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute("Root", "", new { controller = "Home", action = "Index" });
context.MapRoute(
"Public_default",
"Public/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
, new[] { "<Project Namespace here>.Areas.Public.Controllers" }
);
}
}
In my AuthAreaRegistration.cs (Area for Restricted access), I've got
public class AuthAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Auth";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Auth_default",
"Auth/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And finally, my links in my *.cshtml pages would be like
1) #Html.ActionLink("Log Off", "LogOff", new{area= "Public", controller="Home"})
or
2) #Html.ActionLink("Admin Area", "Index", new {area= "Auth", controller="Home"})
Hope this saves someone hours of research! BTW, I'm talking about MVC3 here.
Kwex.
This might not be the case for most of the developers but I encountered this problem when I added a my first area and did not build my solution. As soon as I build my solution the links started to populate correctly.

Resources