使用MVC.Net 5在一次调用中执行多个控制器操作

时间:2014-10-23 17:20:25

标签: c# invalidoperationexception asp.net-mvc-5.2

我们最近将代码库从.Net 4.0升级到.Net 4.5.1,从MVC 2.0升级到MVC 5.2.2。

我们的基本控制器类中有一个自定义方法,它允许我们在单个请求中更新视图的多个部分。升级后,这不再有效。

原始代码:

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);
}

我们在httpHandler.ProcessRequest电话上收到错误消息。我们在很多地方使用过这种技术。经过大量的谷歌搜索后,似乎我们应该使用Server.TransferRequest代替。

新代码

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);
}

从这样的代码调用时:

IncludeAction("OptInBanner", "Person");
IncludeAction("NavMenu", "Person");
return Transfer(returnurl);

我们的新代码会生成此错误:

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[] )

由于消息明确表示我不能多次调用TransferRequest,但我的代码除了重定向和执行第三个动作之外还需要执行两个控制器动作,我以为我将恢复旧代码。但是,这会产生此错误:

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[] )

对于这个函数,如何在使用更新的框架和MVC时保留我们在.Net 4.0和MVC 2.0下的原始行为而没有错误?

1 个答案:

答案 0 :(得分:0)

经过大量研究后,我想出了这个:

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);
}

HtmlHelper使用新的ViewContext构建。使用当前ControllerContext和当前Response对象的TextWriter(this.Response.Output)中的大部分数据构造新的ViewContext。

在HtmlHelper上调用RenderAction会将我选择的Controller-Action结果呈现给Response流,允许我们从其他Controller Actions调用多个Controller Actions,并让我们的客户端AJAX框架将结果应用到正确的部分页面。

对于可以丢弃待处理的Response流的地方,我们仍然可以使用Server.TransferRequest。但现在这允许我们从服务器向同一个响应流附加多个Controller-Action结果。