Angular运行在ASP.NET MVC之上,显示错误信息。

5
我们正在现有的asp.net c# MVC应用程序中添加angular前端。在我们的服务器代码中,我们广泛使用自定义异常来返回业务规则错误。
在MVC控制器或WebApi控制器(实际上是从业务层膨胀)处理异常并将其传递到angular,并在“用户错误”弹出窗口中显示它是否存在最佳实践或最流畅的方法?人们是如何解决这个问题的?

由于这不是特定于代码的内容,因此在程序员堆栈交换中可能更好。 - ChargerIIC
1
我们的黄屏替代方案是使用JavaScript警告并显示抱歉信息。这是一个异常情况,不应该发生,所以我们认为这样做没问题。但标准错误会根据情况进行处理。 - Roman Pushkin
谢谢回复,这些在 Angular 方面如何处理? - Eric Brown - Cal
1
黄屏死亡只是为你而设,例如不要更改设置以允许普通用户查看它们,使用类似于ELMAH的东西,因为它将记录大多数甚至全部错误。然后,通过一点点JS,您可以按照Angular的方式管理它们,用于Ajax类型的调用。 - Steve Drake
@ChargerllC - 虽然问题本身没有代码,但我在答案中找到了(并得到了)代码,这是否合适? - Eric Brown - Cal
5个回答

5
其他人已经给出了很好的答案,但我想详细阐述我的方法,因为我想它将涵盖前端和服务器两端,并且具有更多的细节。
以下是我在WebAPI + AngularJS应用程序中处理错误和异常的完整方法。
第一步。 服务器端的WebApi控制器
我有一个特定的DTO用于向客户端传递“验证错误”,因为我认为它们与“异常”不同。 异常将导致500错误,而验证结果应导致400(错误请求)错误。
所以,这是我的“ApiValidationResult”类:
public class ApiValidationResult
{
    public List<ApiValidationError> Errors { get; set; }

    public static ApiValidationResult Failure(string errorKey)
    {
        return new ApiValidationResult {Errors = new List<ApiValidationError> {new ApiValidationError(errorKey)}};
    }

    // You can add a bunch of utility methods here
}

public class ApiValidationError
{
    public ApiValidationError()
    {
    }

    public ApiValidationError(string errorKey)
    {
        ErrorKey = errorKey;
    }

    // More utility constructors here

    public string PropertyPath { get; set; }
    public string ErrorKey { get; set; }
    public List<string> ErrorParameters { get; set; }
}

我通常会使用自己的基类来编写WebApi(和MVC)控制器,这样我就可以使用它们来添加方便的结果方法,例如:

public abstract class ExtendedApiController : ApiController
{
    protected IHttpActionResult ValidationError(string error)
    {
        return new ValidationErrorResult(ApiValidationResult.Failure(error), this);
    }

    // More utility methods can go here
}

它使用了一个我专门为此目的创建的自定义IHttpActionResult
public class ValidationErrorResult : NegotiatedContentResult<ApiValidationResult>
{
    public ValidationErrorResult(ApiValidationResult content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters) 
        : base(HttpStatusCode.BadRequest, content, contentNegotiator, request, formatters)
    {
    }

    public ValidationErrorResult(ApiValidationResult content, ApiController controller)
        : base(HttpStatusCode.BadRequest, content, controller)
    {
    }
}

因此,我可以在我的控制器操作中干净地使用以下代码:
    [HttpPost]
    public IHttpActionResult SomeAction(SomeInput input)
    {
        // Do whatever...
        if (resultIsValid)
        {
            return Ok(outputObject);
        }

        return ValidationResult(errorMessage);
    }

第二步。处理意外异常

正如我所说的,我认为只有真正的未处理Exception应该导致500(内部服务器错误)响应。

WebApi自动将这些未处理异常转换为500结果。 我需要做的唯一事情就是记录它们。 因此,我创建了一个实现IExceptionLogger接口的实现,并像这样注册它:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new UnhandledExceptionLogger());

第三步:拦截并显示客户端错误

AngularJS允许拦截从$http服务发送的所有HTTP调用。我使用这个功能来集中所有消息弹窗。以下是我的拦截器代码:

appModule.factory("errorsHttpInterceptor", [
    "$q", "$rootScope", "$injector",
    ($q: ng.IQService, $rootScope: IAppRootScopeService, $injector) => {
        return {
            'responseError': rejection => {
                // Maybe put the error in $rootScope and show it in UI
                // Maybe use a popup
                // Maybe use a 'toast'
                var toastr = $injector.get('toastr');
                toastr.error(...);

                return $q.reject(rejection);
            }
        };
    }
]);

拦截器可以执行各种操作,例如记录调试消息或应用密钥以显示错误代码的翻译。您还可以区分500和400错误,并显示不同类型的错误消息。

我使用toastr库,它可以显示漂亮的用户界面,在API级别非常方便。

最后,我像这样注册拦截器:

appModule.config([
    '$httpProvider',
    ($httpProvider: ng.IHttpProvider) => {
        $httpProvider.interceptors.push('errorsHttpInterceptor');
    }
]);

这段代码使用的是TypeScript语法,与JavaScript非常相似,我相信您可以理解它的含义。


非常详细的答案!谢谢,我在实现之前不会标记答案来看哪个最好(这可能需要一点时间),但我已经点赞了这个出色的答案! - Eric Brown - Cal

3

通常情况下,我在 WEB API 中完成此类工作。对于此类工作,返回正确的状态码非常关键,而且它与你想要使用的许多前端框架完全无关。

public IHttpActionResult Get(DateTime? updatesAfter = null)
        {
            try
            {
                // Do something here.
                return this.Ok(result);
            }
            catch (Exception ex) // Be more specific if you like...
            {
                return this.InternalServerError(ex);
                throw;
            }
        }

现在Web Api v2 ApiControllers中提供的辅助方法非常出色...
this.BadRequest()
this.InternalServerError()
this.Ok()
this.Unauthorized()
this.StatusCode()
this.NotFound()

其中一些(例如InternalServerError)允许您将异常或消息(或仅仅是对象)作为参数传递。

通常情况下,与任何前端框架或库一样,当初始化对API方法的ajax调用时,您可以提供失败或错误回调函数,这将在返回错误状态代码的情况下被调用。


2
我们刚刚完成了一个带有Angular前端的大型MVC应用程序。
我们只是让错误自然地发生,例如任何网页都会得到一个错误,但不包括堆栈跟踪(不是黄色屏幕,而是在您的主页面内部的错误信息***)。
Web API调用只返回正确的HTTP状态。如果您希望,它们可以包含一些详细信息。
但是,您不想丢失这些错误,因此我们刚刚安装了elmah与...
Install-Package elmah 

同时,它只是工作,错误会被记录下来,用户会收到有所不同的提示等等...没有额外的工作需要做。

为了使我们的UI更加美观,我们采取了以下步骤,针对:

未处理的错误

MVC页面

只需让MVC前端做好它的工作,它会以一种友好的方式告诉用户出现了问题。

Angular网络调用

在.error函数中向用户发出警报,例如:

}).error(function (data, status, headers, config) {
$scope.addAlert('danger', 'Error deleting autosave details');
}

如果您不想因为错误被覆盖而失去错误,请让错误堆积。addAlert只是将内容写入与前端绑定的数组中。

已处理的错误 如果您正在处理它们,那么您已经控制了发生的情况,但是要记录这些错误

MVC

如果您只想记录它们,elmah API有一个单独的调用可用于此。

ErrorSignal.FromCurrentContext().Raise(exception );

如果您正在使用错误属性,那么可以使用以下方法:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ElmahHandledErrorLoggerFilter());
        filters.Add(new HandleErrorAttribute());
    }
}

public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        // Log only handled exceptions, because all other will be caught by ELMAH anyway.
        if (context.ExceptionHandled)
            ErrorSignal.FromCurrentContext().Raise(context.Exception);
    }
}

此外,如果想要将一般日志记录到ELMAH框架中,请查看https://github.com/TrueNorthIT/Elmah

1
另一种方法是返回内容。
以下是我的实现方式(端到端)。从我的API开始:
return Content(HttpStatusCode.<statuscode>, "ResponseContent: " + "my_custom_error");

在这里,HttpStatusCode和"my_custom_error"可能是从另一个API层返回的响应。在这种情况下,我只需读取来自该层的响应并将其传递给客户端即可。
//If I'm getting output from another API/Layer then I pass it's output like this
var output = response.Content.ReadAsStringAsync().Result;
return Content(response.StatusCode, "ResponseContent: " + output);

如需了解更多关于HttpStatusCodes的详情,请参考HttpStatusCode枚举

在Angular代码中,我是这样读取它的:

$http({ method: methodType, url: endpoint })
     .then(function (response) {
          response.status; //gets you the HttpStatusCode to play with
          response.data; //gets you the ReponseContent section
     }, function (response) {
          response.status; //gets you the HttpStatusCode
          response.data; //gets you the ReponseContent section
     });

确保在进行http调用时,responseType未设置为'JSON'。因为此阶段API返回的数据不是JSON格式。
AngularJS中的$http服务中,响应对象具有以下属性:
data - {string | Object} - 使用转换函数转换的响应体。
status - {number} - 响应的HTTP状态码。
headers - {function([headerName])} - 头部获取函数。
config - {Object} - 生成请求所使用的配置对象。
statusText - {string} - 响应的HTTP状态文本。

0
对于我的API控制器,我返回HttpResponseMessage。请参见下面的示例。希望这可以帮助你。
在响应消息上,您还可以将对象发送回前端。 WEB API
    // Get all
    [HttpGet]
    public HttpResponseMessage Get()
    {
        try
        {
            return Request.CreateResponse(HttpStatusCode.OK, myObject);
        }
        catch (Exception e)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Error Message");
        }
    }

Angular JS

// Get Request on Page Load
    $http({
        url: '/api/department/',
        method: 'GET',
    }).success(function (data) {
        $scope.departments = data;
    }).error(function (error) {
        $scope.error = error;
    });

HTML

<div class="alert alert-danger animate-show" role="alert" ng-show="error != null">{{ error }} </div>
<div class="alert alert-success animate-show" role="alert" ng-show="success != null">{{ success }}</div>

1
对于进行了许多编辑的抱歉,现在应该满足您所需的所有内容 :) - John Cooling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接