public async Task ProblemDetailsException_WithInnerException_InnerExceptionToBeIncludedInProblemDetailsExceptionDetails() { const int expected = StatusCodes.Status429TooManyRequests; var details = new MvcProblemDetails { Title = ReasonPhrases.GetReasonPhrase(expected), Type = $"https://httpstatuses.com/{expected}", Status = expected, }; const string innerExceptionMessage = "inner exception message"; var innerException = new ArgumentException(innerExceptionMessage); var ex = new ProblemDetailsException(details, innerException); using var client = CreateClient(handler: ResponseThrows(ex), SetOnBeforeWriteDetails); var response = await client.GetAsync(string.Empty); var responseProblemDetails = await response.Content.ReadFromJsonAsync <MvcProblemDetails>(); var responseExceptionDetails = (JsonElement)responseProblemDetails.Extensions[ProblemDetailsOptions.DefaultExceptionDetailsPropertyName]; var responseExceptionMessage = responseExceptionDetails.EnumerateArray().ToList()[0].GetProperty("message").GetString(); Assert.Equal(innerExceptionMessage, responseExceptionMessage); }
private async Task HandleExceptionAsync(HttpContext context, Exception exception) { var requestId = System.Diagnostics.Activity.Current?.Id ?? context.TraceIdentifier; var eventId = new EventId(exception.HResult, requestId); _logger.LogError(eventId, exception, exception.Message); var status = 500; var type = string.Concat("https://httpstatuses.com/", status); var title = _env.IsDevelopment() ? exception.Message : $"系统异常"; var detial = _env.IsDevelopment() ? exception.GetExceptionDetail() : $"系统异常,请联系管理员({eventId})"; var problemDetails = new ProblemDetails { Title = title , Detail = detial , Type = type , Status = status }; context.Response.StatusCode = status; context.Response.ContentType = "application/problem+json"; var errorText = JsonSerializer.Serialize(problemDetails, SystemTextJson.GetAdncDefaultOptions()); await context.Response.WriteAsync(errorText); }
public static void SetTraceId(Microsoft.AspNetCore.Mvc.ProblemDetails details, HttpContext httpContext) { // this is the same behaviour that Asp.Net core uses var traceId = Activity.Current?.Id ?? httpContext.TraceIdentifier; details.Extensions["traceId"] = traceId; }
public async void When_Cannot_Deserialize_To_ValidationProblemDetails_Errors_Should_Fallback_To_ProblemDetails() { // given var errorObject = new ErrorObject { Message = "Foo bar", Raw = "<Foo bar>" }; var problem = new MvcProblemDetails { Extensions = { { "errors", new[] { errorObject } } } }; New.ProblemDetails.BadRequest.CopyStandardFieldsTo(problem); var response = await New.HttpResponseMessage.Of(problem); // when var actual = await response.Content.ReadAsProblemDetailsAsync(); // then actual.Should().BeOfType <MvcProblemDetails>(); actual.Extensions.Should().ContainKey("errors"); }
public IActionResult Index(string name) { try { if (string.Equals(name, "khurram")) { return(Ok("good response")); } else { throw new Exception("Testing Problem Detail"); } } catch (Exception ex) { var problemDetails = new Microsoft.AspNetCore.Mvc.ProblemDetails { Status = StatusCodes.Status500InternalServerError, Type = "Home Controller Index ", Title = ex.Message, Detail = ex.StackTrace, Instance = HttpContext.Request.Path }; return(BadRequest(problemDetails)); } }
/// <summary> /// Add the values in the <see cref="MvcProblemDetails.Extensions" /> property of the /// <paramref name="problem" /> to the <paramref name="dimensions" /> /// </summary> protected virtual void CollectExtensionDimensions( IDictionary <string, string> dimensions, MvcProblemDetails problem, HttpContext httpContext) { foreach (var(key, value) in problem.Extensions) { CollectOne(dimensions, problem, UppercaseFirst(key), value, httpContext); } }
internal void CallBeforeWriteHook(HttpContext context, MvcProblemDetails details) { AddTraceId(context, details); OnBeforeWriteDetails?.Invoke(context, details); #if NETCOREAPP3_1 // Workaround for https://github.com/dotnet/aspnetcore/pull/17565. context.Response.StatusCode = details.Status ?? context.Response.StatusCode; #endif }
/// <summary> /// Add the properties from the <paramref name="problem" /> that are defined in the problem-details /// specification to the <paramref name="dimensions" /> /// </summary> protected virtual void CollectionStandardDimensions( IDictionary <string, string> dimensions, MvcProblemDetails problem, HttpContext httpContext) { CollectOne(dimensions, problem, nameof(MvcProblemDetails.Type), problem.Type, httpContext); CollectOne(dimensions, problem, nameof(MvcProblemDetails.Status), problem.Status ?? 0, httpContext); CollectOne(dimensions, problem, nameof(MvcProblemDetails.Detail), problem.Detail, httpContext); CollectOne(dimensions, problem, nameof(MvcProblemDetails.Title), problem.Title, httpContext); CollectOne(dimensions, problem, nameof(MvcProblemDetails.Instance), problem.Instance, httpContext); }
private ObjectResult CreateResult(ActionContext context, MvcProblemDetails problemDetails) { Options.CallBeforeWriteHook(context.HttpContext, problemDetails); return(new ObjectResult(problemDetails) { StatusCode = problemDetails.Status, ContentTypes = Options.ContentTypes, }); }
/// <summary> /// Controllers 注册 /// Sytem.Text.Json 配置 /// FluentValidation 注册 /// ApiBehaviorOptions 配置 /// </summary> protected virtual void AddControllers() { Services .AddControllers(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute))) .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new DateTimeConverter()); options.JsonSerializerOptions.Converters.Add(new DateTimeNullableConverter()); options.JsonSerializerOptions.Encoder = SystemTextJson.GetAdncDefaultEncoder(); //该值指示是否允许、不允许或跳过注释。 options.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; //dynamic与匿名类型序列化设置 options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; //dynamic options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; //匿名类型 options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }) .AddFluentValidation(cfg => { //Continue 验证失败,继续验证其他项 ValidatorOptions.Global.DefaultClassLevelCascadeMode = CascadeMode.Continue; //cfg.ValidatorOptions.DefaultClassLevelCascadeMode = FluentValidation.CascadeMode.Continue; // Optionally set validator factory if you have problems with scope resolve inside validators. // cfg.ValidatorFactoryType = typeof(HttpContextServiceProviderValidatorFactory); }); Services .Configure <ApiBehaviorOptions>(options => { //调整参数验证返回信息格式 //关闭自动验证 //options.SuppressModelStateInvalidFilter = true; //格式化验证信息 options.InvalidModelStateResponseFactory = (context) => { var problemDetails = new ProblemDetails { Detail = context.ModelState.GetValidationSummary("<br>"), Title = "参数错误", Status = (int)HttpStatusCode.BadRequest, Type = "https://httpstatuses.com/400", Instance = context.HttpContext.Request.Path }; return(new ObjectResult(problemDetails) { StatusCode = problemDetails.Status }); }; }); //add skyamp //_services.AddSkyApmExtensions().AddCaching(); }
/// <summary> /// Serialize the <paramref name="value" /> and add any non-null result as a custom dimension using a /// key constructed from the <see cref="ProblemDetailsTelemetryOptions.DimensionPrefix" /> /// and <paramref name="key" /> /// </summary> /// <param name="dimensions">The dictionary to add dimensions to</param> /// <param name="problem">The problem instance from whom dimensions are being collected</param> /// <param name="key">The name of the property or error key</param> /// <param name="value">The value to serialized and added as a dimension</param> /// <param name="httpContext">The http context of the current request</param> protected virtual void CollectOne(IDictionary <string, string> dimensions, MvcProblemDetails problem, string key, object value, HttpContext httpContext) { var serializedValue = Options.SerializeValue(httpContext, problem, key, value); if (serializedValue != null) { dimensions[$"{Options.DimensionPrefix}.{key}"] = serializedValue; } }
/// <summary> /// The default implementation used to serialize value to be sent as custom dimensions /// </summary> /// <returns></returns> public static string SerializeValue(HttpContext httpContext, MvcProblemDetails problem, string key, object value) { return(value switch { null => null, string s => s, int _ => value.ToString(), DateTime dtm => dtm.ToString("O"), DateTimeOffset dtm2 => dtm2.ToString("O"), _ => TrySerializeAsJson(value) });
internal bool TryMapProblemDetails(HttpContext context, Exception exception, out MvcProblemDetails problem) { foreach (var mapper in Mappers) { if (mapper.TryMap(context, exception, out problem)) { return(true); } } problem = default; return(false); }
private static void SetProblemDetailsDefault( MvcProblemDetails result, int statusCode, string?title = null, string?type = null, string?detail = null, string?instance = null) { result.Status = statusCode; result.Title = title ?? result.Title; result.Type = type ?? result.Type ?? StatusCodeProblemDetails.GetDefaultType(statusCode); result.Detail = detail ?? result.Detail; result.Instance = instance ?? result.Instance; }
public MvcProblemDetails ProblemDetailsWithExtensions() { var problem = new MvcProblemDetails { Extensions = { { "stringField", "string field value" }, { "intField", 20 } } }; BadRequest.CopyStandardFieldsTo(problem); return(problem); }
private void AddTraceId(HttpContext context, MvcProblemDetails details) { const string key = "traceId"; if (details.Extensions.ContainsKey(key)) { return; } var traceId = GetTraceId.Invoke(context); if (!string.IsNullOrEmpty(traceId)) { details.Extensions[key] = traceId; } }
private Task WriteProblemDetails(HttpContext context, MvcProblemDetails details) { var routeData = context.GetRouteData() ?? EmptyRouteData; var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor); var result = new ObjectResult(details) { StatusCode = details.Status ?? context.Response.StatusCode, DeclaredType = details.GetType(), }; result.ContentTypes.Add("application/problem+json"); result.ContentTypes.Add("application/problem+xml"); return(Executor.ExecuteAsync(actionContext, result)); }
public bool TryMap(HttpContext context, Exception exception, out MvcProblemDetails problem) { if (CanMap(exception.GetType())) { try { problem = Mapping(context, exception); return(true); } catch { problem = default; return(false); } } problem = default; return(false); }
private Task WriteProblemDetails(HttpContext context, MvcProblemDetails details) { Options.AddTraceId(context, details); Options.OnBeforeWriteDetails?.Invoke(context, details); var routeData = context.GetRouteData() ?? EmptyRouteData; var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor); var result = new ObjectResult(details) { StatusCode = details.Status ?? context.Response.StatusCode, ContentTypes = Options.ContentTypes, DeclaredType = details.GetType(), }; return(Executor.ExecuteAsync(actionContext, result)); }
public async void When_Errors_Not_Suitable_As_ValidationProblemDetails_Should_Fallback_To_ProblemDetails() { // given var problem = new MvcProblemDetails { Extensions = { { "errors", "Some details that are not ValidationProblemDetails" } } }; New.ProblemDetails.BadRequest.CopyStandardFieldsTo(problem); var response = await New.HttpResponseMessage.Of(problem); // when var actual = await response.Content.ReadAsProblemDetailsAsync(); // then actual.Should().NotBeOfType <ValidationProblemDetails>().And.BeEquivalentTo(problem); }
public async Task ProblemDetailsException_IsHandled() { const int expected = StatusCodes.Status429TooManyRequests; var details = new MvcProblemDetails { Title = ReasonPhrases.GetReasonPhrase(expected), Type = $"https://httpstatuses.com/{expected}", Status = expected, }; var ex = new ProblemDetailsException(details); using var client = CreateClient(handler: ResponseThrows(ex), SetOnBeforeWriteDetails); var response = await client.GetAsync("/"); Assert.Equal(expected, (int)response.StatusCode); await AssertIsProblemDetailsResponse(response, expectExceptionDetails : false); }
public virtual void CollectDimensions( IDictionary <string, string> dimensions, MvcProblemDetails problem, HttpContext httpContext, DimensionChoices choices) { CollectionStandardDimensions(dimensions, problem, httpContext); if (problem is ValidationProblemDetails validationProblem && choices.IncludeErrorsValue) { CollectValidationErrorDimensions(dimensions, validationProblem, httpContext); } if (choices.IncludeExtensionsValue) { CollectExtensionDimensions(dimensions, problem, httpContext); } if (choices.IncludeRawJson) { CollectionRawProblemDimension(dimensions, problem, httpContext); } }
public NotFoundRestObjectResult(ProblemDetails problemDetails) : base(problemDetails) { StatusCode = DefaultStatusCode; problemDetails.Status = DefaultStatusCode; }
public async Task Invoke(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var endpoint = context.GetEndpoint(); if (endpoint == null) { await _next(context); return; } var requiredValues = ((RouteEndpoint)endpoint).RoutePattern.RequiredValues; if (!requiredValues.Any()) { await _next(context); return; } var controller = requiredValues["controller"]?.ToString().ToLower(); var action = requiredValues["action"]?.ToString().ToLower(); if (controller.IsNullOrWhiteSpace()) { await _next(context); return; } var statusCode = (HttpStatusCode)context.Response.StatusCode; //判断Api是否需要认证 bool isNeedAuthentication = endpoint.Metadata.GetMetadata <IAllowAnonymous>() == null; //Api不需要认证 if (!isNeedAuthentication) { //如果是调用登录API、刷新token的Api,并且调用成功,需要保存accesstoken到cache if (controller == "account" && (action == "login" || action == "refreshaccesstoken")) { await SaveToken(context, _next); return; } //其他Api await _next(context); return; } //API需要认证 if (isNeedAuthentication) { //是修改密码,需要从cahce移除Token if (controller == "account" && action == "password") { await _next(context); if (statusCode.Is2xx()) { await RemoveToken(context); } return; } //是注销,需要判断是否主动注销 if (controller == "account" && action == "logout") { await _next(context); if (statusCode.Is2xx()) { //主动注销,从cahce移除token if (await CheckToken(context)) { await RemoveToken(context); } return; } } } //API需要认证,并且验证成功,需要检查accesstoken是否在缓存中。 if (statusCode.Is2xx()) { //需要先检查token是否是最新的,再走其它中间件 var result = await CheckToken(context); if (result) { try { await _next(context); } catch (Exception ex) { throw new Exception(ex.Message, ex); } return; } else { var status = (int)HttpStatusCode.Unauthorized; var hostAndPort = context.Request.Host.HasValue ? context.Request.Host.Value : string.Empty; var requestUrl = string.Concat(hostAndPort, context.Request.Path); var type = string.Concat("https://httpstatuses.com/", status); var title = "Token已经过期"; var detial = "Token已经过期,请重新登录"; var problemDetails = new ProblemDetails { Title = title , Detail = detial , Type = type , Status = status , Instance = requestUrl }; context.Response.StatusCode = status; context.Response.ContentType = "application/problem+json"; var stream = context.Response.Body; await JsonSerializer.SerializeAsync(stream, problemDetails); return; } } await _next(context); }
public static void SetType(Microsoft.AspNetCore.Mvc.ProblemDetails details, int statusCode) { details.Type = $"https://httpstatuses.com/{statusCode}"; }
public GoneRestObjectResult Gone(ProblemDetails problemDetails) { problemDetails.Instance = HttpContext.Request.Path; return(new GoneRestObjectResult(problemDetails)); }
public NotFoundRestObjectResult NotFound(ProblemDetails problemDetails) { problemDetails.Instance = HttpContext.Request.Path; return(new NotFoundRestObjectResult(problemDetails)); }
public UnauthorizedRestObjectResult Unauthorized(ProblemDetails problemDetails) { problemDetails.Instance = HttpContext.Request.Path; return(new UnauthorizedRestObjectResult(problemDetails)); }
public ForbiddenRestObjectResult Forbid(ProblemDetails problemDetails) { problemDetails.Instance = HttpContext.Request.Path; return(new ForbiddenRestObjectResult(problemDetails)); }
public ConflictRestObjectResult Conflict(ProblemDetails problemDetails) { problemDetails.Instance = HttpContext.Request.Path; return(new ConflictRestObjectResult(problemDetails)); }