2016-10-21 30 views
5

Próbuję dostroić obsługę błędów w mojej aplikacji MVC.Przekazywanie modelu widoku za pomocą Server.TransferRequest()

Mam włączone błędy niestandardowe w moim web.config i dodałem następujący kod do Application_Error.

global.asax

protected void Application_Error(object sender, EventArgs e) 
{ 
    Exception exception = Server.GetLastError() as Exception; 
    if (exception != null) 
    { 
     Context.ClearError(); 
     Context.Response.TrySkipIisCustomErrors = true; 

     string path = (exception is HttpException && (exception as HttpException).GetHttpCode() == 404) ? 
      "~/Error/NotFound" : 
      "~/Error/Index"; 
     Context.Server.TransferRequest(path, false); 
    } 
} 

ErrorController.cs

[AllowAnonymous] 
public ActionResult Index() 
{ 
    Response.Clear(); 
    Response.StatusCode = 503; 
    Response.TrySkipIisCustomErrors = true; 
    return View(); 
} 

[AllowAnonymous] 
public ActionResult NotFound() 
{ 
    Response.Clear(); 
    Response.StatusCode = 404; 
    Response.TrySkipIisCustomErrors = true; 
    return View(); 
} 

Web.config

<system.web> 
    <customErrors mode="RemoteOnly" defaultRedirect="~/Error"> 
    <error statusCode="404" redirect="~/Error/NotFound"/> 
    <error statusCode="500" redirect="~/Error" /> 
    </customErrors> 
</system.web> 

Wydaje się, działa całkiem dobrze. Ale w jaki sposób mogę przekazać niektóre szczegóły błędu do mojego kontrolera błędów?

Dodatkowe informacje dotyczące wskazówek dotyczących uzyskiwania szczegółowych informacji o wyjątku w moim kontrolerze błędów dotyczących wyjątków występujących w kontrolerze.

Uwaga: nie chcę tutaj używać przekierowania. Spowoduje to, że roboty takie jak Google błędnie podadzą URL.

Odpowiedz

4

Jeśli chcesz uzyskać szczegóły błędu w kontrolerze błędów, nie usuwaj szczegółów błędów (Context.ClearError()) wewnątrz funkcji Application_Error.

Po przejściu do akcji ErrorController należy ponownie pobrać ostatni błąd, a następnie usunąć go.

HttpContext.Server.GetLastError() 

Jeśli chcesz dostać się do kontrolera i akcji nazwę, pod którą wyjątek można skorzystać z poniższego kodu, aby pobrać fragment

Request.RequestContext.RouteData.Values["controller"] 
 
Request.RequestContext.RouteData.Values["action"]

Także jeśli chcesz uruchomić ErrorController i konkretne działanie z funkcji Application_Error można zrobić coś jak poniżej:

protected void Application_Error() 
 
    { 
 

 
Exception exception = Server.GetLastError(); 
 
var httpException = exception as HttpException; 
 
Response.Clear(); 
 
Server.ClearError(); 
 
var routeData = new RouteData(); 
 
routeData.Values["controller"] = "Errors"; 
 
routeData.Values["action"] = "Common"; 
 
routeData.Values["exception"] = exception; 
 
Response.StatusCode = 500; 
 
if (httpException != null) 
 
{ 
 
    Response.StatusCode = httpException.GetHttpCode(); 
 
    switch (Response.StatusCode) 
 
{ 
 
    case 403: 
 
    routeData.Values["action"] = "Http403"; 
 
    break; 
 
    case 404: 
 
    routeData.Values["action"] = "Http404"; 
 
    break; 
 
    case 400: 
 
    routeData.Values["action"] = "Http400"; 
 
    break; 
 
    } 
 
} 
 

 
Response.TrySkipIisCustomErrors = true; 
 
IController errorsController = new ErrorsController(); 
 
var rc = new RequestContext(new HttpContextWrapper(Context), routeData); 
 

 
/* This will run specific action without redirecting */ 
 
errorsController.Execute(rc); 
 

 
}

Jeśli chcesz przekazać błędu jako obiekt do błędu sterownika następnie można dodać dodatkowe dane trasy jak poniżej

routeData.Values["errorDetail"] = httpException;

3

Czy dodanie parametrów pomogłoby?

  • publiczne ActionResult Index (errorMessage string)
  • publiczne ActionResult NOTFOUND (errorMessage ciąg)

A potem w Application_Error może wyglądać mniej więcej tak -

protected void Application_Error(object sender, EventArgs e) 
{ 
    Exception exception = Server.GetLastError() as Exception; 
    if (exception != null) 
    { 
     Context.ClearError(); 
     Context.Response.TrySkipIisCustomErrors = true; 

     string path = (exception is HttpException && (exception as HttpException).GetHttpCode() == 404) ? 
      "~/Error/NotFound?errorMessage="+exception.Message : 
      "~/Error/Index?errorMessage="+exception.Message; 
     Context.Server.TransferRequest(path, false); 
    } 
} 

można dołączyć dodatkowy parametr jak na Twoje wymagania. Nie jest to jednak najlepsze podejście.

+1

To jedno podejście, ale z pewnością mniej niż idealne. Na przykład niektóre wyjątki mają wewnętrzne wyjątki. Zawierają również dodatkowe informacje. –

1

Jest to set-up, które Mam. Nie przekieruje i obsługuje zarówno aplikację, jak i niektóre skonfigurowane błędy IIS w tym samym miejscu. Możesz również przekazać dowolne informacje do kontrolera błędu.

W Web.config:

<system.web> 
    <customErrors mode="Off" /> 
    ... 
</system.web> 

<system.webServer> 
    <httpErrors errorMode="Custom" existingResponse="Auto"> 
    <remove statusCode="403" /> 
    <remove statusCode="404" /> 
    <remove statusCode="500" /> 
    <error statusCode="403" responseMode="ExecuteURL" path="/Error/Display/403" /> 
    <error statusCode="404" responseMode="ExecuteURL" path="/Error/Display/404" /> 
    <error statusCode="500" responseMode="ExecuteURL" path="/Error/Display/500" /> 
    </httpErrors> 
... 
</system.webServer> 

W ErrorController (pokazano tylko sygnatury metody dla uproszczenia)

// This one gets called from Application_Error 
// You can add additional parameters to this action if needed 
public ActionResult Index(Exception exception) 
{ 
    ... 
} 

// This one gets called by IIS (see Web.config) 
public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode) 
{ 
    ... 
} 

Dodatkowo mam ErrorViewModel i widokiem Index.

W Application_Error:

protected void Application_Error(object sender, EventArgs e) 
{ 
    var exception = Server.GetLastError(); 

    var httpContext = new HttpContextWrapper(Context); 

    httpContext.ClearError(); 

    var routeData = new RouteData(); 
    routeData.Values["controller"] = "Error"; 
    routeData.Values["action"] = "Index"; 
    routeData.Values["exception"] = exception; 
    // Here you can add additional route values as necessary. 
    // Make sure you add them as parameters to the action you're executing 

    IController errorController = DependencyResolver.Current.GetService<ErrorController>(); 
    var context = new RequestContext(httpContext, routeData); 
    errorController.Execute(context); 
} 

Jak dotąd, jest to podstawowa konfiguracja, że ​​mam. To nie wykona przekierowania (działanie kontrolera błędu jest wykonywane z Application_Error) i obsługuje wyjątki kontrolera, a także, na przykład, IIS 404 (takie jak twojastrona.com/blah.html).

Od tego momentu wszystko, co stanie się wewnątrz twojego ErrorController, będzie oparte na twoich potrzebach.


Jako przykład, dodam kilka dodatkowych szczegółów dotyczących mojej implementacji. Jak już mówiłem, mam ErrorViewModel.

Moja ErrorViewModel:

public class ErrorViewModel 
{ 
    public string Title { get; set; } 

    public string Text { get; set; } 

    // This is only relevant to my business needs 
    public string ContentResourceKey { get; set; } 

    // I am including the actual exception in here so that in the view, 
    // when the request is local, I am displaying the exception for 
    // debugging purposes. 
    public Exception Exception { get; set; } 
} 

Moi ErrorController (istotne części):

public ActionResult Index(Exception exception) 
{ 
    ErrorViewModel model; 

    var statusCode = HttpStatusCode.InternalServerError; 

    if (exception is HttpException) 
    { 
     statusCode = (HttpStatusCode)(exception as HttpException).GetHttpCode(); 

     // More details on this below 
     if (exception is DisplayableException) 
     { 
      model = CreateErrorModel(exception as DisplayableException); 
     } 
     else 
     { 
      model = CreateErrorModel(statusCode); 
      model.Exception = exception; 
     } 
    } 
    else 
    { 
     model = new ErrorViewModel { Exception = exception }; 
    } 

    return ErrorResult(model, statusCode); 
} 

public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode) 
{ 
    var model = CreateErrorModel(statusCode); 

    return ErrorResult(model, statusCode); 
} 

private ErrorViewModel CreateErrorModel(HttpStatusCode statusCode) 
{ 
    var model = new ErrorViewModel(); 

    switch (statusCode) 
    { 
     case HttpStatusCode.NotFound: 
      // Again, this is only relevant to my business logic. 
      // You can do whatever you want here 
      model.ContentResourceKey = "error-page-404"; 
      break; 
     case HttpStatusCode.Forbidden: 
      model.Title = "Unauthorised."; 
      model.Text = "Your are not authorised to access this resource."; 
      break; 

     // etc... 
    } 

    return model; 
} 


private ErrorViewModel CreateErrorModel(DisplayableException exception) 
{ 
    if (exception == null) 
    { 
     return new ErrorViewModel(); 
    } 

    return new ErrorViewModel 
    { 
     Title = exception.DisplayTitle, 
     Text = exception.DisplayDescription, 
     Exception = exception.InnerException 
    }; 
} 

private ActionResult ErrorResult(ErrorViewModel model, HttpStatusCode statusCode) 
{ 
    HttpContext.Response.Clear(); 
    HttpContext.Response.StatusCode = (int)statusCode; 
    HttpContext.Response.TrySkipIisCustomErrors = true; 

    return View("Index", model); 
} 

W niektórych przypadkach muszę wyświetlać niestandardowy komunikat, gdy wystąpi błąd.Mam niestandardowy wyjątek do tego celu:

[Serializable] 
public class DisplayableException : HttpException 
{ 
    public string DisplayTitle { get; set; } 

    public string DisplayDescription { get; set; } 

    public DisplayableException(string title, string description) 
     : this(title, description, HttpStatusCode.InternalServerError, null, null) 
    { 
    } 

    public DisplayableException(string title, string description, Exception exception) 
     : this(title, description, HttpStatusCode.InternalServerError, null, exception) 
    { 
    } 

    public DisplayableException(string title, string description, string message, Exception exception) 
     : this(title, description, HttpStatusCode.InternalServerError, message, exception) 
    { 
    } 

    public DisplayableException(string title, string description, HttpStatusCode statusCode, string message, Exception inner) 
     : base((int)statusCode, message, inner) 
    { 
     DisplayTitle = title; 
     DisplayDescription = description; 
    } 
} 

Potem używać go tak:

catch(SomeException ex) 
{ 
    throw new DisplayableException("My Title", "My custom display message", "An error occurred and I must display something", ex) 
} 

w moim ErrorController I obsłużyć ten wyjątek oddzielnie, ustawianie ErrorViewModel „s Title i Text właściwości z tego DisplayableException.

+0

Wygląda na to, że Twój kod wymaga trochę więcej sprawdzania błędów, na przykład sprawdzania 'null' w kilku miejscach. Jednym z miejsc, w których nie chcesz wprowadzać nowych wyjątków, jest kod obsługi błędów. –

+0

Całkowicie zgadzam się, że nie chcemy uzyskać wyjątków w kodzie obsługi wyjątku. Jednak patrząc wstecz na mój kod, myślę, że jedynymi dwoma miejscami, które mogą potencjalnie powodować problemy są metoda "ExecuteErrorAction" (w module HttpModule) lub przeciążenie "CreateErrorModel (DisplayableException)" (w 'ErrorController'). W pierwszym przypadku problem może powstać, jeśli nie można utworzyć nowej instancji 'ErrorController' (co moim zdaniem jest mało prawdopodobne i byłoby to natychmiast oczywiste podczas dev). Drugi z nich mógłby zawieść w przypadku mało prawdopodobnego parametru "null". Co myślisz? –

+0

Wiem, że "mało prawdopodobne" nie jest generalnie dobrym powodem, aby nie robić czegoś, ale w przypadku "ExecuteErrorAction" Myślę, że jest wystarczająco bezpieczne, aby założyć, że instancja zostanie utworzona. Dla drugiego przypadku (z 'CreateErrorModel (DisplayableException)'), ok, jest to bardziej możliwe, aby to się stało. Mówię "mało prawdopodobne" ze względu na sposób, w jaki metoda jest wywoływana w tym konkretnym przypadku. Wiem jednak, że nie może to być dobra argumentacja, ponieważ metoda nie powinna być świadoma tego, jak jest wywoływana. Dotyczy to również "ExecuteErrorAction". Zaktualizuję kod, aby to odzwierciedlić. –

2

Prostym rozwiązaniem byłoby przekazać wyjątku lub ViewModel tak:

W swojej application_error:

HttpContext.Current.Items["Exception"] = exception; 

w kontrolerze błędzie:

var exception = HttpContext.Current.Items["Exception"] as Exception; 

zastrzeżenie: nie jestem fan używania HttpContext.

+0

Dlaczego nie fanem używania 'HttpContext'? –

+0

System.Web to problematyczny monolityczny interfejs API, który jest zastępowany, ale bardziej do punktu, w którym HttpContext jest stanem pojedynczego/globalnego, który jest bardzo trudny do przetestowania –

0

Spróbuj tego;

Następnie napisz mały kod biznesowy w metodzie Index w ErrorController. (jeśli kod stanu == 400 ... jeszcze ...)