简体   繁体   English

MVC3和WebApi错误处理

[英]MVC3 and WebApi Error Handling

I am editing an old project which is using MVC3. 我正在编辑使用MVC3的旧项目。 It has a Global.asax file which handles errors like this: 它具有处理以下错误的Global.asax文件:

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

This works fine when there is an error inside my MVC project. 当我的MVC项目中出现错误时,这可以正常工作。 There is also a base class which makes calls to an external API like this: 还有一个基类,可以像这样对外部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

The issue I have with this approach is the HandleResponse method. 这种方法的问题是HandleResponse方法。 When an API call is made, if the call fails it lands on this line: 进行API调用时,如果调用失败,它将到达以下行:

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

which in turn is captured by the Application_Error method inside Global.asax . 依次由Global.asax中Application_Error方法捕获。 The problem with this, is that because this is an API call the controller can't redirect to the ErrorController ... 问题在于,由于这是API调用,因此控制器无法重定向到ErrorController ...

So my question is: 所以我的问题是:

  1. Can I somehow ignore the Global.asax error handling and just return JSON so that my JavaScript can decide what to do with the error OR 我可以以某种方式忽略Global.asax错误处理而只返回JSON,以便我的JavaScript可以决定如何处理错误,或者
  2. Is there a better way of doing this? 有更好的方法吗?

If you have any questions, please ask. 如果您有任何问题,请询问。 I have tried to make sure the post is not just a wall of text. 我已尝试确保帖子不只是一堵墙。

Update 1 更新1

So, I have tried to use the AttributeFilter to help with this issue. 因此,我尝试使用AttributeFilter来解决此问题。 I used 2 methods that 2 users suggested. 我使用了2个用户建议的2种方法。 First I created a custom Exception like this: 首先,我创建了一个自定义异常,如下所示:

/// <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)
    {
    }
}

Then I updated my HandleResponse method in my base controller to look like this: 然后,我在基本控制器中更新了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;
}

Then I created a filter which I added to the FilterConfig which looked like this: 然后创建一个过滤器,添加到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;
    }
}

and finally I removed the logic from the Application_Error method in Global.asax hoping that that would work. 最后,我从Global.asaxApplication_Error方法中删除了逻辑,希望它能起作用。 But it didn't. 但事实并非如此。 I still get a document being returned when there is an ApiException. 当出现ApiException时,我仍然会返回文档。

Can anyone help me? 谁能帮我?

Can I somehow ignore the Global.asax error handling and just return JSON so that my JavaScript can decide what to do with the error 我可以以某种方式忽略Global.asax错误处理而只返回JSON,以便我的JavaScript可以决定如何处理错误

Since Global.asax is part of the ASP.NET pipeline, there is no native way to ignore it. 由于Global.asax是ASP.NET管道的一部分,因此没有本机的方法可以忽略它。 You could resort to some hack maybe, but it would be better if you use the MVC and WebApi frameworks to solve the issue instead of relying antiquated ASP.NET behavior. 也许可以求助于黑客,但是如果您使用MVC和WebApi框架解决问题而不是依靠过时的ASP.NET行为,那就更好了。

Is there a better way of doing this? 有更好的方法吗?

You can use Exception filters in both MVC and in WebApi . 您可以在MVCWebApi中使用异常过滤器。 Each of these frameworks has their own separate configuration, which will allow you to keep the logic separate of each stack of exception filters. 这些框架中的每一个都有各自独立的配置,这使您可以将逻辑与每个异常过滤器堆栈分开。

If you like to accomplish what you are trying to do in minimum number of code then what you can do is instead of throwing an HttpException you can return a serialized JSON representing your exception in the form of string (since your method returns string) like following: 如果您想用最少的代码完成您想做的事情,那么您可以做的是代替抛出HttpException而不是抛出HttpException它可以以字符串形式返回表示您的异常的JSON(因为您的方法返回字符串),如下所示:

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

Obviously, this is a hack and not recommended practice but it will not set of your Application_Error and you can also reply a JSON to your client code. 显然,这是一种hack,不建议这样做,但不会设置Application_Error并且您还可以将JSON答复给客户端代码。

Better options would be to refactor your code to return HttpResponseMessage or use filter attributes, etc. 更好的选择是重构代码以返回HttpResponseMessage或使用过滤器属性等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM