ASP.NET MVC Routing giving dir listing at root - asp.net-mvc

I've got the following routes:
// Submission/*
routes.MapRoute(
"Submission",
"Submission/{form}",
new { controller = "Submission", action = "Handle", form = "" });
// /<some-page>
routes.MapRoute(
"Pages",
"{page}",
new { controller = "Main", action = "Page", page = "Index" });
The first routes requests exactly as per this question. The second generically routes a bunch of static content pages. For instance localhost/Help, localhost/Contact, etc. all route to the MainController which simply returns the view according to the page name:
public class MainController : Controller
{
public ActionResult Page()
{
var page = (string)RouteData.Values["page"];
return View(page);
}
}
The problem is, during testing at least, localhost/ gives a dir listing instead of routing to Main/Index.aspx. The real problem is it fubars my SiteMap menu because the URLs aren't matching what's defined in the Web.sitemap file. localhost/Index does give me the correct view, however.
The curious thing is this works as expected on Mono / XSP.

If you are testing it using Visual Studio Dev Server than it should work. I have tried it just now.
On IIS neither of "localhost/" and "localhost/Index" should work unless you enabled wildcard mapping
So it works for me. You probably are missing something that is not obvious from the post.
BTW, your action can be improved:
public ActionResult Page(string page)
{
return View(page);
}
EDIT: Here is my sample project.

I finally figured it out. There were (potentially) two things going awry. One, the project must have the MVC project type GUID. Check out this for an idea - though the post isn't quite on topic. Two, Visual Studio 2008 requires SP1 for an updated ASP.NET development server; the version of it prior to SP1 doesn't kick off Global.asax w/o a Default.aspx page.

Related

ASP.NET - unable to find view

There is a long-standing issue with ASP.NET MVC not properly handling URL routes if there are forward slashes as a parameter even if they are URL encoded.
Example:
On a default install, go to this URL (adjusting ports as needed)
http://localhost:11541/Token/Create?
callback=http%3a%2f%2flocalhost%3a11491%2ftoken%2fcreatetoken%2fAddPrivateValues
Notice that the Controller is "Token" and "Create" is the method to be called. This is the error I get:
The view 'http://localhost:11491/token/createtoken/AddPrivateValues' or
its master was not found or no view engine supports the searched
locations. The following locations were searched:
~/Views/Token/http://localhost:11491/token/createtoken/AddPrivateValues.aspx
~/Views/Token/http://localhost:11491/token/createtoken/AddPrivateValues.ascx
~/Views/Shared/http://localhost:11491/token/createtoken/AddPrivateValues.aspx
~/Views/Shared/http://localhost:11491/token/createtoken/AddPrivateValues.ascx
Notice that it's calling "CreateToken/AddPrivateValues". This is wrong. It should be calling Token.Create.
This issue appears to have been broken since 2009 (according to prior S.O. research) so I'm not holding my breath. I just need to fix this and move it to Azure.
I tried adding a {*id} route to my controller, but that doesn't work because there are many forward slashes. The only way to fix this is to disable this parsing in the machine.config (web.config WILL NOT WORK)
Question
How do I set this property in Windows Azure, without using RDP so that the permissions on Web.config are as secure and locked down as they were before I attempted to do this?
I am unable to replicate your issue on a clean deployment of an MVC 4 project. How are you trying to pass the callback parameter back to your view (if at all). If you are using
return View(callback);
This would be why it is failing, because it is interpreting the string variable as a view name. Try either storing it in a view bag or encapsulating it within a view model. As a side note, I know that passing parameters with slashes seems to work in all MVC versions since on the Account/Login controller action you can send a returnUrl without issue.
Below is my configuration, let me know if I have deviated anywhere from your setup.
Route Config
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Changed default route to allow me to only have to create one controller
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Token", action = "Index", id = UrlParameter.Optional }
);
}
Token Controller
public ActionResult Index()
{
return View();
}
public ActionResult Create(string callback)
{
#ViewBag.Item = callback;
return View();
}
Create.cshtml
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#ViewBag.Item
My URL: http://localhost:11319/Token/Create?callback=http%3a%2f%2flocalhost%3a11491%2ftoken%2fcreatetoken%2fAddPrivateValues
My Create Page Results
<h2>Create</h2>
http://localhost:11491/token/createtoken/AddPrivateValues
<script src="/Scripts/jquery-1.8.2.js"></script>

Handling Brochure-style pages in ASP.Net MVC

I've built a number of ASP.Net MVC sites and in each of them there are a number of pages suited to MVC's Controllers and Actions, and a number of pages that are really just brochure pages - /why, /why/ouradvantage, /about, etc - pages that have no real functionality, just a View, maybe a Layout, and that's it.
For these brochure-style pages I'd really prefer to have just the View and a good Route to find it, so I could put the /why at /Brochure/Why.cshtml or /Brochure/Why/Index.cshtml and either way it will be picked up just fine. I'd like to avoid making silly Controllers and Actions (as I've done in the past) to handle this set of URLs and pages.
How can I go about this in an ASP.Net MVC project? This must be a common need.
EDIT: An example of how I can do this the verbose way:
I could use the standard MVC route ({controller}/{action}/{id}) and spam a bunch of useless Controllers to get the set of URLs and pages I want. Every time I want to add a brochure-style (no functionality, just a View) page I'd add a Controller or Action like this:
Why Controller:
public class WhyController : Controller
{
public ViewResult Index()
{
return View();
}
public ViewResult OurAdvantage()
{
return View();
}
}
This puts a View at /why and /why/ouradvantage - clean URLs. If I wanted an /about page, I could add another Controller that does nothing but return a View named AboutController. If it had 5 subpages I could add 5 Actions to that Controller, all of those Actions doing nothing but return a View.
If these brochure-style pages in the MVC site amounted to say, 100 pages, I'd have quite a few needless Controllers and Actions all doing nothing really. Not very DRY. I'm interested in a way to just put Views in a folder in my Project and have them accessible just for being there (Configuration through Convention), at clean URLs like /why and /why/ouradvantage.
There are a few ways that get me close:
I could put a bunch of .cshtml pages in and visit them directly - but then I have to have the file extension in the URL and the View files themselves have to sit in the root.
I could use ASP.Net Areas to define an Area for these, but then all brochure-style pages have to sit at least one URL segment deep and I still have the above problem of file extensions in URLs.
There are crazy Routes I can define.
I suspect this comes up often in MVC projects that have a small or large number of these Brochure-style pages - it seems like there should be a clean way to do this.
EDIT: A crappy solution that spams the routing engine.
Create a class that maps routes like:
public static void MapRoutes(RouteCollection routes, string appRoot, string path)
{
if (!path.Contains("~/"))
throw new NotSupportedException("Pages path must be virtual (use ~/ syntax).");
var physicalPath = appRoot + path.Substring(2).Replace("/", "\\");
var dir = new DirectoryInfo(physicalPath);
var pages = dir.GetFiles("*.cshtml", SearchOption.AllDirectories);
int rootLength = appRoot.Length;
var rootParsed = pages.Select(p => "~/" + p.FullName.Substring(rootLength).Replace("\\", "/"));
int folderPathLength = path.Length + 1;
var mapped = rootParsed.Select(p => new {
Url = p.Substring(folderPathLength, p.Length - 7 - folderPathLength),
File = p
});
var routedPages = mapped.Select(m => routes.MapRoute(
name: m.Url,
url: "{*url}",
defaults: new { path = m.File, Controller = "Brochure", Action = "Catchall" },
constraints: new { url = m.Url }
)).ToArray();
}
You can call this in RouteConfig like:
BrochureRoute.MapRoutes(routes, Server.MapPath("~/"), "~/Brochure");
That obviously maps all these pages to a BrochureController, which you'll need as well:
public class BrochureController : Controller
{
public ViewResult Catchall(string path)
{
return View(path);
}
}
2 problems:
It spams the routing engine as I mentioned - if you have 100 pages you have 100 routes.
Passing the path as above seems to upset the normal Razor pipeline - visiting a page in this manner gets you an error like:
The view at '~/Brochure/About.cshtml' must derive from WebViewPage
To map individual pages RouteCollection.MapPageRoute method can be used.
routes.MapPageRoute("", "why", "~/Brochure/Why.aspx");
Sure, you could use Razor view engine (.cshtml) instead of WebForms.
Check MSDN documentation for some more details.
Update 2
You are right you won't be able to use this for for .cshtml pages. However, you don't need to use routing to access Web Pages (.cshtml files). It is enough to create the files, and omit the extension in URLs. To achieve your desired structure you could do this:
Your web project must allow Web Pages rendering. To enable this go to web.config and set webpages.Enabled to true. (<add key="webpages:Enabled" value="true" />)
Create folder why in your web application root
Add MVC ViewPage named Index.cshtml. This will be accessible from http://yoursite.com/why
Add MVC ViewPage named ouradvantages.cshtml. This will be accessible from http://yoursite.com/why/ouradvantages
You will also be able to access url data using #UrlData collection
Check more about this here. More about Web Pages in general is also available on asp.net website.
If for some reason you really need to use routing, you'll need custom RouteHandler. You can find one implemented on Nuget. Usage examples of this package are here.
Update
If you'd like to avoid manually adding each route you have few choices.
1) Create a convention to identify brochure pages
You could do this by expecting URLs to be brochure pages by default, and isolating "non-brochure" pages to specific sections:
routes.MapPageRoute("Default", "{brochurepage}", "~/Brochure/{brochurepage}.aspx");
// isolate non-brochure pages to "site" section
routes.MapRoute(
"",
"site/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
2) Hold brochure page names in a collection
List<string> brochurePages = new List<string>()
{ "about", "why", "contact" }; // add more pages here
....
foreach (var brochurePage in brochurePages)
routes.MapPageRoute("", brochurePage, "~/Brochure/" + brochurePage + ".aspx");
I have to be very specific about two URLs you mentioned in your question. To implement routing for "/why" and "/why/ouradvantage" you will NOT be able to write elegant code. This will take special handling.
If your issue is that you don't want to have separate controllers for all these "silly" pages, may I suggest that you create a logical way to route them to a singular controller and then render the correct data/ view based on the route?
For instance, your URLs could read "/Pages/Why/About" and you could route that to your pages controller and it would receive the "Why" and "About" as parameters and you could render your content accordingly. Does that make sense?

Rewrite old .aspx path to new MVC path

i have a mixed aspx/MVC webapp project and need to rewrite incoming URL's either in the MVC routing or through IIS rewriting. whatever works. I cannot figure this out.
I have the following OLD path:
/Article/Nugget/Article.aspx?articleId=30
and i need to rewrite this to:
/Article/Nugget/30
The issue is the MVC route is reading in the Article.aspx being passed as a parameter and anything i do to rewrite this in IIS7 is being ignored. Well.. the issue is i don't have a clue :)
Try something like:
routes.MapRoute(
"Article",
"Article.aspx",
new { controller = "Article", action = "Nugget"}
);
With a parameter named articleId in your action method of course
public ActionResult Nugget(int articleId)
{
..
}

HTTP 404 Error When starting ASP.Net Application using MVC 2

I am having problems trying to get a very simple ASP.Net application to start using .Net Framework 4 and MVC 2.
When press F5 in Visual Studio 2010, I get the following error message HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unable. Please review the following URL and make sure it is spelled correctly.
When I added the view, I added the view by right clicking on the method in the controller, and this of course added the view. I have also noticed that when selecting "Go to View", that this too throws an error in Visual Studio and says that the view does not exist! Further, I have gone to the Global.asax page and changed the default controller to be that of the one I have added, but this has made NO difference.
So, please tell me - but do I need to change?
Try to go to /ControllerName/ActionName. If you have changed the default, you have to make sure you have spelled it correctly. Also note that the ASP.NET MVC Framework removes the "Controller" suffix of controller names.
If your new controller is called MyNewController it should:
Inherit from Controller
Be called MyNewController
Like this
public MyNewController : Controller {
public ActionsResult MyAction() {
return View();
}
}
In Global.asax.cs for this case, the default settings counld be:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "MyNew", action = "MyAction" }
);
Note how the default controller setting lacks the "Controller" suffix.
To install MVC some considerations need to be take in account.
I think you are trying to install it on IIS 5 or 6
Please read this article
http://blog.stevensanderson.com/2008/07/04/options-for-deploying-aspnet-mvc-to-iis-6/
or upgrade to IIS 7
Had a similar problem. I had made the mistake of modifying the default route from this:
url: "{controller}/{action}/{id}",
To this:
url: "{controller}/{action}/{id*}",
Placing the asterisk in the wrong place caused absolutely every URL to give me a 404 - and as usual ASP.Net routes are practically impossible to debug.
The solution was placing the asterisk in the proper place:
url: "{controller}/{action}/{*id}",
(This allows routes with as many forward slashes as you like, instead of limiting them to 3. The portion with an asterisk is a catchall.)

ASP.NET MVC Routing in Azure

I have an Azure Web Role project that was recently MVC'd by another developer. According to the developer the app works with no problem when run on it's own (i.e. as a simple web app). However, when I try to run it in the context of the Azure cloud service, I'm seeing a number of 404 errors. I suspect something is not quite right with the routing. Here's an abbreviated version of the current RegisterRoutes method that's part of Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes){
routes.IgnoreRoute("{Services}/{*pathInfo}");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Configuration",
"Configuration",
new { controller = "Configuration", action = "Index" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Index", id = "" }
);}
When the app starts up, the correct view from the Account controller's Index action is displayed. However if I try to navigate to Configuration I get a 404. Converesly if I change the method to this:
public static void RegisterRoutes(RouteCollection routes){
routes.IgnoreRoute("{Services}/{*pathInfo}");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Account",
"Account",
new { controller = "Account", action = "Index" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Configuration", action = "Index", id = "" }
);}
I get the correct view from the Configuration controller's Index action, but I can't navigate to the Account view.
I'm guessing this is a simple problem to solve, but not knowing what exactly was done to "MVC" the Azure app and being new to MVC has me beating my head into the wall.
Here's the configuration of the machine where I'm encountering this issue:
Windows 7 Ultimate with IIS 7.0
Visual Studio 2008 SP1
ASP.NET MVC 1.0
Windows Azure SDK 1.0
Thoughts?
Try using my Routing Debugger. It can help you understand what's going on. http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
It's weird that the behavior would be different locally than in Azure. Also, you should post your controller code (remove the contents of the action methods, we just need to see the method signatures).
If I had to make a wild guess, I'd guess your Configuration route (in the first example you gave) needs to add id="" in the defaults section.
Haacked: Thanks for pointing me to the debugger. That helped me hunt down the issue in a matter of minutes.
The answer was much simpler than I thought. It all had to do with the following line of code:
routes.IgnoreRoute("{Services}/{*pathInfo}");
I put this line in to help resolve an issue I was having with ASP.NET MVC and WCF RIA Services (more info on that here). The curly braces shouldn't be there. I don't want to replace Services. The code should look like this:
routes.IgnoreRoute("Services/{*pathInfo}");
You can read a full write-up here.
I don't think this is your problem, but you might verify that the System.Web.Mvc reference has its Copy Local = true.

Resources