views:

843

answers:

2

Problem

I am trying to take an existing ASP.NET web application and manually migrate it over to use MVC 2 (I am currently running the RC). I followed several steps (I'll list in a moment) and seemed to have it working, but then I noticed I can't set the AuthorizeAttribute because User is null on the controller. Then I noticed that when I navigate to an action, none of the HttpApplication's usual lifecycle events are being raised (such as BeginRequest, etc.). I figure that's probably related to why User principal is null. The web host is a local IIS7 instance (on my Vista workstation).

I created a brand new MVC 2 web application to use as a reference while migrating. It runs just fine, raising application events and populating the User principal as I would expect.

If you're feeling like punishing yourself, read on for full details (to the best of my ability) below.


Migration Steps

  1. Ensure the App Pool for the application directory is .NET 2.0 and Integrated
  2. Referenced System.Web.Abstractions (v.3.5.0.0), System.Web.routing (v3.5.0.0), and System.Web.Mvc (v2.0.0.0) in my web project. Now, I added System.Web.Mvc as a local reference to make it easier on integration and deployment.
  3. Modified the csproj to enable the MVC VS add-in goodness (See this article)
  4. Added the Controllers and Views directories in the project, added in the /Views/web.config from my example MVC application.
  5. Modified my web.config (NOTE: I have other crap in modules and handlers, but I hid that for simplicity and security sake... but those may very well be part of the problem):

The compilation section:

<compilation defaultLanguage="c#" debug="true">
      <assemblies>
        <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
      </assemblies>
    </compilation>

The pages section:

<pages enableEventValidation="false"
       pageBaseType="MyAssembly.ThemedBasePage">
  <controls>
    <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  </controls>

  <namespaces>
    <add namespace="System.Web.Mvc"/>
    <add namespace="System.Web.Mvc.Ajax"/>
    <add namespace="System.Web.Mvc.Html"/>
    <add namespace="System.Web.Routing"/>
    <add namespace="System.Linq"/>
    <add namespace="System.Collections.Generic"/>
  </namespaces>
</pages>

The system.webServer/modules section (remember, IIS7 integrated):

<modules>
  <remove name="ScriptModule" />
  <remove name="UrlRoutingModule" />
  <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

</modules>

The system.webServer/handlers section:

<handlers>
      <remove name="WebServiceHandlerFactory-Integrated"/>
      <remove name="ScriptHandlerFactory"/>
      <remove name="ScriptHandlerFactoryAppServices"/>
      <remove name="ScriptResource"/>
      <remove name="MvcHttpHandler"/>
      <remove name="UrlRoutingHandler"/>
      <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      <add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </handlers>
  1. Updated my global.asax to register routes, and ignore stuff that would interfere with my legacy stuff:

    private static void RegisterRoutes(RouteCollection routes)
    {
      // place any routes here you need to ignore, whether they
      // be legacy or legitimate resources.
      routes.IgnoreRoute(""); // i currently have functionality at "/", and this route frees up the root to be used by my Default.aspx
      routes.IgnoreRoute("{webForms}.aspx/{*pathInfo}");
      routes.IgnoreRoute("{webServices}.asmx/{*pathInfo}");
      routes.IgnoreRoute("ajaxpro/{*pathInfo}");
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
    
      routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = ""} // Parameter defaults
      );
    }   
    
    
    protected void Application_Start()
    {
       // legacy crap here
    
    
       AreaRegistration.RegisterAllAreas();    
       RegisterRoutes(RouteTable.Routes);
    }
    

Additional Info

I also use the latest release version of Autofac (v1.4.5.676) and utilize their Web integration handlers. I've checked both ways, with all autofact stuff completely removed/disabled and with everything set up the way I want; no effect on the issue either way.

I've also tried this with and without my specialized super-sweet IgnoreRoute settings. No effect.

Also, I'd like to make it clear that the routes do seem to work, I get sent to my controllers and actions properly, I just have a Null principal at HttpContext.Current.User and it seems to totally not raise any application lifecycle events. If I didn't have to, you know, get the current principal or do any pesky authorization, i'd never have known anything was wrong ;)

And yes, the regular ASPX pages work correctly and application lifecycle events are raised normally.

Here's an example of a simple test controller I made that fails:

    [Authorize]
    public class FartsController : Controller
    {
        //
        // GET: /Farts/
        public ActionResult Index()
        {
            return View();
        }

    }

yielding the following exception:

[NullReferenceException: Object reference not set to an instance of an object.]
   System.Web.Mvc.AuthorizeAttribute.AuthorizeCore(HttpContextBase httpContext) +48
   System.Web.Mvc.AuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext) +35
   System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor) +103
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +316
   System.Web.Mvc.Controller.ExecuteCore() +104
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +36
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +7
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__4() +34
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +53
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +43
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +7
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8678910
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

This sucks :(. Thanks for reading my short story.

A: 

This may or may not help: I just finished reviewing the Nerd Dinner code and theycovered this topic specifically in the area of unit testing. Try browsering http://tinyurl.com/aspnetmvc and scrolling nearly to the bottom where they cover the sample unit tests. They have some fake out magic that makes their sample Account controller work to let you supply a fake user ID. Might work for you.

No Refunds No Returns
+1  A: 

UPDATED Alright, I think the issue is a failure in my understanding of how MVC really works (which was not clear until I pulled back and with a fresh mind compared the differences between ASP.NET and MVC web configs).

The reason for my problem is because the following setting is required for MVC applications to run properly in integrated mode (the important part is runAllManagedModulesForAllRequests="true"):

<system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true">
        <remove name="ScriptModule"/>
        <remove name="UrlRoutingModule"/>
        <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

       <!-- omitted for clarify -->
    </modules>
    <handlers>
        <!-- omitted for clarity -->
        <add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
    </handlers>
</system.webServer>

Where this causes an issue for me would be the fact that my ASP.NET application uses a default authorization rule denying access to unauthenticated users:

  <system.web>
    <authorization>
      <deny users="?"/>
    </authorization>
  </system.web>

Whereas MVC does not encourage this practice, but rather using AuthorizeAttribute or some other filter.

Until I move all of my existing code over to MVC (which will take quite some time), I will have to come up with a clever solution for keeping the ASP.NET-style authorization in place to push out unauthenticated requests (maybe a custom HttpModule?) for secured resources (which for us is everything except images, javascript, css, static html, and the login/logout pages), and remove the above from the web.config.

Furthermore, my application, being originally just ASP.NET, assumed that Application_BeginRequest (and the likes) would only be significant resource requests, like ASPX, ASMX, ASHX, and AXD pages. So, I need to adjust any application events to stop any resource intensive processing (security checks that hit the DB, etc.) for static resources that I don't care about (again, things like images, etc.).

SUMMARY

runAllManagedModulesForAllRequests="true" is required for MVC in integrated mode. If your ASP.NET application uses location-based authorization heavily, and/or does a lot of processing on request lifecycle events, you're going to have some extra work cut out for you to get MVC working along side your ASP.NET forms.

HackedByChinese
Thank you! just spend a loooong time not spotting the missing runAllManagedModulesForAllRequests="true"!
Whisk