2016-02-09 5 views
1

Я редактирую старый проект, который использует MVC3. Он имеет Global.asax файл, который обрабатывает ошибки, как это:Обработка ошибок MVC3 и WebApi

protected void Application_Error(object sender, EventArgs e) 
{ 
    var currentController = " "; 
    var currentAction = " "; 
    var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(Context)); 

    if (currentRouteData != null) 
    { 
     if (currentRouteData.Values["controller"] != null && !String.IsNullOrEmpty(currentRouteData.Values["controller"].ToString())) 
      currentController = currentRouteData.Values["controller"].ToString(); 

     if (currentRouteData.Values["action"] != null && !String.IsNullOrEmpty(currentRouteData.Values["action"].ToString())) 
      currentAction = currentRouteData.Values["action"].ToString(); 
    } 

    var ex = Server.GetLastError(); 
    var controller = new ErrorController(); 
    var routeData = new RouteData(); 
    var action = "Index"; 

    var code = (ex is HttpException) ? (ex as HttpException).GetHttpCode() : 500; 

    switch (code) 
    { 
     case 400: 
      action = "BadRequest"; 
      break; 
     case 401: 
      action = "Unauthorized"; 
      break; 
     case 403: 
      action = "Forbidden"; 
      break; 
     case 404: 
      action = "NotFound"; 
      break; 
     case 500: 
      action = "InternalServerError"; 
      break; 
     default: 
      action = "Index"; 
      break; 
    } 

    Server.ClearError(); 
    Response.Clear(); 
    Response.StatusCode = code; 
    Response.TrySkipIisCustomErrors = true; 

    routeData.Values["controller"] = "Error"; 
    routeData.Values["action"] = action; 

    controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction); 
    ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); 
} 

Это прекрасно работает, когда есть ошибка в моем проекте MVC. Существует также базовый класс, который делает вызовы внешнего API, как это:

/// <summary> 
/// Used to make a Get request to a specified url 
/// </summary> 
/// <param name="url">The target url</param> 
/// <returns>Returns a string</returns> 
public async Task<string> MakeApiCall(string url) 
{ 
    return await MakeApiCall(url, HttpMethod.GET, null); 
} 

/// <summary> 
/// Used to make a Post request to a specified url 
/// </summary> 
/// <param name="url">The target url</param> 
/// <param name="method">The Http method</param> 
/// <param name="data">The object to send to the api</param> 
/// <returns>Returns a string</returns> 
public async Task<string> MakeApiCall(string url, HttpMethod method, object data) 
{ 

    // Create our local variables 
    var client = new HttpClient(); 
    var user = Session["AccessToken"]; 
    var authenticating = user == null; 

    // If we are not authenticating, set our auth token 
    if (!authenticating) 
     client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Session["AccessToken"].ToString()); 

    // Check to see what HTTP method is being used 
    switch (method) 
    { 
     case HttpMethod.POST: 

      // If we are POSTing, then perform a post request 
      return await PostRequest(client, url, data, authenticating); 
     default: 

      // If we are GETing, then perform a get request 
      return await GetRequest(client, url); 
    } 
} 

#region Helper methods 

/// <summary> 
/// Posts data to a specifed url 
/// </summary> 
/// <param name="client">The HttpClient used to make the api call</param> 
/// <param name="url">The target url</param> 
/// <param name="data">The object to send to the api</param> 
/// <param name="authenticating">Used to set the content type when authenticating</param> 
/// <returns>Returns a string</returns> 
private async Task<string> PostRequest(HttpClient client, string url, object data, bool authenticating) 
{ 

    // If the data is a string, then do a normal post, otherwise post as json 
    var response = (data is string) ? await client.PostAsync(this.apiUrl + url, new StringContent(data.ToString())) : await client.PostAsJsonAsync(this.apiUrl + url, data); 

    // If we are authenticating, set the content type header 
    if (authenticating == true) 
     response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); 

    // Handle our response 
    return await HandleResponse(response); 
} 

/// <summary> 
/// Gets data from a specifed url 
/// </summary> 
/// <param name="client">The HttpClient used to make the api call</param> 
/// <param name="url">The target url</param> 
/// <returns>Returns a string</returns> 
private async Task<string> GetRequest(HttpClient client, string url) 
{ 

    // Perform the get request 
    var response = await client.GetAsync(this.apiUrl + url); 

    // Handle our response 
    return await HandleResponse(response); 
} 

/// <summary> 
/// Used to handle the api response 
/// </summary> 
/// <param name="response">The HttpResponseMessage</param> 
/// <returns>Returns a string</returns> 
private async Task<string> HandleResponse(HttpResponseMessage response) 
{ 

    // Read our response content 
    var result = await response.Content.ReadAsStringAsync(); 

    // If there was an error, throw an HttpException 
    if (response.StatusCode != HttpStatusCode.OK) 
     throw new HttpException((int)response.StatusCode, result); 

    // Return our result if there are no errors 
    return result; 
} 

#endregion 

Проблема у меня с этим подходом является HandleResponse метод. Когда вызов API производится, если вызов не приземляется на этой линии:

// If there was an error, throw an HttpException 
if (response.StatusCode != HttpStatusCode.OK) 
    throw new HttpException((int)response.StatusCode, result); 

, который, в свою очередь захватывается методом Application_Error внутри Global.asax. Проблема с этим, что, поскольку это API называют контроллер не может перенаправить на ErrorController ...

Так что мой вопрос:

  1. Могу ли я как-то игнорировать Global. asax обработка ошибок и просто вернуть JSON, чтобы мой JavaScript мог решить, что делать с ошибкой ИЛИ
  2. Есть ли лучший способ сделать это?

Если у вас есть какие-либо вопросы, обратитесь к нам. Я попытался удостовериться, что сообщение - это не просто стена текста.

Update 1

Итак, я попытался использовать AttributeFilter, чтобы помочь с этим вопросом. Я использовал 2 метода, которые предложили 2 пользователя. Сначала я создал пользовательские исключения так:

/// <summary> 
/// Custom Api Exception 
/// </summary> 
public class ApiException : Exception 
{ 

    /// <summary> 
    /// Default constructor 
    /// </summary> 
    public ApiException() 
    { 
    } 

    /// <summary> 
    /// Constructor with message 
    /// </summary> 
    /// <param name="message">The error message as a string</param> 
    public ApiException(string message) 
     : base(message) 
    { 
    } 

    /// <summary> 
    /// Constructor with message and inner exception 
    /// </summary> 
    /// <param name="message">The error message as a string</param> 
    /// <param name="inner">The inner exception</param> 
    public ApiException(string message, Exception inner) 
     : base(message, inner) 
    { 
    } 
} 

Тогда я обновил свой HandleResponse метода в моем регуляторе, чтобы выглядеть следующим образом:

/// <summary> 
/// Used to handle the api response 
/// </summary> 
/// <param name="response">The HttpResponseMessage</param> 
/// <returns>Returns a string</returns> 
private async Task<string> HandleResponse(HttpResponseMessage response) 
{ 

    // Read our response content 
    var result = await response.Content.ReadAsStringAsync(); 

    // If there was an error, throw an HttpException 
    if (response.StatusCode != HttpStatusCode.OK) 
     throw new ApiException(result); 

    // Return our result if there are no errors 
    return result; 
} 

Затем я создал фильтр, который я добавил к FilterConfig, который выглядит следующим образом:

public class ExceptionAttribute : IExceptionFilter 
{ 

    /// <summary> 
    /// Handles any exception 
    /// </summary> 
    /// <param name="filterContext">The current context</param> 
    public void OnException(ExceptionContext filterContext) 
    { 

     // If our exception has been handled, exit the function 
     if (filterContext.ExceptionHandled) 
      return; 

     // If our exception is not an ApiException 
     if (!(filterContext.Exception is ApiException)) 
     { 

      // Set our base status code 
      var statusCode = (int)HttpStatusCode.InternalServerError; 

      // If our exception is an http exception 
      if (filterContext.Exception is HttpException) 
      { 

       // Cast our exception as an HttpException 
       var exception = (HttpException)filterContext.Exception; 

       // Get our real status code 
       statusCode = exception.GetHttpCode(); 
      } 

      // Set our context result 
      var result = CreateActionResult(filterContext, statusCode); 

      // Set our handled property to true 
      filterContext.ExceptionHandled = true; 
     } 
    } 

    /// <summary> 
    /// Creats an action result from the status code 
    /// </summary> 
    /// <param name="filterContext">The current context</param> 
    /// <param name="statusCode">The status code of the error</param> 
    /// <returns></returns> 
    protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode) 
    { 

     // Create our context 
     var context = new ControllerContext(filterContext.RequestContext, filterContext.Controller); 
     var statusCodeName = ((HttpStatusCode)statusCode).ToString(); 

     // Create our route 
     var controller = (string)filterContext.RouteData.Values["controller"]; 
     var action = (string)filterContext.RouteData.Values["action"]; 
     var model = new HandleErrorInfo(filterContext.Exception, controller, action); 

     // Create our result 
     var view = SelectFirstView(context, string.Format("~/Views/Error/{0}.cshtml", statusCodeName), "~/Views/Error/Index.cshtml", statusCodeName); 
     var result = new ViewResult { ViewName = view, ViewData = new ViewDataDictionary<HandleErrorInfo>(model) }; 

     // Return our result 
     return result; 
    } 

    /// <summary> 
    /// Gets the first view name that matches the supplied names 
    /// </summary> 
    /// <param name="context">The current context</param> 
    /// <param name="viewNames">A list of view names</param> 
    /// <returns></returns> 
    protected string SelectFirstView(ControllerContext context, params string[] viewNames) 
    { 
     return viewNames.First(view => ViewExists(context, view)); 
    } 

    /// <summary> 
    /// Checks to see if a view exists 
    /// </summary> 
    /// <param name="context">The current context</param> 
    /// <param name="name">The name of the view to check</param> 
    /// <returns></returns> 
    protected bool ViewExists(ControllerContext context, string name) 
    { 
     var result = ViewEngines.Engines.FindView(context, name, null); 

     return result.View != null; 
    } 
} 

и, наконец, я удалил логику из Application_Error метод в Global.asax надеясь, что это сработает. Но это не так. Я все еще получаю документ, возвращаемый, когда есть ApiException.

Может ли кто-нибудь мне помочь?

+0

звонков от контроллера MVC к контроллеру WebAPI следует ожидать набранный ответ. Наш абстрактный класс ответа (commandResult) содержит свойство «Ошибки» List , и мы отвечаем на это свойство в конфигурации наших MVC-контроллеров. На этом этапе вы можете перенаправить или использовать специальное предупреждение на экране или что угодно. – beauXjames

+0

Вместо того, чтобы бросать исключение HttpException при внешнем api; определить новый тип исключения, например. ExternalApiException и бросить это. В своем глобальном обработчике ошибок проверяйте тип исключения и выполняйте все, что вы хотите, на основе типа исключения. –

+0

Pankaj, я выполнил ваши инструкции, а затем проверил в файле Global.asax, чтобы убедиться, что исключение не было исключением ExternalApiException. Если он не просто игнорирует мой метод Application_Error. Проблема в том, что она по-прежнему пытается сгенерировать страницу (я вижу это в скрипачке), а не только json. – r3plica

ответ

1

Могу ли я как-то игнорировать Глобальный.asax обработка ошибок и просто вернуть JSON, так что мой JavaScript может решить, что делать с ошибкой

Поскольку Global.asax является частью конвейера ASP.NET, не существует собственный способ игнорировать его. Возможно, вы могли бы прибегнуть к хачкам, но было бы лучше, если бы вы использовали рамки MVC и WebApi для решения проблемы вместо того, чтобы полагаться на устаревшее поведение ASP.NET.

Есть ли лучший способ сделать это?

Фильтры исключений можно использовать как в MVC, так и в WebApi. Каждая из этих фреймворков имеет свою собственную отдельную конфигурацию, которая позволит вам сохранить логику отдельно от каждого стека фильтров исключений.

+0

Я обновил свой вопрос :( – r3plica

0

Если вы хотите выполнить то, что вы пытаетесь сделать в минимальном количестве кода, то что вы можете сделать, вместо того, чтобы бросать HttpException, вы можете вернуть сериализованный JSON, представляющий ваше исключение, в форме строки (поскольку ваш метод возвращает строка), как следующее:

if (response.StatusCode != HttpStatusCode.OK) 
    JsonConvert.SerializeObject("{ StatusCode : " + response.StatusCode.ToString() + "}"); 

Очевидно, что это хак и не рекомендуется практика, но он не будет установлен в вашем Application_Error, и вы также можете ответить на JSON в код клиента.

Лучших вариантов будут реорганизовывать код, чтобы вернуть HttpResponseMessage или использовать атрибуты фильтра и т.д.