From 007c622fdddc71239fad4c30cf6f86c7c29fa83f Mon Sep 17 00:00:00 2001 From: Safia Abdalla <safia@microsoft.com> Date: Thu, 23 Sep 2021 14:00:41 -0700 Subject: [PATCH] Improve Results.Problem and Results.ValidationProblem APIs (#36856) --- .../Http.Results/src/PublicAPI.Unshipped.txt | 7 +-- src/Http/Http.Results/src/Results.cs | 44 ++++++++++++++++--- .../MinimalSample/MinimalSample.csproj | 2 + src/Http/samples/MinimalSample/Program.cs | 27 +++++++++--- 4 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index 96ce109da59..7eff16008f1 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -20,7 +20,8 @@ static Microsoft.AspNetCore.Http.Results.LocalRedirect(string! localUrl, bool pe static Microsoft.AspNetCore.Http.Results.NoContent() -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.NotFound(object? value = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Ok(object? value = null) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.Problem(string? detail = null, string? instance = null, int? statusCode = null, string? title = null, string? type = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Problem(string? detail = null, string? instance = null, int? statusCode = null, string? title = null, string? type = null, System.Collections.Generic.IDictionary<string!, object?>? extensions = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Problem(Microsoft.AspNetCore.Mvc.ProblemDetails! problemDetails) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Redirect(string! url, bool permanent = false, bool preserveMethod = false) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName = null, object? routeValues = null, bool permanent = false, bool preserveMethod = false, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal, Microsoft.AspNetCore.Authentication.AuthenticationProperties? properties = null, string? authenticationScheme = null) -> Microsoft.AspNetCore.Http.IResult! @@ -30,6 +31,6 @@ static Microsoft.AspNetCore.Http.Results.Stream(System.IO.Stream! stream, string static Microsoft.AspNetCore.Http.Results.Text(string! content, string? contentType = null, System.Text.Encoding? contentEncoding = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Unauthorized() -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.UnprocessableEntity(object? error = null) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.ValidationProblem(System.Collections.Generic.IDictionary<string!, string![]!>! errors, string? detail = null, string? instance = null, int? statusCode = null, string? title = null, string? type = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.ValidationProblem(System.Collections.Generic.IDictionary<string!, string![]!>! errors, string? detail = null, string? instance = null, int? statusCode = null, string? title = null, string? type = null, System.Collections.Generic.IDictionary<string!, object?>? extensions = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Extensions.get -> Microsoft.AspNetCore.Http.IResultExtensions! -Microsoft.AspNetCore.Http.IResultExtensions \ No newline at end of file +Microsoft.AspNetCore.Http.IResultExtensions diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 15d4937e3a2..aa69b20ea4d 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -471,13 +471,15 @@ namespace Microsoft.AspNetCore.Http /// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param> /// <param name="title">The value for <see cref="ProblemDetails.Title" />.</param> /// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param> + /// <param name="extensions">The value for <see cref="ProblemDetails.Extensions" />.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> public static IResult Problem( string? detail = null, string? instance = null, int? statusCode = null, string? title = null, - string? type = null) + string? type = null, + IDictionary<string, object?>? extensions = null) { var problemDetails = new ProblemDetails { @@ -485,9 +487,30 @@ namespace Microsoft.AspNetCore.Http Instance = instance, Status = statusCode, Title = title, - Type = type + Type = type, }; + if (extensions is not null) + { + foreach (var extension in extensions) + { + problemDetails.Extensions.Add(extension); + } + } + + return new ObjectResult(problemDetails) + { + ContentType = "application/problem+json", + }; + } + + /// <summary> + /// Produces a <see cref="ProblemDetails"/> response. + /// </summary> + /// <param name="problemDetails">The <see cref="ProblemDetails"/> object to produce a response from.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult Problem(ProblemDetails problemDetails) + { return new ObjectResult(problemDetails) { ContentType = "application/problem+json", @@ -502,8 +525,9 @@ namespace Microsoft.AspNetCore.Http /// <param name="detail">The value for <see cref="ProblemDetails.Detail" />.</param> /// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param> /// <param name="statusCode">The status code.</param> - /// <param name="title">The value for <see cref="ProblemDetails.Title" />.</param> + /// <param name="title">The value for <see cref="ProblemDetails.Title" />. Defaults to "One or more validation errors occurred."</param> /// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param> + /// <param name="extensions">The value for <see cref="ProblemDetails.Extensions" />.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> public static IResult ValidationProblem( IDictionary<string, string[]> errors, @@ -511,16 +535,26 @@ namespace Microsoft.AspNetCore.Http string? instance = null, int? statusCode = null, string? title = null, - string? type = null) + string? type = null, + IDictionary<string, object?>? extensions = null) { var problemDetails = new HttpValidationProblemDetails(errors) { Detail = detail, Instance = instance, - Title = title, Type = type, Status = statusCode, }; + + problemDetails.Title = title ?? problemDetails.Title; + + if (extensions is not null) + { + foreach (var extension in extensions) + { + problemDetails.Extensions.Add(extension); + } + } return new ObjectResult(problemDetails) { diff --git a/src/Http/samples/MinimalSample/MinimalSample.csproj b/src/Http/samples/MinimalSample/MinimalSample.csproj index 6b59d1446b9..eea90b96520 100644 --- a/src/Http/samples/MinimalSample/MinimalSample.csproj +++ b/src/Http/samples/MinimalSample/MinimalSample.csproj @@ -8,6 +8,8 @@ <Reference Include="Microsoft.AspNetCore" /> <Reference Include="Microsoft.AspNetCore.Diagnostics" /> <Reference Include="Microsoft.AspNetCore.Hosting" /> + <Reference Include="Microsoft.AspNetCore.Http" /> + <Reference Include="Microsoft.AspNetCore.Http.Results" /> <!-- Mvc.Core is referenced only for its attributes --> <Reference Include="Microsoft.AspNetCore.Mvc.Core" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> diff --git a/src/Http/samples/MinimalSample/Program.cs b/src/Http/samples/MinimalSample/Program.cs index 5441e671a8f..12b78a78246 100644 --- a/src/Http/samples/MinimalSample/Program.cs +++ b/src/Http/samples/MinimalSample/Program.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Mvc; var app = WebApplication.Create(args); @@ -13,12 +11,29 @@ if (app.Environment.IsDevelopment()) } string Plaintext() => "Hello, World!"; -app.MapGet("/plaintext", (Func<string>)Plaintext); +app.MapGet("/plaintext", Plaintext); + object Json() => new { message = "Hello, World!" }; -app.MapGet("/json", (Func<object>)Json); +app.MapGet("/json", Json); string SayHello(string name) => $"Hello, {name}!"; -app.MapGet("/hello/{name}", (Func<string, string>)SayHello); +app.MapGet("/hello/{name}", SayHello); + +var extensions = new Dictionary<string, object>() { { "traceId", "traceId123" } }; + +app.MapGet("/problem", () => + Results.Problem(statusCode: 500, extensions: extensions)); + +app.MapGet("/problem-object", () => + Results.Problem(new ProblemDetails() { Status = 500, Extensions = { { "traceId", "traceId123"} } })); + +var errors = new Dictionary<string, string[]>(); + +app.MapGet("/validation-problem", () => + Results.ValidationProblem(errors, statusCode: 400, extensions: extensions)); + +app.MapGet("/validation-problem-object", () => + Results.Problem(new HttpValidationProblemDetails(errors) { Status = 400, Extensions = { { "traceId", "traceId123"}}})); app.Run(); -- GitLab