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

viv*_*una 6 c# asp.net-web-api asp.net-core asp.net-core-webapi .net-5

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

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)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

那么有没有办法在集中的地方(例如中间件或其他东西)来处理这个问题。

预期回应:

{
    "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"
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

我已经扩展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)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

如果有一种方法可以在扩展ValidationAttribute类中处理这种情况,那也对我有用。

注意:目标框架是.Net5

viv*_*una 2

我可以通过在ConfigureServicesStartup.cs 方法中使用以下代码来解决该问题。

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);
    };
});
Run Code Online (Sandbox Code Playgroud)

我必须使用一种技巧,通过在扩展的方法:中使用像这样的分隔符来传递错误代码和消息。IsValidValidationAttribute

return new ValidationResult("01:Proprty 1");
Run Code Online (Sandbox Code Playgroud)

如果大家有更好的方法或者建议,欢迎评论留言。我很高兴知道。