.NET MVC: как перенаправить ответ внутри вспомогательного метода - PullRequest
2 голосов
/ 18 февраля 2011

У меня есть некоторый код, который является общим для ряда моих действий контроллера, извлеченных в частный «вспомогательный» метод только для консолидации. Ожидается, что метод «получит» объект для меня, хотя он выполняет некоторые проверки работоспособности / безопасности и потенциально может перенаправить пользователя на другие действия.

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

Это работает правильно, за исключением того, что Элма уведомляет меня об исключении:

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

Итак, мой вопрос: есть ли более правильный способ сделать то, что я пытаюсь сделать? По сути, все, что я хочу, это заставить текущее действие остановить обработку и вместо этого вернуть RedirectToRouteResult.

Ответы [ 5 ]

2 голосов
/ 16 сентября 2013

Один из способов сделать это - определить фильтр исключений, который будет обрабатывать перенаправление для вас.

Сначала создайте пользовательское исключение для представления вашего перенаправления:

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

Конечно, вам нужно убедиться, что вы не вызываете GetThingy() из попытки / улова, или, если вы это сделаете, убедитесь, что вы повторно выбросили исключение.

2 голосов
/ 18 февраля 2011

Нельзя вызывать 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;
}

Обновление Вы можете поместить этот код в метод расширения или базовый класс Controller

public static class ThingyExtensions {

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

}
0 голосов
/ 18 февраля 2011

Хорошо, думаю, я понял это.По-видимому, вы можете произвольно инициировать выполнение 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;
}

Работает хорошо и позволяет мне хранить код там, где я хотел.Довольно мило.

0 голосов
/ 18 февраля 2011

Что насчёт этого.

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).

0 голосов
/ 18 февраля 2011

попробуй:

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

Надеюсь, это поможет.

...