以干净的方式在 Asp.Net Core API 中实现 ProblemDetails 的最佳方法

Posted

技术标签:

【中文标题】以干净的方式在 Asp.Net Core API 中实现 ProblemDetails 的最佳方法【英文标题】:Best way to implement the ProblemDetails in Asp.Net Core API in clean way 【发布时间】:2022-01-03 16:22:48 【问题描述】:

我需要使用ProblemDetails 来处理验证错误。它按预期工作。但是这里有个大问题,我必须在所有的action方法中写一个类似的代码,我认为这不是一个好主意。

public async Task<ActionResult<SampleResponse>> Post([FromBody] SampleRequest getRateApiRequest)

    try
    
        if (ModelState.IsValid == false)
        
            ProblemDetails problemDetails = new ProblemDetails();
            problemDetails.Detail = "Detail";
            problemDetails.Instance = "Instance";
            problemDetails.Status = StatusCodes.Status400BadRequest;
            problemDetails.Title = "Title";
            problemDetails.Type = "Type";

            List<FieldCodeMessage> codeMessages = new List<FieldCodeMessage>();
            foreach (var modelState in ModelState)
            
                if (modelState.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
                
                    MemberInfo property = typeof(TradeBookingRequestAPI).GetProperty(modelState.Key);
                    var attribute = property.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().Single();
                    string displayName = attribute.DisplayName;
                    switch (modelState.Key)
                    
                        case "Property1":
                            codeMessages.Add(new FieldCodeMessage(field: displayName, code: "01", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
                            break;
                        case "Property2":
                            codeMessages.Add(new FieldCodeMessage(field: displayName, code: "02", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
                            break;
                        case "Property3":
                            codeMessages.Add(new FieldCodeMessage(field: displayName, code: "03", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
                            break;
                        case "Property4":
                            codeMessages.Add(new FieldCodeMessage(field: displayName, code: "04", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
                            break;
                        case "Property5":
                            codeMessages.Add(new FieldCodeMessage(field: displayName, code: "05", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
                            break;
                        case "Property6":
                            codeMessages.Add(new FieldCodeMessage(field: displayName, code: "06", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
                            break;
                    
                
            

            problemDetails.Extensions.Add("Invalid Fields", codeMessages);

            return BadRequest(problemDetails);
        
    
    catch (Exception)
    
        ...
    

那么有没有办法在一个集中的地方处理这个问题,比如中间件或其他东西。

预期响应:


    "type": "Type",
    "title": "Title",
    "status": 400,
    "detail": "Detail",
    "instance": "Instance",
    "Invalid Fields": [
        
            "field": "Proprty 1",
            "code": "01",
            "message": "Invalid Proprty 1"
        ,
        
            "field": "Property 2",
            "code": "02",
            "message": "Invalid Property 2"
        
    ]

我已经扩展ValidationAttribute 来实现所有属性的验证逻辑,下面是Property1 的实现。

protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)

    try
    
        if (value != null)
        
            propertyDisplayName = validationContext.DisplayName;
            long property1 = (Int64)value;
            Match match = Regex.Match($"property1", @"^\d+$", RegexOptions.IgnoreCase);

            if (!string.IsNullOrWhiteSpace($"property1") && match.Success)
            
                return ValidationResult.Success;
            
            else
            
                return new ValidationResult($"Invalid propertyDisplayName");
            

        
        else
        
            return new ValidationResult($"Invalid propertyDisplayName");
        
    
    catch (Exception ex)
    
        ...
    

如果在扩展的ValidationAttribute 类中也有处理这种情况的方法,那也适用于我。

注意:目标框架是.Net5

【问题讨论】:

嗨@vivek nuna,我想你可以尝试自定义扩展ValidationProblemDetails的类。参考:***.com/a/67916802/11398810 @Rena 是的,我尝试过使用类似的解决方法。完成后我会发布。谢谢你的建议。 @Rena 我已经添加了我的答案,请查看并提供您的反馈:) 【参考方案1】:

我可以通过在 Startup.cs 的 ConfigureServices 方法中使用以下代码来解决此问题。

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest).ConfigureApiBehaviorOptions(options =>

    options.InvalidModelStateResponseFactory = c =>
    
        ProblemDetails problemDetails = new ProblemDetails();
        problemDetails.Status = StatusCodes.Status400BadRequest;
        problemDetails.Title = "One or more validation errors occurred.";
        problemDetails.Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1";
        
        List<FieldCodeMessage> codeMessages = new List<FieldCodeMessage>();
        foreach (var modelState in c.ModelState)
        
            if (modelState.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
            
                string[] errorMessageCode = modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault().Split(':');
                string code = errorMessageCode[0];
                string message = errorMessageCode[1];

                codeMessages.Add(new FieldCodeMessage(field: modelState.Key, code: code, message: message));
            
        

        problemDetails.Extensions.Add("Invalid Fields", codeMessages);

        return new BadRequestObjectResult(problemDetails);
    ;
);

我不得不使用一种技巧来传递错误代码和消息,方法是在扩展的ValidationAttributeIsValid 方法中使用这样的: 分隔符。

return new ValidationResult("01:Proprty 1");

如果有人有更好的方法或建议,请添加评论。我很高兴知道。

【讨论】:

以上是关于以干净的方式在 Asp.Net Core API 中实现 ProblemDetails 的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

详解ASP.NET Core API 的Get和Post请求使用方式

在 ASP.NET Core 3.1 中上传和下载大文件?

仅在文件上传中出现 Asp.Net Core API CORS 策略错误

ASP.NET MVC Core API 将枚举序列化为字符串

Refit结合Polly访问ASP.NET Core Web API

ASP.NET Core Web Api之JWT