MVC routing for both .mvc and extensionless URLs - asp.net-mvc

I have an odd question. I'm working on some MVC code that was setup to run extensionless, as in /Home/Index?id=10 However, it used to be setup for IIS 6, and was using the .mvc extension on all the controllers, as in /Home.mvc/Index?id=10.
I would like both routes to be able to work. In my global.asax.cs file, I created the following code:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("Default2",
"{controller}.mvc/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
This almost worked! But unfortunately when my controllers return RedirectToAction, the URL which is generated fails.
I call RedirectToAction like this:
return RedirectToAction("Index", "Account");
and it returns this:
/Account.mvc
Now Index is assumed so I'm not worried that that's missing. However, there is no / on the end of that URL, and because of that, it is returning a 404 error. If I add the / it works fine. Is there some way I can help MVC to generate the URLs correctly, or is there a way I can modify the routes to make it recognize this URL?
Background: I'm in this situation because I began converting my application to use extensionless URLs after we upgraded our webserver to IIS 7.5. I didn't realize that there were alot of links into the applications which had been shared with the outside world--changing to extensionless broke all those links. Doh! Should've left well enough alone.

I would personally use the URL Rewriter. You can Redirect ASP.NET Legacy URLs to Extensionless with the IIS Rewrite Module.
So for example you could create an outbound rules that looked something like (not exactly, they definitely need testing):
<outboundRules>
<rule name="remove .mvc outbound 1">
<match serverVariable="RESPONSE_LOCATION"
pattern="^/(.*).mvc$" />
<action type="Rewrite" value="/{R:1}" />
</rule>
<rule name="remove .mvc outbound 2">
<match filterByTags="A, Form, IFrame, Img, Input, Link, Script"
pattern="^/(.*).mvc$" />
<action type="Rewrite" value="/{R:1}" />
</rule>
</outboundRules>
If someone decided to hardcode an MVC extension...
<rule name="Add Slash Inbound">
<match url="(.*)" />
<conditions>
<add input="{PATH_INFO}" pattern="^/(.*).mvc$" negate="true" />
</conditions>
<action type="Rewrite" url="{R:0}/" />
</rule>

Looks like here is coded your solution with adding / at end of each requests Add a trailing slash at the end of each url?. If it is not enough We can try something else :)

For anyone trying to figure this out: the problem may not be that RedirectToAction is generating a bad URL, but actually that the URL is not being interpreted properly by IIS.
I discovered that other servers had no problem with the missing slash in the URLs described in the original question.
This led me to look at the App Pool setting. One setting in particular seems to affect this: "Enabled 32-bit applications". Setting this to true on the Application Pool enabled my application to parse URLs which were missing the trailing '/'.
Weird huh?

Related

IIS reverse proxy configuration issues with MVC 5 app

I cannot properly configure reverse proxy in IIS 8.5 for my MVC5 application. We use 3rd party application Tibco Spotfire WebPlayer 7.0.1 to integrate into our MVC webapp. To do so we use Javascript API to open report from WebPlayer and insert it into iframe.
We have to configure reverse proxy to allow requests go to backend server with WebPlayer installed. Our main webapp located at http://mywebapp.com/ and all requests that should be rewrite looks like http://mywebapp.com/SpotfireWeb/..., backend server located at private ip http://172.29.1.7/SpotfireWeb/...
The main issue that I configured reverse proxy using ARR 3.0 and URL Rewrite Module 2.0, but it doesn't work for *.ashx, *.aspx, *.axd, *.asmx files. It looks like some handler conflicts with ARR/RewriteModule and doesn't allow to pass requests to different server
Details:
When I try to load static html file like http://mywebapp.com/SpotfireWeb/secret.html the request is been rewrote to 172.29.1.7 server and I got content of this file. But once I request something like http://mywebapp.com/SpotfireWeb/GetJavaScriptApi.ashx?Version=6.0 I got 404 error from mywebapp.com. I run Wireshark and discovered that requests to back-end server 172.29.1.7 don't go at all if requests contains *.ashx file.
I installed Failed Requests Trace and found that event HANDLER_CHANGED occurred which changed ApplicationRequestRoutingHandler to System.Web.Mvc.MvcHandler
Playing with this I removed some handlers and now Wireshark catches requests to the back-end server. I believe this approach of removing handlers is not correct. May be I configured something in wrong way?
Anyway, removing one module by one I faced issue when POST request to http://mywebapp.com/SpotfireWeb/AjaxService.asmx/InitiateOpen didn't processed and I got 404 error from my initial app. When I removed *.asmx handler I still got the same error. This is point where I get stuck right now.
My rewrite rules from web.config of mywebapp.com. I configured it at website level:
<rewrite>
<rules>
<rule name="ReverseProxyInboundRule1" enabled="true" stopProcessing="true">
<match url="SpotfireWeb/?(.*)$" />
<action type="Rewrite" url="http://172.29.1.7/SpotfireWeb/{R:1}" logRewrittenUrl="true" />
<serverVariables>
<set name="ORIGINAL_HOST" value="{HTTP_HOST}" />
</serverVariables>
</rule>
</rules>
<outboundRules>
<clear />
<rule name="ReverseProxyOutboundRule1" preCondition="" enabled="true">
<match filterByTags="A, Area, Base, Form, IFrame, Img, Input, Link, Script" pattern="SpotfireWeb/?(.*)" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true" />
<action type="Rewrite" value="http://mywebapp.com/SpotfireWeb/{R:1}" />
</rule>
<rule name="ReverseProxy_Redirection" preCondition="IsRedirection" enabled="true">
<match filterByTags="A, Area, Base, Form, IFrame, Img, Input, Link, Script" serverVariable="RESPONSE_Location" pattern="^http://[^/]+/(.*)" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true">
<add input="{ORIGINAL_HOST}" pattern=".+" />
<add input="{URL}" pattern="^/(SpotfireWeb)/?.*" />
</conditions>
<action type="Rewrite" value="http://{ORIGINAL_HOST}/{C:1}/{R:1}" />
</rule>
<preConditions>
<preCondition name="IsRedirection">
<add input="{RESPONSE_STATUS}" pattern="3\d\d" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
Have anyone saw something similar ?
I assume that root cause in MVC handler, but can't understand where.
I fixed this. Obviously we don't need to remove modules. The answer basically into logs of failed requests.
During MAP_REQUEST_HANDLER event ASP.NET infrastructure determines the request handler for the current request. In my case MVC handler selected every time I requested URL like /SpotfireWeb/... regardless of ARR was current handler. Here I found that:
..the handler mapping can be changed during request execution in the MAP_REQUEST_HANDLER event. This allows scenarios such as URL rewriting to work.
My fix was removing SpotfireWeb from MVC routes completely. Thus I added following code into RouteConfig.cs
routes.IgnoreRoute("SpotfireWeb/{*pathInfo}");
And the whole class looks like:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// ignore everything under 'SpotfireWeb' because this request will be routed
// to separate server via reverse proxy ARR/Rewrite module
routes.IgnoreRoute("SpotfireWeb/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { area = string.Empty, controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "GKSPortal.Controllers" });
}
}

ReturnUrl in RouteConfig.Cs not working

In my MVC project I Use this code in RouteConfig.Cs
routes.MapRoute(
name: "Signin",
url: "login/{ReturnUrl}",
defaults: new { controller = "Account", action = "Signin", ReturnUrl = UrlParameter.Optional }
);
but url not workin well when i'm browsing the signin page from another page for example:
localhost:43773/account/signin?ReturnUrl=anotherpage
i expect : localhost:43773/login/anotherpage
how can i fix this?
I understand what you are expecting. But ASP.NET MVC does NOT generate URLs based on the route(s) you define. That would be both unnecessary and would incur an overhead.
If it is really important for you to have a URL like localhost:43773/account/login/anotherpage, then you could look into IIS URL Rewrite Module.
Here is an example of how you can achieve what you want to do
<rewrite>
<rules>
<rule name="Standardize Log In Url" stopProcessing="true">
<match url="^account/signin?ReturnUrl=([0-9a-z]+)" />
<action type="Rewrite" url="account/login/{R:1}" />
</rule>
</rules>
</rewrite>
NOTE This solution is untested, so you may need to make some modifications to it.

How can I force all IIS requests to go to one MVC controller & action?

I have a situation that I figured would be quite easy to do in MVC.
I want to have all request on a IIS site end up going to one specific action.
So I added the following to my route
routes.MapRoute(
"Everything",
"{*foo}",
new { controller = "Home", action = "Index" }
);
That works for most request going to the server, but there are some things the are ending being dealt with by other handlers:
http://foo.com/ (works)
http://foo.com/bar (works)
http://foo.com/bar/baz (works)
http://foo.com/bar/baz.txt (works - used to not work, but started working after adding runAllManagedModulesForAllRequests="true" in web.config)
http://foo.com/bar/baz. (does not work. stumped as to why)
This is in MVC 5, but I don't think that should really make a difference to the solution.
What trick am I missing here?
I would use the url rewrite module for this. Something like this should do it. (Obviously, change the url to whatever you want):
<rewrite>
<rules>
<rule name="Catch-All" stopProcessing="true">
<match url=".*" />
<action type="Rewrite" url="/index.html" appendQueryString="false" />
</rule>
</rules>
</rewrite>

Incorrect redirect from ASP.NET MVC application under URL Rewrite

I have the next configuration of websites in the IIS:
http://main.domain.com website (ASP.NET MVC 5 app, website binded to the specified host, port 80)
default website binded to :80 with ARR/URL Rewrite module enabled
A wildcard binding *.domain.com is specified in DNS settings.
The desired behavior is to have http://main.domain.com as an entry point and a set of dynamic user-subdomains like http://user1.domain.com, http://user2.domain.com, etc.
Now this behavior is simulated using links like http://main.domain.com/user/user1
I have set up the URL Rewrite rule for main.domain.com in the way like this:
<rule name="user-redirection" enabled="true" stopProcessing="true">
<match url="^.*$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true">
<add input="{HTTP_HOST}" pattern="^([\w\d-]+)\.domain\.com$" />
</conditions>
<action type="Rewrite" url="http://main.domain.com/user/{C:1}/{R:0}" logRewrittenUrl="true" />
</rule>
Everything is ok here - I can see that http://user1.domain.com works just like http://main.domain.com/user/user1 as it worked earlier.
Then I try to re-implement a logic of checking the existance of specified user in the database. For instance, when user47 doesn't exist - opening of the http://main.domain.com/user/user47 link leads to redirection to the http://main.domain.com entry point.
In the code-side it is done by adding custom filter attribute to the controller action that implements the needed conditional redirect. I have the next code:
public class UserController : Controller {
[CustomRedirectBehavior]
public ActionResult Index()
{
...
}
}
public class CustomRedirectBehaviorAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
...
if (redirectingCondition) {
filterContext.Result = new RedirectResult("http://main.domain.com");
}
}
}
And here.... I get a cyclic redirection error in the browser! To be certain - I have double-checked this behavior:
When I open http://main.domain.com/user/user47 I'm properly redirected to http://main.domain.com
When I open http://user47.domain.com/ I get a cyclic redirection error.
Then in order to investigate the problem I've modified redirect callback to:
filterContext.Result = new RedirectResult("http://someotherdomain.com/some/other/path");
And.... I can see that:
When I open http://main.domain.com/user/user47 I'm properly redirected to http://someotherdomain.com/some/other/path
When I open http://user47.domain.com/ I'm redirected to http://user47.domain.com/some/other/path !!! (And yes, that's not a typo and I've also double-checked this behavior)
So. I need an idea on how to pass through this problem
I've tried to reproduce your case and it seems you are really getting a redirecting loop. So I added a negative lookahead group ((?!main)) into the pattern and it works for me. This is how my web.config looks:
<rule name="user-redirection" enabled="true" stopProcessing="true">
<match url="^.*$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true">
<add input="{HTTP_HOST}" pattern="^(?!main)([\w\d-]+)\.domain\.com$" />
</conditions>
<action type="Rewrite" url="http://main.domain.com/user/{C:1}" logRewrittenUrl="true" />
</rule>
In order to investigate your problem I've tried:
Disable browser cache. If your browser using cache it might you will not see every change of the rule you made.
Change the type of action to be "Redirect" instead of "Rewrite" and watching what's going on in the "Network" tab in the "Developer tools (F12)".
e.g. <action type="Redirect" ...
Well... Finally I got the answer.
The problem was coming from the Application Request Routing (ARR) module being installed in order to make rewriting work with custom subdomains.
The Reverse rewrite host in response headers option was set to true by default after enabling proxy in the Server Proxy Settings area. Disabling this setting makes redirect working correctly under subdomain-based rewriting.

asp.net mvc does not receive the GET request with a period in it

I'm using .net4.5rc with MVC4.0rc
The code below is taken from a MVC webapi application but I the same behaviour is there for reular asp.net mvc
My registerroutes code looks like this
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "R1",
routeTemplate: "product/{id}",
defaults: new {
controller="Product",
id = RouteParameter.Optional
}
);
This works well with Ids that doesn't have a period in them. However when I ask for a product with a period in it, the call does not seem to get to ASP.NET
when I use the setting below as suggested on similar posts it works when there is a trailing /
But it doesn't work without it
in summary
http://mysite.com/product/AZ0 //works
http://mysite.com/product/AZ.0/ //work with relaxedUrlToFileSystemMapping
http://mysite.com/product/AZ.0 //does not work
The response is 404 and I guess this is returned by IIS without involving ASP.NET.
When I run routedebugger with a URL like the last one above, I don't even get the routedebugger stuff
How do I make IIS to pass the control to ASP.NET when there is a URL with a pattern:
mysite.com/product/{id} regardless of whether there is a period in the id or not.
thanks.
Let me answer this myself. Adding a URL Rewrite solves the problem.
The code below added to the web.config tells to rewrite the Url with a trailing "/" if the URL is of the form (product/.) Then it is no longer handled as a file but handled as regular MVC.
<system.webServer>
....
<rewrite>
<rules>
<rule name="Add trailing slash" stopProcessing="true">
<match url="(product/.*\..*[^/])$" />
<action type="Rewrite" url="{R:1}/" />
</rule>
</rules>
</rewrite>
</system.webServer>

Resources