问题
We have recently upgraded our code base from .Net 4.0 to .Net 4.5.1 and from MVC 2.0 to MVC 5.2.2.
We have a custom method in our base controller class which allowed us to update multiple parts of our views within a single request. Since upgrading, this no longer works.
Original Code:
protected void IncludeAction(string actionName, string controllerName, object routeValues)
{
//Get Url
RouteValueDictionary routes = null;
if (routeValues != null)
routes = new RouteValueDictionary(routeValues);
else
routes = new RouteValueDictionary();
routes.Add("Action", actionName);
if (!string.IsNullOrEmpty(controllerName))
routes.Add("Controller", controllerName);
else
routes.Add("Controller", this.ControllerContext.RouteData.Values["Controller"].ToString());
var url = RouteTable.Routes.GetVirtualPath(this.ControllerContext.RequestContext, routes).VirtualPath;
//Rewrite path
System.Web.HttpContext.Current.RewritePath(url, false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(System.Web.HttpContext.Current);
}
We receive errors on the httpHandler.ProcessRequest
call. We used this technique in a number of places. After much googling it seemed that we should use Server.TransferRequest
instead.
New Code
protected void IncludeAction(string actionName, string controllerName, object routeValues)
{
//Get Url
RouteValueDictionary routes = null;
if (routeValues != null)
routes = new RouteValueDictionary(routeValues);
else
routes = new RouteValueDictionary();
routes.Add("Action", actionName);
if (!string.IsNullOrEmpty(controllerName))
routes.Add("Controller", controllerName);
else
routes.Add("Controller", this.ControllerContext.RouteData.Values["Controller"].ToString());
var url = RouteTable.Routes.GetVirtualPath(this.ControllerContext.RequestContext, routes).VirtualPath;
//Rewrite path
System.Web.HttpContext.Current.RewritePath(url, false);
System.Web.HttpContext.Current.Server.TransferRequest(url, true);
}
When called from code like this:
IncludeAction("OptInBanner", "Person");
IncludeAction("NavMenu", "Person");
return Transfer(returnurl);
Our new code generates this error:
Type:
System.InvalidOperationException
Message:
TransferRequest cannot be invoked more than once.
Stack Trace:
at System.Web.HttpServerUtility.TransferRequest(String path, Boolean preserveForm)
at MyProject.MyNamspace.MyBaseController.IncludeAction(String actionName, String controllerName, Object routeValues)
at MyProject.MyNamspace.MyBaseController.IncludeAction(String actionName, String controllerName)
at MyProject.MyNamspace.MyController.MyAction(Boolean myChoice, String returnurl)
at .lambda_method(Closure , ControllerBase , Object[] )
Since the message plainly says I cannot call TransferRequest more than once, but my code needs to execute two controller actions in addition to redirecting and performing a third action, I thought I'd revert to the old code. However, that generates this error:
Type:
System.InvalidOperationException
Message:
'HttpContext.SetSessionStateBehavior' can only be invoked before 'HttpApplication.AcquireRequestState' event is raised.
Stack Trace:
at System.Web.Routing.UrlRoutingHandler.ProcessRequest(HttpContextBase httpContext)
at MyProject.MyNamspace.MyBaseController.IncludeAction(String actionName, String controllerName, Object routeValues)
at MyProject.MyNamspace.MyBaseController.IncludeAction(String actionName, String controllerName)
at MyProject.MyNamspace.MyController.MyAction(Boolean myChoice, String returnurl)
at .lambda_method(Closure , ControllerBase , Object[] )
For this function, how can I retain the original behavior, without errors, that we had under .Net 4.0 and MVC 2.0 while using the newer framework and MVC?
回答1:
After a lot of research, I came up with this:
protected void IncludeAction(string actionName, string controllerName, object routeValues)
{
string targetController = null;
if (!string.IsNullOrWhiteSpace(controllerName))
{
targetController = controllerName;
}
else
{
targetController = this.ControllerContext.RouteData.Values["Controller"].ToString();
}
HtmlHelper htmlHelper = new HtmlHelper(
new ViewContext(
this.ControllerContext,
new WebFormView(this.ControllerContext, actionName),
this.ViewData,
this.TempData,
this.Response.Output
),
new ViewPage()
);
htmlHelper.RenderAction(actionName, targetController, routeValues);
}
The HtmlHelper is constructed with a new ViewContext. The new ViewContext is constructed with much of the data from the current ControllerContext and the current Response object's TextWriter (this.Response.Output
).
Calling RenderAction
on the HtmlHelper will render my chosen Controller-Action results to the Response stream, allowing us to call multiple Controller Actions from other Controller Actions and letting our client side AJAX framework apply the results to the correct portions of the page.
For places where it is okay to throw out the pending Response stream, we can still use Server.TransferRequest
. But this now allows us to append multiple Controller-Action results to the same response stream, from the server.
来源:https://stackoverflow.com/questions/26533724/execute-multiple-controller-actions-in-one-call-with-mvc-net-5