بررسی بهبودهای ProblemDetails در ASP.NET Core 7x
نویسنده: وحید نصیری
تاریخ: ۱۴۰۲/۰۱/۱۷ ۱۱:۵۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/msgs/abc",
"status": 403,
} var mediaType = response.Content.Headers.ContentType?.MediaType;
if (mediaType != null && mediaType.Equals("application/problem+json", StringComparison.InvariantCultureIgnoreCase))
{
var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>(null, ct) ?? new ProblemDetails();
// ...
} [HttpPost("/sales/products/{sku}/availableForSale")]
public async Task<IActionResult> AvailableForSale([FromRoute] string sku)
{
return Problem(
"Product is already Available For Sale.",
"/sales/products/1/availableForSale",
400,
"Cannot set product as available.",
"http://example.com/problems/already-available");
} HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
Content-Language: en
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"User": [
"The user name is not verified."
]
}
} namespace WebApplication.Controllers
{
[ApiController]
[Route("[controller]")]
public class DemoController : ControllerBase
{
[HttpPost]
public ActionResult Post()
{
var problemDetails = new ProblemDetails
{
Detail = "The request parameters failed to validate.",
Instance = null,
Status = 400,
Title = "Validation Error",
Type = "https://example.net/validation-error",
};
problemDetails.Extensions.Add("invalidParams", new List<ValidationProblemDetailsParam>()
{
new("name", "Cannot be blank."),
new("age", "Must be great or equals to 18.")
});
return new ObjectResult(problemDetails)
{
StatusCode = 400
};
}
}
public class ValidationProblemDetailsParam
{
public ValidationProblemDetailsParam(string name, string reason)
{
Name = name;
Reason = reason;
}
public string Name { get; set; }
public string Reason { get; set; }
}
} services.AddProblemDetails();
app.UseExceptionHandler();
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = ctx =>
ctx.ProblemDetails.Extensions.Add("MachineName", Environment.MachineName)); public class MyErrorFeature
{
public ErrorType Error { get; set; }
}
public enum ErrorType
{
ArgumentException
} [HttpGet("{value}")]
public IActionResult MyErrorTest(int value)
{
if (value <= 0)
{
var errorType = new MyErrorFeature
{
Error = ErrorType.ArgumentException
};
HttpContext.Features.Set(errorType);
return BadRequest();
}
return Ok(value);
} services.AddProblemDetails(options =>
options.CustomizeProblemDetails = ctx =>
{
var MyErrorFeature = ctx.HttpContext.Features.Get<MyErrorFeature>();
if (MyErrorFeature is not null)
{
(string Title, string Detail, string Type) details = MyErrorFeature.Error switch
{
ErrorType.ArgumentException =>
(
nameof(ArgumentException),
"This is an argument-exception.",
"https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1"
),
_ =>
(
nameof(Exception),
"default-exception",
"https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1"
)
};
ctx.ProblemDetails.Title = details.Title;
ctx.ProblemDetails.Detail = details.Detail;
ctx.ProblemDetails.Type = details.Type;
}
}
); public class MyCustomException : Exception
{
public MyCustomException(
string message,
HttpStatusCode statusCode = HttpStatusCode.BadRequest
) : base(message)
{
StatusCode = statusCode;
}
public HttpStatusCode StatusCode { get; }
} [HttpGet("{value}")]
public IActionResult MyErrorTest(int value)
{
if (value <= 0)
{
throw new MyCustomException("The value should be positive!");
}
return Ok(value);
} app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.ContentType = "application/problem+json";
if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService)
{
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
var exceptionType = exceptionHandlerFeature?.Error;
if (exceptionType is not null)
{
(string Title, string Detail, string Type, int StatusCode) details = exceptionType switch
{
MyCustomException MyCustomException =>
(
exceptionType.GetType().Name,
exceptionType.Message,
"https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1",
context.Response.StatusCode = (int)MyCustomException.StatusCode
),
_ =>
(
exceptionType.GetType().Name,
exceptionType.Message,
"https://www.rfc-editor.org/rfc/rfc7231#section-6.6.1",
context.Response.StatusCode = StatusCodes.Status500InternalServerError
)
};
await problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = details.Title,
Detail = details.Detail,
Type = details.Type,
Status = details.StatusCode
}
});
}
}
});
});
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
internal sealed class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext context,
Exception exception,
CancellationToken cancellationToken = default)
{
_logger.LogError(exception, "An unhandled exception has occurred while executing the request.");
var problemDetails = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "Server Error",
};
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(problemDetails);
return true;
}
} builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); // other code app.UseExceptionHandler();