URLs with several path components get sent directly to static file handler - asp.net-mvc

I have the following problem with an Asp.net MVC application when running on IIS (tested with version 7.0 and 8.5) : if the path in the URL has no more than 3 components (for example http://myhost.com/a/b/c, it gets correctly handled by the application code. But when an additional component is added in the path (http://myhost.com/a/b/c/d), it will be sent directly to the static file handler. As the path is not used for a physical file, I get a 404 (with the error page coming from the static file handler, not the custon 404 page configured in web.config)
I don't have this problem on my dev station running IIS Express.
I use runAllManagedModulesForAllRequests="true" in web.config, disabling it doesn't solve the issue.
Disabling the static file handler doesn't help, I still get a 404 but this time the error message says "Handler Not yet determined".
I really looks this is something happening very early in IIS processing, because if I recycle the application pool and try to access to this URL, the response comes immediately, without the usual delay that I get when the application needs to start.
What am I missing? Why is IIS not forwarding such requests to my application?

It's your routes!!
The default route in MVC only takes over controller, action & ID. You're trying to use a fourth which you haven't defined. Just add another url parameter to your route collection.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}/{another}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, another= UrlParameter.Optional }
);
Hope this helps.

I think I found something: the long url was not handled by an MVC route, but by an OWIN middleware. Somehow, it seems that for IIS, if no route is configured for an URL, then the request pipeline is completely ignored, including any OWIN middlewares (and also custom page for 404).
By adding another parameter in a route like suggested by heymega, I was able to work around the problem, the OWIN middlewares were executed. I added the following route:
routes.MapRoute("CatchLongUrls", "{a}/{b}/{c}/{*d}",
new {controller = "Invalid", action = "Nothing"});

Related

Url.Action returning empty string in some environments

I have a Sitecore site and on 2 of my CD servers, Url.Action returns an empty string. This works locally and on 9 other servers ranging from dev to prod, CD and CM.
Deployment automation ensures that the exact same web.config is deployed to all environments; ditto for all other configs.
My controller inherits from SitecoreController properly. This is not isolated to a certain controller or action, this happens with all controllers and actions.
What would make Url.Action return an empty string in one environment and not others, with identical code?
What would make Url.Action return an empty string sometimes?
Specifically, route values that are derived from the current request.
Explanation
The Url.Action method is driven by the UrlHelper, which in turn is driven by routes. It uses route values to determine which route to use to build the URL. The routing framework attempts to match each route against the route values in the order they are registered until a match is found. If the routing framework reaches the end of the routing table and there is still no match, it returns an empty string (because there is no other reasonable default behavior).
On the other hand, if you call Url.Action and pass a route name, this narrows the possible matches to only 1 specific route (the named one). But the route values still need to match that route or you get the default empty string.
In general, all route values must match, but there are a couple of things that may make the behavior quirky:
Route values can be made optional. This means that the route value doesn't need to be present in order for the route to match.
If a route value is not supplied in the call to Url.Action, it may be supplied automatically if it exists in the current request.
This second quirk means that if Url.Action is put on a shared view and one request contains a route value to make it match a route, and another request doesn't contain that route value, in the latter case the URL may match another route or it may be an empty string.
Example
Say the routing configuration is setup like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "AboutWith4RouteValues",
url: "test/home/about/{foo}/{bar}",
defaults: new { controller = "Home", action = "About" });
routes.MapRoute(
name: "ContactWith4RouteValues",
url: "test/home/contact/{foo}/{bar}",
defaults: new { controller = "Home", action = "Contact", bar = UrlParameter.Optional });
routes.MapRoute(
name: "Home",
url: "",
defaults: new { controller = "Home", action = "Index" }
);
}
}
Also, let's say there is a link on the _Layout.cshtml page:
<a href='#Url.Action("About", "Home")'>About</a>
If you go to the home page in the browser (/), the resulting link URL will be:
<a>About</a>
This is because foo and bar are not present in the route values of the request so it doesn't match any of the registered routes.
Actually, Url.Action returns an empty string, but Razor optimizes away the empty href='' attribute.
On the other hand, if you put this URL in the browser:
/test/home/contact/arg1/arg2
The link URL is generated as:
<a href='/test/home/about/arg1/arg2'>About</a>
This is because both {foo} (with value arg1) and {bar} (with value arg2) are available in the current request. Note that the incoming request matches the ContactWith4RouteValues route, but when the link URL is generated it uses the AboutWith4RouteValues route. Since both foo and bar are present in the request, they are carried over to the generation of the URL.
Now, if the URL in the browser is changed to:
/test/home/contact/arg1
It still matches the ContactWith4RouteValues route because the last value is optional. However, the URL that is generated is now:
<a>About</a>
This is because foo has a value in the request, but bar has no value, the Url.Action generation request does not match AboutWith4RouteValues because bar is a required value in order to make it match. And since it also doesn't match the Home route, we have reached the end of the route table and the only logical thing to return is empty string.
Workaround
The simplest workaround to avoid these quirks of the current request is to manually specify the route values when calling Url.Action or other UrlHelper based methods (such as ActionLink, RedirectToRoute, etc).
<a href='#Url.Action("About", "Home", new { foo = Model.Foo, bar = Model.Bar })'>About</a>
This ensures those values are always present when building the URL even if they don't happen to be part of the current request.
I had this exact same issue and for me the issue had to do with Case-Sensitivity in my Routing.
Deploying the same site to two different Applications on the same IIS Web Server.
I used the same web-config and same VS Web Publish Settings.
Yet, my Url.Action was returning blanks (empty-strings) in Prod, but not in Dev.
After reviewing the code, something caught my eye.
I have an Area called WorkBench (upper-case "B").
In a few places (where Url.Action returned blank), I was passing in Workbench (lower-case "b").
I have some fancy logic in my RouteConfig.cs and Area Registration (i.e. WorkBenchAreaRegistration.cs).
In those files, I had Conditional Logic to determine what Environment the Application was running in.
I use the same Project to share code, but only want some Areas accessible in different Environments.
Even though Prod and Dev are different Environments, to debug this issue,
I altered them so they would temporarily appear the same.
The issue was fixed once I capitalized the "b" to "B" everywhere.
I still do not know why it would work on my local machine and in Dev, but not in Prod.
It should have behaved the same way in both Environments.
Again, same server, same publish, same web.config, same iis config, same application code, etc...
Sorry I don't have an explanation for this, but at least this is a possible fix you could try.
Hope this helps someone out there.
Update 02/08/2019: I had this problem again with a different link and for this one I realized I had an option set to access all Actions within an Area when one of my Debug variables was set to true, or when running in Prod. I removed this conditional logic in my AreaRegistration.cs file and it fixed the problem when running in a Staging envrionment.
The Lesson Learned here is that anytime you see a blank Href, you gotta check those Route Configs.

What is wrong with this MVC route?

I want to create a custom image URL handler, which can handle GET requests with the following URL pattern:
http://blah.com/images/image1.gif
To handle this I created the following route:
routes.MapRoute(
name: "appImages",
url: "images/{*imageName}",
defaults: new { controller = "Image", action = "Grab" });
This does not seem to work even for images with no spaces in their names. What am I doing wrong?
By default, IIS looks for existing files and folders when it processes the request from the user. If you have an existing folder or file, then IIS will simply try and retrieve the file from disk rather than passing the request on to the .NET pipeline / routing engine.
Check to see if you have a folder already called images in your solution. If you do have this folder, I suggest (the easiest method) either changing your controller name or the existing folder name in order to resolve your issues.

Redirect to error controller in web Api

I write project WEB API 2 and I'd like to show json result in my custom format when I user wrong url like "localhost/api/v1/" or "localhost/api", for this I tryed to create ErrorController with get request which will return message about error. I add route config to WebApiConfig:
config.Routes.MapHttpRoute(
"ErrorInApi",
"v{version}",
new {controller ="Error" }
);
How I can write config to redirect in error controller in all wrong url cases?
Most of the times you use exception filters to deal with exceptions in web api, however you can use them to deal with exceptions during routing, which is the case OP asks. So you may use two new things web api offers you: IExceptionLogger and IExceptionHandler which receive instance of ExceptionContext. Linked page provide sample implementation of such hander and logger.

ServiceStack DefaultRedirectPath not triggering

I'm using ServiceStack in an MVC4, VS2012 project running on windows7. I'm attempting to call a default "/search" route when the application loads. To do this I have the following code in my AppHost Configure method
Plugins.Add(new RazorFormat());
SetConfig(new EndpointHostConfig
{
ServiceStackHandlerFactoryPath = "api",
DefaultRedirectPath = "/search"
});
If I call this route directly (via /api/search) it works correctly. However when I run my project I simply get a HTTP Error 403.14 - Forbidden error. It's appears to be attempting to locate a static source document from the website root (I've removed all of these) rather than the dynamic route specified in DefaultRedirectPath
I've also added a HttpHandler via the CatchAllHandlers method to see if the route is being attempted but it appears the DefaultRedirect is simply not happening.
Any suggestions would be greatly appreciated. From everything I've read this should just work.
I'm assuming that you've set up your /api path correctly in web.config too.
DefaultRedirectPath is just a simple redirect used when you request the root of your API (i.e. "/api"). It literally returns a 302 with your DefaultRedirectPath as the Location header (combined with the application root URL, if your ASP.NET application isn't at the root of the server). In other words, it's not a "route", just a relative URL. And in your case, it will redirect to /search in the root of your application, not to /api/search.
It should work, if you use DefaultRedirectPath = "/api/search".
However, when ServiceStack isn't at the root of the website, it will use MetadataRedirectPath first, and only if that is null or empty, DefaultRedirectPath. So you'll need to set MetadataRedirectPath to null, if this is what you want.
As for your test with CatchAllHandlers, as far as I can tell, CatchAllHandlers will actually cause your DefaultRedirect to not be used - the CatchAllHandler will be used as a handler, and the DefaultRedirectHandler that does the DefaultRedirectPath redirect won't ever come into play.
That will take care of redirecting from "/api" to "/api/search".
MVC is in control of your root url - the "api" addition to web.config and AppHost.Config does exactly that - keep ServiceStack only in control of "/api", while letting MVC take care of the rest. So, with that setup, if you want a redirect from "/" to "/api/search", you'd need to do it in the MVC home controller.

Prevent static file handler from intercepting filename-like URL

In my web application I have a route which looks like this:
routeCollection.MapRoute(
"AdfsMetadata", // name
"FederationMetadata/2007-06/FederationMetadata.xml", // url
new { controller = "AdfsController", action = "MetaData" }); // defaults
The idea behind this route was to work better with Microsoft AD FS server (2.0+) which looks for AD FS metadata at this point when you just specify a host name. With MVC3 all worked fine. But we upgraded the project to MVC4 recently and now the call for this URL results in a 404, the handler mentioned on the error page is StaticFile and the physical path is D:\path\to\my\project\FederationMetadata\2007-06\FederationMetadata.xml. I assume that MVC or ASP.NET "thinks" it must be a request for a static file and looks for the file, but it isn't a file. The data is generated dynamically - that's why I routed the URL to a controller action. The problem is that even the Route Debugger by Phil Haack doesn't work. It's just a 404 with no further information besides that IIS tried to access a physical file which isn't there.
Does anyone have a solution for this? I just want this URL to be routed to a controller action.
P.S.: I'm not 100% sure that the cause was the upgrade to MVC4, it was just a guess because the error occurred at about the same time as the upgrade, and the same route works in another project which is still using MVC3.
Edit:
I have a custom ControllerFactory which needs the full class name (AdfsController instead of Adfs), so the suffix Controller is correct in this case.
Add the following to the <handlers> section of the <system.webServer> node:
<add
name="AdfsMetadata"
path="/FederationMetadata/2007-06/FederationMetadata.xml"
verb="GET"
type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />
This instructs IIS that GET requests to the specified url should be handled by the managed pipeline and not considered as static files and served by IIS directly.
I have a custom ControllerFactory which needs the full class name
(AdfsController instead of Adfs), so the suffix Controller is correct
in this case.
Double check this please. Try with and without the Controller suffix in the route definition.
IIS by default is serving that file as static withouth going into ASP pipeline, can you change the route to not have a .xml extension (which is not necessary at least you have a specific requirement about that)
You can specifiy the route like this:
routeCollection.MapRoute(
"AdfsMetadata", // name
"FederationMetadata/2007-06/FederationMetadata", // url
new { controller = "AdfsController", action = "MetaData" }); // defaults
Other solution would be to add to your web.config
<modules runAllManagedModulesForAllRequests="true">
but I recommend you to avoid this as possible since what it does is to stop IIS from serving all static files
EDIT Last try... a custom http handler for that specific path would work. This post is similar to your problem only that with images (look for "Better: Custom HttpHandlers" section)

Resources