aspnet / AspNetWebStack

ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core)
Other
858 stars 354 forks source link

BUG: WebAPI2 requires app pool recycle after the very first request #245

Closed Peperud closed 5 years ago

Peperud commented 5 years ago

Setup

ASP.NET WebForms - .aspx,.asmx, *.ashx + WebAPI2 with attribute routing application. 32-bit enabled.

Scenario

  1. Recycle the app pool. Optional. Only necessary if needed to make IIS release files.
  2. Delete C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\<appname> folder.

Observed

WebAPI2 routes begin throwing 404 errors, WebForms routes work fine.

Detailed Error Information:

Module   IIS Web Core
Notification   MapRequestHandler
Handler   StaticFile
Error Code   0x80070002
Requested URL   https://<redacted>
Physical Path   C:\<redacted>
Logon Method   Anonymous
Logon User   Anonymous

At this point if the app pool is recycled, WebAPI2 starts working again

Expected

WebAPI2 routes should work. It should not be necessary to recycle the app pool after the temp asp files folder is re-created.

Notes

Upon first request (regardless whether to WebForms or WebAPI2), the temp folder is re-created (as it should). Application tracing confirms that the OWIN Startup class is loaded and Configure (which calls MapHttpAttributeRoutes while wiring up the Web API) is called. IIS preload settings does not seem to matter. Recycling the app pool before the temp folder is created does not fix the issue.

Peperud commented 5 years ago

Here's another way to make 404 happen on WebAPI2 routes (attribute routing):

  1. IIS reset
  2. Drop new aspx & dll
  3. Send a request to the newly deployed page. It will be OK.
  4. Send a request to a WepAPI2 endpoint. It will FAIL.
Peperud commented 5 years ago

I am closing this one, but for the sake of anyone else that might benefit - here's the deal:

  1. The application is big and mixed. There are many dlls (>500) sitting in the bin folder.
  2. WebAPI2 controllers reside in several different assemblies.

Because of the above, the WebAPI2 routes did not work without a custom IAssembliesResolver in the WebAPI configuration. Simply put - not all assemblies in the bin folder are scanned for routes, just the ones that at that time happen to be loaded in the current AppDomain. Which 99.9% the time are just framework/system assemblies and the ones dragged in by the dll that contains the OwinStartup class.

The custom resolver looked like this:

class CustomApiResolver : DefaultAssembliesResolver
{
        public override ICollection<Assembly> GetAssemblies()
        {
            ICollection<Assembly> baseAssemblies = base.GetAssemblies();
            var apiControllerFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory + "bin\\", "*.Api.*.dll");
            foreach (var apiControllerFile in apiControllerFiles)
            {
                try
                {
                    var apiAssembly = Assembly.LoadFrom(apiControllerFile);
                    if (apiAssembly != null)
                    {
                        baseAssemblies.Add(apiAssembly);
                    }
                    else
                    {
                        this.TraceWarning($"CustomApiResolver: Failed to load assembly {apiControllerFile}.");
                    }
                }
                catch (Exception ex)
                {
                    this.TraceWarning($"CustomApiResolver: Failed to load assembly {apiControllerFile}. {ex.Message}");
                }
            }

            return baseAssemblies;
        }
    }
}

There are at least two issues with this code:

  1. There is no need (or so it seems) to return any other assemblies than the ones that contain the WebAPI controllers.
  2. Sometimes, like under the circumstances described in my first posts, (at least) some of the assemblies, containing the WebAPI controllers do get loaded in the AppDomain by the time GetAssemblies() is called. Then they are unconditionally added once again. And this causes the WebAPI routes to start throwing 404.