diff --git a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs index aa88de9348..13267dd241 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs @@ -118,19 +118,15 @@ namespace Umbraco.Web.Macros routeVals.Values.Add("action", "Index"); routeVals.DataTokens.Add("umbraco-context", umbCtx); //required for UmbracoViewPage - //lets render this controller as a child action if we are currently executing using MVC - //(otherwise don't do this since we're using webforms) - var mvcHandler = http.CurrentHandler as MvcHandler; + //lets render this controller as a child action var viewContext = new ViewContext {ViewData = new ViewDataDictionary()};; - if (mvcHandler != null) - { - //try and extract the current view context from the route values, this would be set in the UmbracoViewPage. - if (mvcHandler.RequestContext.RouteData.DataTokens.ContainsKey(Umbraco.Web.Mvc.Constants.DataTokenCurrentViewContext)) - { - viewContext = (ViewContext) mvcHandler.RequestContext.RouteData.DataTokens[Umbraco.Web.Mvc.Constants.DataTokenCurrentViewContext]; - } - routeVals.DataTokens.Add("ParentActionViewContext", viewContext); - } + //try and extract the current view context from the route values, this would be set in the UmbracoViewPage or in + // the UmbracoPageResult if POSTing to an MVC controller but rendering in Webforms + if (http.Request.RequestContext.RouteData.DataTokens.ContainsKey(Mvc.Constants.DataTokenCurrentViewContext)) + { + viewContext = (ViewContext)http.Request.RequestContext.RouteData.DataTokens[Mvc.Constants.DataTokenCurrentViewContext]; + } + routeVals.DataTokens.Add("ParentActionViewContext", viewContext); var request = new RequestContext(http, routeVals); string output; diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index bfa2063f7a..04ae377027 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Text; using System.Web; +using System.Web.Compilation; using System.Web.Mvc; using System.Web.Routing; using System.Web.SessionState; @@ -359,8 +360,9 @@ namespace Umbraco.Web.Mvc // so we have a template, so we should have a rendering engine if (pcr.RenderingEngine == RenderingEngine.WebForms) // back to webforms ? - return (global::umbraco.UmbracoDefault)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault)); - else if (pcr.RenderingEngine != RenderingEngine.Mvc) // else ? + return GetWebFormsHandler(); + + if (pcr.RenderingEngine != RenderingEngine.Mvc) // else ? return new PublishedContentNotFoundHandler("In addition, no rendering engine exists to render the custom 404."); return null; @@ -385,6 +387,14 @@ namespace Umbraco.Web.Mvc return HandlePostedValues(requestContext, postedInfo); } + //Now we can check if we are supposed to render WebForms when the route has not been hijacked + if (publishedContentRequest.RenderingEngine == RenderingEngine.WebForms + && publishedContentRequest.HasTemplate + && routeDef.HasHijackedRoute == false) + { + return GetWebFormsHandler(); + } + //here we need to check if there is no hijacked route and no template assigned, if this is the case //we want to return a blank page, but we'll leave that up to the NoTemplateHandler. if (!publishedContentRequest.HasTemplate && !routeDef.HasHijackedRoute) @@ -435,9 +445,20 @@ namespace Umbraco.Web.Mvc return new UmbracoMvcHandler(requestContext); } + /// + /// Returns the handler for webforms requests + /// + /// + internal static IHttpHandler GetWebFormsHandler() + { + return (global::umbraco.UmbracoDefault)BuildManager.CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault)); + } + private SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext, string controllerName) { return _controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index f0904c64a4..d6fe6964f6 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -1,7 +1,10 @@ using System; +using System.IO; +using System.Web; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; +using umbraco.presentation.install.utills; namespace Umbraco.Web.Mvc { @@ -17,23 +20,33 @@ namespace Umbraco.Web.Mvc ValidateRouteData(context.RouteData); var routeDef = (RouteDefinition)context.RouteData.DataTokens["umbraco-route-def"]; - var factory = ControllerBuilder.Current.GetControllerFactory(); - context.RouteData.Values["action"] = routeDef.ActionName; - - ControllerBase controller = null; - - try + //Special case, if it is webforms but we're posting to an MVC surface controller, then we + // need to return the webforms result instead + if (routeDef.PublishedContentRequest.RenderingEngine == RenderingEngine.WebForms) { - controller = CreateController(context, factory, routeDef); - - CopyControllerData(context, controller); - - ExecuteControllerAction(context, controller); + EnsureViewContextForWebForms(context); + var webFormsHandler = RenderRouteHandler.GetWebFormsHandler(); + webFormsHandler.ProcessRequest(HttpContext.Current); } - finally + else { - CleanupController(controller, factory); + var factory = ControllerBuilder.Current.GetControllerFactory(); + context.RouteData.Values["action"] = routeDef.ActionName; + ControllerBase controller = null; + + try + { + controller = CreateController(context, factory, routeDef); + + CopyControllerData(context, controller); + + ExecuteControllerAction(context, controller); + } + finally + { + CleanupController(controller, factory); + } } } @@ -70,6 +83,27 @@ namespace Umbraco.Web.Mvc } } + /// + /// When POSTing to MVC but rendering in WebForms we need to do some trickery, we'll create a dummy viewcontext with all of the + /// current modelstate, tempdata, viewdata so that if we're rendering partial view macros within the webforms view, they will + /// get all of this merged into them. + /// + /// + private static void EnsureViewContextForWebForms(ControllerContext context) + { + var tempDataDictionary = new TempDataDictionary(); + tempDataDictionary.Save(context, new SessionStateTempDataProvider()); + var viewCtx = new ViewContext(context, new DummyView(), new ViewDataDictionary(), tempDataDictionary, new StringWriter()); + + viewCtx.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); + + foreach (var d in context.Controller.ViewData) + viewCtx.ViewData[d.Key] = d.Value; + + //now we need to add it to the special route tokens so it's picked up later + context.HttpContext.Request.RequestContext.RouteData.DataTokens[Constants.DataTokenCurrentViewContext] = viewCtx; + } + /// /// Ensure ModelState, ViewData and TempData is copied across /// @@ -120,5 +154,12 @@ namespace Umbraco.Web.Mvc if (controller != null) controller.DisposeIfDisposable(); } + + private class DummyView : IView + { + public void Render(ViewContext viewContext, TextWriter writer) + { + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 8f7e5fc208..3a616e22bc 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -395,7 +395,8 @@ namespace Umbraco.Web #endregion /// - /// Rewrites to the correct Umbraco handler, either WebForms or Mvc + /// Rewrites to the Umbraco handler - we always send the request via our MVC rendering engine, this will deal with + /// requests destined for webforms. /// /// /// @@ -406,49 +407,24 @@ namespace Umbraco.Web // rewritten url, but this is not what we want! // read: http://forums.iis.net/t/1146511.aspx - string query = pcr.Uri.Query.TrimStart(new[] { '?' }); + var query = pcr.Uri.Query.TrimStart(new[] { '?' }); - string rewritePath; + // GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any) + var rewritePath = GlobalSettings.Path.TrimEnd(new[] { '/' }) + "/RenderMvc"; + // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc) + context.RewritePath(rewritePath, "", query, false); - if (pcr.RenderingEngine == RenderingEngine.Unknown) - { - // Unkwnown means that no template was found. Default to Mvc because Mvc supports hijacking - // routes which sometimes doesn't require a template since the developer may want full control - // over the rendering. Can't do it in WebForms, so Mvc it is. And Mvc will also handle what to - // do if no template or hijacked route is exist. - pcr.RenderingEngine = RenderingEngine.Mvc; - } - - switch (pcr.RenderingEngine) - { - case RenderingEngine.Mvc: - // GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any) - rewritePath = GlobalSettings.Path.TrimEnd(new[] { '/' }) + "/RenderMvc"; - // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc) - context.RewritePath(rewritePath, "", query, false); - - //if it is MVC we need to do something special, we are not using TransferRequest as this will - //require us to rewrite the path with query strings and then reparse the query strings, this would - //also mean that we need to handle IIS 7 vs pre-IIS 7 differently. Instead we are just going to create - //an instance of the UrlRoutingModule and call it's PostResolveRequestCache method. This does: - // * Looks up the route based on the new rewritten URL - // * Creates the RequestContext with all route parameters and then executes the correct handler that matches the route - //we also cannot re-create this functionality because the setter for the HttpContext.Request.RequestContext is internal - //so really, this is pretty much the only way without using Server.TransferRequest and if we did that, we'd have to rethink - //a bunch of things! - var urlRouting = new UrlRoutingModule(); - urlRouting.PostResolveRequestCache(context); - break; - - case RenderingEngine.WebForms: - rewritePath = "~/default.aspx"; - // rewrite the path to the path of the handler (i.e. default.aspx) - context.RewritePath(rewritePath, "", query, false); - break; - - default: - throw new Exception("Invalid RenderingEngine."); - } + //if it is MVC we need to do something special, we are not using TransferRequest as this will + //require us to rewrite the path with query strings and then reparse the query strings, this would + //also mean that we need to handle IIS 7 vs pre-IIS 7 differently. Instead we are just going to create + //an instance of the UrlRoutingModule and call it's PostResolveRequestCache method. This does: + // * Looks up the route based on the new rewritten URL + // * Creates the RequestContext with all route parameters and then executes the correct handler that matches the route + //we also cannot re-create this functionality because the setter for the HttpContext.Request.RequestContext is internal + //so really, this is pretty much the only way without using Server.TransferRequest and if we did that, we'd have to rethink + //a bunch of things! + var urlRouting = new UrlRoutingModule(); + urlRouting.PostResolveRequestCache(context); } /// diff --git a/src/Umbraco.Web/umbraco.presentation/default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/default.aspx.cs index e4d7c546d6..c7e4cd5eda 100644 --- a/src/Umbraco.Web/umbraco.presentation/default.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/default.aspx.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Web; +using System.Web.Mvc; using System.Web.Routing; using System.Web.UI; using System.IO; @@ -29,6 +30,13 @@ namespace umbraco /// public class UmbracoDefault : Page { + /// + /// Simply used to clear temp data + /// + private class TempDataController : Controller + { + } + private page _upage; private PublishedContentRequest _docRequest; bool _validateRequest = true; @@ -85,9 +93,19 @@ namespace umbraco { using (DisposableTimer.DebugDuration("Init")) { - base.OnInit(e); + //This is a special case for webforms since in some cases we may be POSTing to an MVC controller, adding TempData there and then redirecting + // to a webforms handler. In that case we need to manually clear out the tempdata ourselves since this is normally the function of the base + // MVC controller instance and since that is not executing, we'll deal with that here. + //Unfortunately for us though, we can never know which TempDataProvider was used for the previous controller, by default it is the sessionstateprovider + // but since the tempdataprovider is not a global mvc thing, it is only a per-controller thing, we can only just assume it will be the sessionstateprovider + var provider = new SessionStateTempDataProvider(); + //We create a custom controller context, the only thing that is referenced from this controller context in the sessionstateprovider is the HttpContext.Session + // so we just need to ensure that is set + var ctx = new ControllerContext(new HttpContextWrapper(Context), new RouteData(), new TempDataController()); + provider.LoadTempData(ctx); + //This is only here for legacy if people arent' using master pages... //TODO: We need to test that this still works!! Or do we ?? if (!UmbracoSettings.UseAspNetMasterPages)