Fixes: U4-4223 MVC based forms with SurfaceControllers don't work when rendered in Webforms mode.
This slightly changes how the routing works in the module, now we always send the request to the RenderRouteHandler, this will first process the logic for route hijacking, then it will determine if the request was found to be a WebForms template, if so then we'll return the webforms handler. This also now allows us to have POSTed values to SurfaceController's from forms that have been rendered in Webforms whilst maintaining all viewdata, tempdata and model state on postback even when rendering in webforms!
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the handler for webforms requests
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure ModelState, ViewData and TempData is copied across
|
||||
/// </summary>
|
||||
@@ -120,5 +154,12 @@ namespace Umbraco.Web.Mvc
|
||||
if (controller != null)
|
||||
controller.DisposeIfDisposable();
|
||||
}
|
||||
|
||||
private class DummyView : IView
|
||||
{
|
||||
public void Render(ViewContext viewContext, TextWriter writer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,7 +395,8 @@ namespace Umbraco.Web
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="pcr"> </param>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
/// </remarks>
|
||||
public class UmbracoDefault : Page
|
||||
{
|
||||
/// <summary>
|
||||
/// Simply used to clear temp data
|
||||
/// </summary>
|
||||
private class TempDataController : Controller
|
||||
{
|
||||
}
|
||||
|
||||
private page _upage;
|
||||
private PublishedContentRequest _docRequest;
|
||||
bool _validateRequest = true;
|
||||
@@ -85,9 +93,19 @@ namespace umbraco
|
||||
{
|
||||
using (DisposableTimer.DebugDuration<UmbracoDefault>("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)
|
||||
|
||||
Reference in New Issue
Block a user