Custom ASP.NET MVC Route In Nested Folders - asp.net-mvc-routing

I want sub-folders in my MVC application, so the current routes just don't cut it.
I've got a folder structure such as
Views/Accounts/ClientBalances/MyReport.aspx
and I'm wanting a URL such as http://myapp/Accounts/ClientBalances/MyReport. How do you achieve this with mapping routes? I've had a bash but I'm not very savvy with them. I thought it'd be along the lines of
routes.MapRoute( _
"Accounts/ClientBalances", _
"Accounts/ClientBalances/{controller}/{action}/{id}", _
New With {.controller = "Home", .action = "Index", .id = ""} _
)
I've had no luck though. Any ideas?

Take a look at ASP.NET MVC 2's areas; they look like very similar to what you're trying to achieve. You can watch a quick, 3-minutes video introducing them here.
If you can't (or don't want to) use them, then check this answer about nested view folders. In summary:
You can just return the appropriate view like this (from the action method):
return View("~/Views/controllername/modulename/actionname.ascx", [optional model]);

The location of the view has nothing to do with the route.
Your views should be in Views/[ControllerName]

Related

Enforce Hyphens in .NET MVC 4.0 URL Structure

I'm looking specifically for a way to automatically hyphenate CamelCase actions and views. That is, I'm hoping I don't have to actually rename my views or add decorators to every ActionResult in the site.
So far, I've been using routes.MapRouteLowercase, as shown here. That works pretty well for the lowercase aspect of URL structure, but not hyphens. So I recently started playing with Canonicalize (install via NuGet), but it also doesn't have anything for hyphens yet.
I was trying...
routes.Canonicalize().NoWww().Pattern("([a-z0-9])([A-Z])", "$1-$2").Lowercase().NoTrailingSlash();
My regular expression definitely works the way I want it to as far as restructuring the URL properly, but those URLs aren't identified, of course. The file is still ChangePassword.cshtml, for example, so /account/change-password isn't going to point to that.
BTW, I'm still a bit rusty with .NET MVC. I haven't used it for a couple years and not since v2.0.
This might be a tad bit messy, but if you created a custom HttpHandler and RouteHandler then that should prevent you from having to rename all of your views and actions. Your handler could strip the hyphen from the requested action, which would change "change-password" to changepassword, rendering the ChangePassword action.
The code is shortened for brevity, but the important bits are there.
public void ProcessRequest(HttpContext context)
{
string controllerId = this.requestContext.RouteData.GetRequiredString("controller");
string view = this.requestContext.RouteData.GetRequiredString("action");
view = view.Replace("-", "");
this.requestContext.RouteData.Values["action"] = view;
IController controller = null;
IControllerFactory factory = null;
try
{
factory = ControllerBuilder.Current.GetControllerFactory();
controller = factory.CreateController(this.requestContext, controllerId);
if (controller != null)
{
controller.Execute(this.requestContext);
}
}
finally
{
factory.ReleaseController(controller);
}
}
I don't know if I implemented it the best way or not, that's just more or less taken from the first sample I came across. I tested the code myself so this does render the correct action/view and should do the trick.
I've developed an open source NuGet library for this problem which implicitly converts EveryMvc/Url to every-mvc/url.
Uppercase urls are problematic because cookie paths are case-sensitive, most of the internet is actually case-sensitive while Microsoft technologies treats urls as case-insensitive. (More on my blog post)
NuGet Package: https://www.nuget.org/packages/LowercaseDashedRoute/
To install it, simply open the NuGet window in the Visual Studio by right clicking the Project and selecting NuGet Package Manager, and on the "Online" tab type "Lowercase Dashed Route", and it should pop up.
Alternatively, you can run this code in the Package Manager Console:
Install-Package LowercaseDashedRoute
After that you should open App_Start/RouteConfig.cs and comment out existing route.MapRoute(...) call and add this instead:
routes.Add(new LowercaseDashedRoute("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
new DashedRouteHandler()
)
);
That's it. All the urls are lowercase, dashed, and converted implicitly without you doing anything more.
Open Source Project Url: https://github.com/AtaS/lowercase-dashed-route
Have you tried working with the URL Rewrite package? I think it pretty much what you are looking for.
http://www.iis.net/download/urlrewrite
Hanselman has a great example herE:
http://www.hanselman.com/blog/ASPNETMVCAndTheNewIIS7RewriteModule.aspx
Also, why don't you download something like ReSharper or CodeRush, and use it to refactor the Action and Route names? It's REALLY easy, and very safe.
It would time well spent, and much less time overall to fix your routing/action naming conventions with an hour of refactoring than all the hours you've already spent trying to alter the routing conventions to your needs.
Just a thought.
I tried the solution in the accepted answer above: Using the Canonicalize Pattern url strategy, and then also adding a custom IRouteHandler which then returns a custom IHttpHandler. It mostly worked. Here's one caveat I found:
With the typical {controller}/{action}/{id} default route, a controller named CatalogController, and an action method inside it as follows:
ActionResult QuickSelect(string id){ /*do some things, access the 'id' parameter*/ }
I noticed that requests to "/catalog/quick-select/1234" worked perfectly, but requests to /catalog/quick-select?id=1234 were 500'ing because once the action method was called as a result of controller.Execute(), the id parameter was null inside of the action method.
I do not know exactly why this is, but the behavior was as if MVC was not looking at the query string for values during model binding. So something about the ProcessRequest implementation in the accepted answer was screwing up the normal model binding process, or at least the query string value provider.
This is a deal breaker, so I took a look at default MVC IHttpHandler (yay open source!): http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/MvcHandler.cs
I will not pretend that I grok'ed it in its entirety, but clearly, it's doing ALOT more in its implementation of ProcessRequest than what is going on in the accepted answer.
So, if all we really need to do is strip dashes from our incoming route data so that MVC can find our controllers/actions, why do we need to implement a whole stinking IHttpHandler? We don't! Simply rip out the dashes in the GetHttpHandler method of DashedRouteHandler and pass the requestContext along to the out of the box MvcHandler so it can do its 252 lines of magic, and your route handler doesn't have to return a second rate IHttpHandler.
tl:dr; - Here's what I did:
public class DashedRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["action"] = requestContext.RouteData.GetRequiredString("action").Replace("-", "");
requestContext.RouteData.Values["controller"] = requestContext.RouteData.GetRequiredString("controller").Replace("-", "");
return new MvcHandler(requestContext);
}
}

Zend Router Question

I use modules layout to structure my controllers:
:module/:controller/:action
I would like to add a new custom route so that the following url will work.
domain.com/username
where username is a username of any registered user on the website.
Can anyone point me in the right direction?
Thank you
See this blog post for a detailed explanation of how to do this in ZF:
http://tfountain.co.uk/blog/2010/9/9/vanity-urls-zend-framework
Not sure if you can make something like domain.com/username. Instead you could do domain.com/u/username or domain.com/user/username. For example, to make the second route in you application.ini you could put something similar to the following:
resources.router.routes.user.route = "/user/:user"
resources.router.routes.user.type = "Zend_Controller_Router_Route"
resources.router.routes.user.defaults.module = default
resources.router.routes.user.defaults.controller = user
resources.router.routes.user.defaults.action = user
resources.router.routes.user.defaults.user =
resources.router.routes.user.reqs.user = "\s+"
http://framework.zend.com/manual/en/zend.controller.router.html covers quite well all the different ways you can add routes. Keep in mind, once you add custom routes, the default one will not work anymore unless you explicitly define it (as well as in url view helpers etc.).

Incorrect page loading in MVC

We are currently hosting a asp.net mvc 2 website in IIS 6. In this application we override the 'Create Controler' method and configure a custom view engine. This engine specifies the location of the views depending on the url format. for example; if a user lands on www.asite.com/test/1.0/index.aspx
the view engine tells mvc to look for index.aspx in the 'sitedirectory/test/1.0/views/pages/' directory;
string versionDirectory = String.Format("~/{0}/{1}", offerCode, version.ToString("#0.0000"));
ViewLocationFormats = new[]
{
versionDirectory + "/Views/Pages/{0}.aspx",
versionDirectory + "/Views/Pages/{0}.ascx",
"~/Views/Pages/{0}.aspx",
"~/Views/Pages/{0}.ascx",
"~/Shared/Views/{0}.aspx",
"~/Shared/Views/{0}.ascx"
};
MasterLocationFormats = new[]
{
versionDirectory + "/Views/Layouts/{0}.master",
"~/Views/Layouts/{0}.master"
};
PartialViewLocationFormats = ViewLocationFormats;
The Issue that we are having is that when two or more users land on the site at roughly the same time,
the views that get loaded can get switched around. However the data that is shown for those views is correct.
does anyone have any ideas why this would be happening?
This is a (little) known issue - there is a problem with caching going on.
Take a look at this post:
http://www.hanselman.com/blog/ABetterASPNETMVCMobileDeviceCapabilitiesViewEngine.aspx
And go through the comments.
I ended up implementing owe view engine that derives from IViewEngine directly and uses WebFormsViewEngine internally.

Translate asp.net mvc 2 application?

I'm hoping this is a rather simple question, but I'm pretty new to MVC and can't see clearly how it should be done. I have a site that I need to translate to another language. I've tried to search on this, but all I found was pretty complex translations about how to handle strings etc in resx files. That may be something for later, but for now all I want is to be able to let the user switch language (by links I can place in the master page), and then based on that choice have different pages shown in different languages.
From my search it seemed this could be achieved by routing somehow. As suggested in another post:
routes.MapRoute(
"Default",
"{language}/{controller}/{action}/{id}",
new { language = "en", controller = "Home", action = "Index", id = "" }
);
And the master page switch links:
<li><%= Html.ActionLink(
"Spanish",
ViewContext.RouteData.Values["action"].ToString(),
new { language = "es" })%></li>
<li><%= Html.ActionLink(
"French",
ViewContext.RouteData.Values["action"].ToString(),
new { language = "fr" })%></li>
<li><%= Html.ActionLink(
"English",
ViewContext.RouteData.Values["action"].ToString(),
new { language = "en" })%></li>
I could try this, but what I don't understand is, what type of routes does this create? Is it "language/controllername/actionname"? And if so, where does it lead? I mean, usually, with just a controller and an action, all I have is one controller and one view, and as long as that view exists it will work. But what is the language in this? Is it just as a folder, so if I have a folder say en-GB/Home such a route would work? That doesn't make sense, so I guess not. So how do I actually make these routes lead somewhere? Where do I place the translated views?
I think using resource files instead will be easier in the long run and not that hard to get going with.
Check out this link for more information.
Here's a quick how to on it.
Here's some gotchas to using resources in .Net MVC with solutions.
re the url, it is like you said / like it reads - language/controllername/actionname
re what it calls - what you need to focus on to understand it is in this bit of the route definition:
new { language = "en", controller = "Home", action = "Index", id = "" }
{controller}/{action}, matches the corresponding controller and action like before. Language and id matches those parameters in the action method you define. Those could also be properties of the (view)model, if that's the parameter you have in the method.
I don't think there is anything automatically hooked for the languages in mvc, so you have to explicitly decide how you want to handle it. One way would be for your action methods to return a view in a subfolder for each language or by adding the language as part of the file name.
Another way to go about it, is to define a handler in the route that sets the thread ui culture as you would in classic asp.net. From then on you use the asp.net mvc resources like in klabranche links.

Rewrite Route to map to default route

Because of the problems I experienced here: Zend_ Controller_ Router_Exception: “xyz” is not specified
I want to have this route:
":module/:controller/:id"
and map it onto this:
":module/:controller/:action/id/$id"
Is there any possibility to do this with Zend Framework? I do not want to forward the browser to that URL. I just want to tell Zend Framework how to handle this route.
As for reasons why I would like to do this, you can find them in that linked SO question.
Yes it is possible. In my application.ini I specify my routes using regex this way:
resources.router.routes.something.type = "Zend_Controller_Router_Route_Regex"
resources.router.routes.something.route = "mymodule/mycontroller/([0-9]+)"
resources.router.routes.something.defaults.module = "mymodule"
resources.router.routes.something.defaults.controller = "mycontroller"
resources.router.routes.something.defaults.action = "myaction"
resources.router.routes.something.map.1 = "id"
I am not familiar with the ":variable" way of defining routes, but you can take from my example the ability to set default controllers, modules, and actions, without the need to explicitly define them in the url.