Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC4 not handling POST requests in IIS7 integrated mode, but in IIS7.5

I have an interesting case I cannot explain, and I need help figuring out what my problem is on IIS7:

Given:

  • ASP.NET MVC 4 web application
  • default route registered to {controller}/{action}

See the following controller:

public class ServiceController : Controller
{
    public ActionResult Test()
    {
        return Content("Test");
    }

    [HttpPost]
    public ActionResult Test2()
    {
        return Content("Test2");
    }
}

Additionally, in Global.asax there is this code:

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 404)
    {
        ExecuteIndexPage();
    }
}

protected void Application_Error(object sender, EventArgs e)
{
    var error = Server.GetLastError();
    ExceptionLogger.Log(error);

    ExecuteIndexPage();
}

So, whenever there is a server error, this is logged. In this case and in the case of a normal 404, the start page is returned. This works (almost) fine. More to that later.

This setup gives very different behaviours on IIS7 (Windows Server 2008, production environment) and IIS7.5 (Win7 Pro, Development environment and Windows Server 2008 R2, also production environment).

Given the following configuration in IIS (both versions):

  • Web in IIS is configured using an integrated mode ASP.NET 4 application pool
  • <modules runAllManagedModulesForAllRequests="true" /> is set in the system.webServer section

In IIS 7.5 the behaviour is:

  • GET request to /: Returns index page
  • POST request to /: Returns index page
  • GET request to /Service/Test: Returns Test
  • POST request to /Service/Test: Returns Test
  • GET request to /Service/Test2: Executes Global.asax Application_Error: HttpException: A public action method 'Test2' was not found on controller 'MyTestProject.Controllers.ServiceController'.
  • POST request to /Service/Test2: Returns Test2
  • GET request to something there is no route for: Executes Global.asax End_Request.

In IIS 7 the behaviour instead is:

  • GET request to /: Returns index page
  • POST request to /: IIS 404 page
  • GET Request to /Service/Test: Returns Test
  • POST Request to /Service/Test: IIS 404 page
  • GET Request to /Service/Test2: Executes Global.asax Application_Error: HttpException: A public action method 'Test2' was not found on controller 'MyTestProject.Controllers.ServiceController'.
  • POST Request to /Service/Test2: Returns IIS 404 page
  • GET Request to something there is no route for: IIS 404 page

So, IIS 7 and IIS 7.5 work great when using GET requests, except when there is no route. When there is no route, IIS 7.5 executes the Global.asax end request with status code 404 and delivers the index page. IIS 7 does NOT execute Global.asax end request. Why? I could (and currently do) work around this issue by registering a {*catchall} route, so that a matching route exists.

As soon as I am trying to use HTTP POST, IIS 7 does work even less than I would expect.

When POSTing the request, IIS 7 does not execute any code in my application whatsoever, and directly returns an IIS 404 page.

So my question is: Why does IIS 7 refuses so hard to handle POST requests in my MVC 4 application, and what can I do to have it also handle post request?

like image 950
Sebastian P.R. Gingter Avatar asked Feb 05 '13 15:02

Sebastian P.R. Gingter


1 Answers

We figured it out - finally.

The default configuration inserts this in the web.config:

<system.webServer>
  <validation validateIntegratedModeConfiguration="false" />
  <modules runAllManagedModulesForAllRequests="true" />
  <handlers>
    <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
    <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

The problem is the "*." path, which would cover /test.aspx but not simply /test.

If you change this to "*", then all requests will be handled by the ExtensionlessUrlHandler, including those to static files which will not be served anymore.

So the solution is: Remove the POST verb from the handler entries and add new entries for the ExtensionlessUrlHandler with the path set to "*" and only for POST requests:

    <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit_post" path="*" verb="POST" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit_post" path="*" verb="POST" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0_post" path="*" verb="POST" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

Ideally remove the ones you don't need (x86 and x64 in classic vs. integrated pipeline).

like image 111
Sebastian P.R. Gingter Avatar answered Oct 23 '22 09:10

Sebastian P.R. Gingter