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);
    }
Beispiel #3
0
        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));
     }
 }
Beispiel #6
0
 /// <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
        }
Beispiel #8
0
 /// <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);
 }
Beispiel #9
0
        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();
    }
Beispiel #11
0
        /// <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;
            }
        }
Beispiel #12
0
 /// <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);
        }
Beispiel #14
0
 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);
        }
Beispiel #22
0
        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;
 }
Beispiel #24
0
    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);
    }
Beispiel #25
0
 public static void SetType(Microsoft.AspNetCore.Mvc.ProblemDetails details, int statusCode)
 {
     details.Type = $"https://httpstatuses.com/{statusCode}";
 }
Beispiel #26
0
        public GoneRestObjectResult Gone(ProblemDetails problemDetails)
        {
            problemDetails.Instance = HttpContext.Request.Path;

            return(new GoneRestObjectResult(problemDetails));
        }
Beispiel #27
0
        public NotFoundRestObjectResult NotFound(ProblemDetails problemDetails)
        {
            problemDetails.Instance = HttpContext.Request.Path;

            return(new NotFoundRestObjectResult(problemDetails));
        }
Beispiel #28
0
        public UnauthorizedRestObjectResult Unauthorized(ProblemDetails problemDetails)
        {
            problemDetails.Instance = HttpContext.Request.Path;

            return(new UnauthorizedRestObjectResult(problemDetails));
        }
Beispiel #29
0
        public ForbiddenRestObjectResult Forbid(ProblemDetails problemDetails)
        {
            problemDetails.Instance = HttpContext.Request.Path;

            return(new ForbiddenRestObjectResult(problemDetails));
        }
Beispiel #30
0
        public ConflictRestObjectResult Conflict(ProblemDetails problemDetails)
        {
            problemDetails.Instance = HttpContext.Request.Path;

            return(new ConflictRestObjectResult(problemDetails));
        }