【问题标题】:.NET MVC: How to redirect response within a helper method.NET MVC:如何在辅助方法中重定向响应
【发布时间】:2011-02-18 16:28:34
【问题描述】:

我有一些代码对于我的一些控制器操作是通用的,这些代码被提取到一个私有“帮助器”方法中,只是为了整合。该方法预计会为我“获取”一个对象,尽管它会执行一些健全性/安全性检查,并且可能会将用户重定向到其他操作。

private Thingy GetThingy(int id)
{
    var thingy = some_call_to_service_layer(id);

    if( null == thingy )
        Response.Redirect( anotherActionUrl );

    // ... many more checks, all more complex than a null check

    return thingy;
}

public ActionResult ActionOne(int id)
{
    var thingy = GetThingy(id);
    // do more stuff
    return View();
}

// ... n more actions

public ActionResult ActionM(int id)
{
    var thingy = GetThingy(id);
    // do more stuff
    return View();
}

这可以正常运行,但 Elmah 会通知我一个异常:

System.Web.HttpException: Cannot redirect after HTTP headers have been sent.

所以,我的问题是:有没有更正确的方法来做我想做的事情?本质上,我想要的只是使当前操作停止处理,而是返回一个 RedirectToRouteResult。

【问题讨论】:

    标签: asp.net-mvc redirect controller response.redirect actionmethod


    【解决方案1】:

    你不能在一个动作中调用 Response.Redirect() 而不破坏执行流程导致错误。相反,您可以返回 RedirectToRouteResult 对象或 RedirectResult 对象。在你的行动中

    return Redirect("/path");
    //or
    return RedirectToAction("actionname");
    

    在您的情况下,但是您想返回一个 Thingy 对象,您需要分离出逻辑。您可以执行以下操作(我假设您想要重定向到不同的操作,否则 Oenning 的代码将起作用

    public ActionResult Get(int id) {
    
      var thingy = GetThingy(id);
    
      var result = checkThingy(thingy);
      if (result != null) {
        return result;
      }
    
      //continue...
    }
    
    [NonAction]
    private ActionResult CheckThingy(Thingy thingy) {
    
      //run check on thingy
      //return new RedirectResult("path");
    
      //run another check
      //new RedirectResult("different/path");
    
      return null;
    }
    

    更新您可以将此代码放在扩展方法或控制器基类中

    public static class ThingyExtensions {
    
      public static ActionResult Check(this Thingy thingy) {
        //run checks here
      }
    
    }
    

    【讨论】:

    • 遗憾的是,这会导致在每个控制器中复制/粘贴更多的代码,但最终似乎是最正确的方法。我当然可以从关注点分离和避免副作用的角度来理解。
    • @James 您可以将检查代码放在扩展方法或基本控制器类中,以使其更具可重用性,但仍然会有一些重复性。
    【解决方案2】:

    一种方法是定义一个异常过滤器来为您处理重定向。

    首先,创建一个自定义异常来表示您的重定向:

    public class RedirectException : Exception {
    
        private readonly string _url;
    
        public RedirectException(string url) {
            _url = url;
        }
    
        public string Url { get { return _url; }}
    
    }
    

    然后,定义您的异常过滤器:

    public class RedirectExceptionAttribute : FilterAttribute, IExceptionFilter {
        public void OnException(ExceptionContext filterContext) {
    
            if (filterContext.ExceptionHandled) return;
            if (filterContext.Exception.GetType() != typeof(RedirectException)) return;
    
            filterContext.Result = new RedirectResult(((RedirectException)filterContext.Exception).Url);
            filterContext.ExceptionHandled = true;
        }
    }
    

    全局注册新的异常过滤器,使其适用于所有控制器操作(如果这是您想要的):

    // in FilterConfig.cs (MVC 4.0)
    public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
        filters.Add(new RedirectExceptionAttribute());
    }
    

    现在,无论你想重定向到哪里,只要抛出一个 RedirectException:

    private Thingy GetThingy(int id)
    {
        var thingy = some_call_to_service_layer(id);
    
        if( null == thingy )
            throw new RedirectException( anotherActionUrl );
    
        // ... many more checks, all more complex than a null check
    
        return thingy;
    }
    
    public ActionResult ActionOne(int id)
    {
        var thingy = GetThingy(id);
        // do more stuff
        return View();
    }
    
    // ... n more actions
    
    public ActionResult ActionM(int id)
    {
        var thingy = GetThingy(id);
        // do more stuff
        return View();
    }
    

    当然,您需要确保您没有在 try/catch 中调用 GetThingy(),或者如果您这样做了,请确保您重新抛出异常。

    【讨论】:

      【解决方案3】:

      尝试:

      return RedirectToAction("youraction", "yourcontroller");
      

      希望对你有帮助。

      【讨论】:

      • 我会,除非此代码不在“动作”中,而是从动作中调用。我正在更新问题以使其更清楚。谢谢!
      【解决方案4】:

      这个怎么样。

      public ActionResult ActionOne(int id)
      {
          var thingy = GetThingy(id);
          if (thingy == null)
              return RedirectToAction("action", "controller");
          // do more stuff
          return View();
      }
      
      private Thingy GetThingy(int id)
      {
          var thingy = some_call_to_service_layer(id);
      
          if( null == thingy )
              return null;
      
          // ... many more checks, all more complex than a null check
      
          return thingy;
      }
      

      顺便说一句,恕我直言,GetThingy(int id) 方法应该放在其他地方。也许和some_call_to_service_layer(id)在同一个地方。

      【讨论】:

      • 遗憾的是,这也可以,但随后会将我希望集中的所有复杂代码重新集中到我的每个操作中。由于大约有 6 个操作将执行相同的代码,我真的希望它在一个地方便于维护。
      • 刚刚更新了我的答案,如果你把所有这些验证放在你的服务层中,你应该能够将它集中到一个地方。我不喜欢将非操作方法放在我的控制器中,因为它会比我预期的增长得更多。
      • 我认为 GetThingy(int id) 的放置是合适的,因为它基于“网络知识”(例如当前登录的用户)进行安全检查,而服务层是只是一个代码库项目,不引用 System.Web 等。人。
      • 即使你写的例子有效,GetThingy(int id)上面的代码也会被执行。毕竟这是一种方法,退出它的唯一选择是使用return 语句。如果这些操作非常相似,另一种选择是使用单个操作(具有更多参数)而不是 6 个操作。根据新参数,您可以将流程传递给其他方法。我不知道我是否说清楚了。
      【解决方案5】:

      好吧,我想我明白了。显然,您可以任意触发 ActionResult 的执行,仅用于此类事情。所以辅助方法变成了:

      private Thingy GetThingy(int id)
      {
          var thingy = some_call_to_service_layer(id);
      
          if( null == thingy )
              RedirectToAction("myAction").ExecuteResult(ControllerContext);
      
          // ... many more checks, all more complex than a null check
      
          return thingy;
      }
      

      运行良好,并允许我将代码保留在我想要的位置。很甜。

      【讨论】:

      • 正如我在调用RedirectToAction 后在上面的评论中所说的,您的代码将继续执行,流程不会停止。不仅在 GetThingy 方法上,而且在您调用此方法的操作上。如果这对您来说不是问题,请继续 :)
      • 感谢您指出这一点。我错误地假设 ExecuteResult 将处理抛出一个特殊异常,该异常会冒泡到框架以负责停止操作。在没有这种“特殊例外”的情况下,您上面的评论当然是正确的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-25
      • 1970-01-01
      相关资源
      最近更新 更多