locked
AspCompat=true does not work with MVC RRS feed

  • Question

  • User2085202478 posted

    I couldn't find any info on this-- so I guess it is time for me to contribute once again....

    AspCompat=true does not... and cannot work under MVC. Here's my findings:

    The RenderView() method calls the System.Web.UI.Page.ProcessRequest() method directly-- never checking if it is AspCompat=true.

    It can't just simply check it in RenderView() however. To properly setup the threading, we need to go back further in the process-- back to the MvcRouteModule. When the module finds a route, that route needs to be smart enough to use a handler that impliments IHttpAsyncHandler if the ViewPage is AspCompat=true. Buuuut since the View that is used is controlled by a Controller-- we can't know until the controller calls the View.

    But do we really want the page to run in STA mode under MVC?... probably not-- since business logic goes in the controller, we'd most likely want to run the controller in AspCompat mode.

    ....hmmm. Any solutions on the horizon for this?

    Thursday, August 7, 2008 4:00 PM

Answers

  • User2085202478 posted

    OY! I finally hacked it together... well for my purpose anyhow. It is ugly... but it works.

    Basically, I just wanted to run a handler that will dish out a dynamic image that is generated with WPF (which requires STA).

    I found Jeff Prosise's Wicked Code article: Running ASMX Web Services on STA Threads which helped a lot -- but I want to use the MVC pattern so I can do the cool testing and wire it up to my cool routing. However, the way MVC is set up under the hood (see my first post) adds several new twists to Jeff's solution-- insult to injury kinda thing.

    Here's what I came up with:

    I created a class called MvcHandlerSTA that inherits from Page which I use in place of the default MvcHandler and a MvcRouteHandler to pipe it under the MVC hood and in to the framework.

    public class MvcRouteHandlerSTA : MvcRouteHandler
    {
    protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
    {
    return new MvcHandlerSTA(requestContext);
    }
    }
    public class MvcHandlerSTA : Page, IHttpAsyncHandler
    {
    public MvcHandlerSTA(RequestContext requestContext)
    {
    if (requestContext == null)
    throw new ArgumentNullException("requestContext");
    this.RequestContext = requestContext;
    }
    private ControllerBuilder _controllerBuilder;
    internal ControllerBuilder ControllerBuilder
    {
    get{return this._controllerBuilder ?? (this._controllerBuilder = ControllerBuilder.Current);}
    }
    public RequestContext RequestContext { get; set; }
    #region Make it STA
    protected override void OnInit(EventArgs e)
    {
    string requiredString = this.RequestContext.RouteData.GetRequiredString("controller");
    IControllerFactory controllerFactory = this.ControllerBuilder.GetControllerFactory();
    IController controller = controllerFactory.CreateController(this.RequestContext, requiredString);
    if (controller == null)
    throw new InvalidOperationException("Could not find controller: " + requiredString);
    try
    {
    ControllerContext controllerContext = new ControllerContext(this.RequestContext, controller);
    controller.Execute(controllerContext);
    }
    finally
    {
    controllerFactory.DisposeController(controller);
    }
    this.Context.ApplicationInstance.CompleteRequest();
    }
    public override void ProcessRequest(HttpContext httpContext)
    {
    throw new NotSupportedException("This should not get called for an STA");
    }
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
    return this.AspCompatBeginProcessRequest(context, cb, extraData);
    }
    public void EndProcessRequest(IAsyncResult result)
    {
    this.AspCompatEndProcessRequest(result);
    }
    #endregion
    void
    IHttpHandler.ProcessRequest(HttpContext httpContext)
    {
    this.ProcessRequest(httpContext);
    }
    }

     
    Next I needed a test controller:

    public class TestController : Controller
    {
    protected override void Execute(ControllerContext controllerContext)
    {
    if (controllerContext == null)
    {
    throw new ArgumentNullException("controllerContext");
    }
    this.ControllerContext = controllerContext;
    string requiredString = this.RouteData.GetRequiredString("action");
    ControllerActionInvoker invoker = this.ActionInvoker ?? new ControllerActionInvoker(controllerContext);
    if (!invoker.InvokeAction(requiredString, new Dictionary<string, object>()))
    {
    this.HandleUnknownAction(requiredString);
    }
    }
    public ActionResult Test1()
    {
    ApartmentState state = System.Threading.Thread.CurrentThread.GetApartmentState();
    Response.Write(state);
    return null;
    }
    public ActionResult Test2()
    {
    ApartmentState state = System.Threading.Thread.CurrentThread.GetApartmentState();
    Response.Write(state);
    return null;
    }
    }
     

    In the Test1() and Test2() methods, you could optionally (which I origionally did, but ommitted for this example) fire up a ViewPage as usual by calling View();
    Also notice that I did an override on the execute method. That was the last thing that bit me: the hackery I've done kills the session state-- which the controller class relies on for it's TempData. I just want to take a moment to complain about this recent addition of TempData: Really? You're going to require me to have SessionState enabled to run MVC? ICK! Anyhow, I'll move on....

    We need a TestRoute to map the test pages to the correct handlers:
    Test1 is created with the standard MvcHandler
    Test2 is created with the hacked MvcHandlerSTA
     

    public class TestRoute : RouteBase
    {
    private IRouteHandler routeHandler;
    private IRouteHandler routeHandlerSTA;

    public override RouteData GetRouteData(System.Web.HttpContextBase httpContext)
    {
    string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2);

    if (virtualPath.Equals("test1", StringComparison.InvariantCultureIgnoreCase))
    {
    if (routeHandler == null) routeHandler = new MvcRouteHandler();
    return CreateRouteData("Test", virtualPath, routeHandler);
    }
    if (virtualPath.Equals("test2", StringComparison.InvariantCultureIgnoreCase))
    {
    if (routeHandlerSTA == null) routeHandlerSTA = new MvcRouteHandlerSTA();
    return CreateRouteData("Test", virtualPath, routeHandlerSTA);
    }

    return null;
    }
    //helper method
    private RouteData CreateRouteData(string controller, string action, IRouteHandler routeHandler)
    {
    RouteData data = new RouteData(this, routeHandler);
    data.Values.Add("controller", controller);
    data.Values.Add("action", action);
    return data;
    }
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
    //we don't need this for this example
    return null;
    }
    }
     

    Now I registered the route in the global.asax:

    routes.Add(new TestRoute());
     

    .....and now run it by going to /Test1 to see it run normally (spits out MTA to the browser) and then run /Test2 where you'll see STA spit out. Mission accomplished!

    Hope this helps someone!

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, August 8, 2008 6:15 AM