Skip to content

Commit f1ae661

Browse files
Refactor to IProblemDetailsService
1 parent 94a3afc commit f1ae661

File tree

16 files changed

+167
-200
lines changed

16 files changed

+167
-200
lines changed

src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
namespace Asp.Versioning;
55

66
using Newtonsoft.Json;
7-
using System.Web.Http;
87
using static Asp.Versioning.ProblemDetailsDefaults;
98
using static Newtonsoft.Json.NullValueHandling;
109
using static System.Globalization.CultureInfo;

src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/HttpServerFixture.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22

3-
#pragma warning disable CA1056 // URI-like properties should not be strings
4-
53
namespace Asp.Versioning;
64

75
using Microsoft.AspNetCore.Builder;
@@ -26,7 +24,7 @@ public abstract partial class HttpServerFixture
2624
protected virtual void OnConfigurePartManager( ApplicationPartManager partManager ) =>
2725
partManager.ApplicationParts.Add( new TestApplicationPart( FilteredControllerTypes ) );
2826

29-
protected virtual void OnConfigureServices( IServiceCollection services ) => services.AddControllers();
27+
protected virtual void OnConfigureServices( IServiceCollection services ) => services.AddProblemDetails().AddControllers();
3028

3129
protected virtual void OnAddMvcApiVersioning( MvcApiVersioningOptions options ) { }
3230

src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/given a versioned Controller/when using media type negotiation.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,14 @@ public async Task then_get_should_return_406_for_an_unsupported_version()
4343
// arrange
4444
using var request = new HttpRequestMessage( Get, "api/values" )
4545
{
46-
Headers = { Accept = { Parse( "application/json;v=3.0" ) } },
46+
Headers =
47+
{
48+
Accept =
49+
{
50+
Parse( "application/json;v=3.0" ),
51+
Parse( ProblemDetailsDefaults.MediaType.Json ),
52+
},
53+
},
4754
};
4855

4956
// act

src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/ODataFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected override void OnConfigurePartManager( ApplicationPartManager partManag
2525
}
2626

2727
protected override void OnConfigureServices( IServiceCollection services ) =>
28-
services.AddControllers().AddOData();
28+
services.AddProblemDetails().AddControllers().AddOData();
2929

3030
protected override void OnAddApiVersioning( IApiVersioningBuilder builder ) =>
3131
builder.AddOData( OnEnableOData );

src/AspNetCore/WebApi/src/Asp.Versioning.Http/DefaultProblemDetailsFactory.cs

Lines changed: 0 additions & 74 deletions
This file was deleted.

src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ private static void AddApiVersioningServices( IServiceCollection services )
8686

8787
services.TryAddSingleton<IApiVersionSetBuilderFactory, DefaultApiVersionSetBuilderFactory>();
8888
services.TryAddSingleton<IApiVersionParser, ApiVersionParser>();
89-
services.TryAddSingleton<IProblemDetailsFactory, DefaultProblemDetailsFactory>();
9089
services.Add( Singleton( sp => sp.GetRequiredService<IOptions<ApiVersioningOptions>>().Value.ApiVersionReader ) );
9190
services.Add( Singleton( sp => (IApiVersionParameterSource) sp.GetRequiredService<IOptions<ApiVersioningOptions>>().Value.ApiVersionReader ) );
9291
services.Add( Singleton( sp => sp.GetRequiredService<IOptions<ApiVersioningOptions>>().Value.ApiVersionSelector ) );
@@ -95,6 +94,7 @@ private static void AddApiVersioningServices( IServiceCollection services )
9594
services.TryAddEnumerable( Transient<IPostConfigureOptions<RouteOptions>, ApiVersioningRouteOptionsSetup>() );
9695
services.TryAddEnumerable( Singleton<MatcherPolicy, ApiVersionMatcherPolicy>() );
9796
services.Replace( WithLinkGeneratorDecorator( services ) );
97+
TryAddProblemDetailsRfc7231Compliance( services );
9898
}
9999

100100
// REF: https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceDescriptor.cs#L125
@@ -157,4 +157,27 @@ LinkGenerator NewFactory( IServiceProvider serviceProvider )
157157
return Describe( typeof( LinkGenerator ), NewFactory, lifetime );
158158
}
159159
}
160+
161+
private static void TryAddProblemDetailsRfc7231Compliance( IServiceCollection services )
162+
{
163+
var descriptor = services.FirstOrDefault( IsDefaultProblemDetailsWriter );
164+
165+
if ( descriptor == null )
166+
{
167+
return;
168+
}
169+
170+
var decoratedType = descriptor.ImplementationType!;
171+
var lifetime = descriptor.Lifetime;
172+
173+
services.Add( Describe( decoratedType, decoratedType, lifetime ) );
174+
services.Replace( Describe( typeof( IProblemDetailsWriter ), sp => NewProblemDetailsWriter( sp, decoratedType ), lifetime ) );
175+
176+
static bool IsDefaultProblemDetailsWriter( ServiceDescriptor serviceDescriptor ) =>
177+
serviceDescriptor.ServiceType == typeof( IProblemDetailsWriter ) &&
178+
serviceDescriptor.ImplementationType?.FullName == "Microsoft.AspNetCore.Http.DefaultProblemDetailsWriter";
179+
180+
static Rfc7231ProblemDetailsWriter NewProblemDetailsWriter( IServiceProvider serviceProvider, Type decoratedType ) =>
181+
new( (IProblemDetailsWriter) serviceProvider.GetRequiredService( decoratedType ) );
182+
}
160183
}

src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpContextExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Microsoft.AspNetCore.Http;
44

55
using Asp.Versioning;
6+
using Microsoft.Extensions.DependencyInjection;
67

78
/// <summary>
89
/// Provides extension methods for the <see cref="HttpContext"/> class.
@@ -42,4 +43,10 @@ public static IApiVersioningFeature ApiVersioningFeature( this HttpContext conte
4243
/// API version is in an invalid format.</remarks>
4344
/// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
4445
public static ApiVersion? GetRequestedApiVersion( this HttpContext context ) => context.ApiVersioningFeature().RequestedApiVersion;
46+
47+
internal static bool TryGetProblemDetailsService( this HttpContext context, [NotNullWhen( true )] out IProblemDetailsService? problemDetailsService )
48+
{
49+
problemDetailsService = context.RequestServices.GetService<IProblemDetailsService>();
50+
return problemDetailsService is not null;
51+
}
4552
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning;
4+
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.Net.Http.Headers;
7+
8+
// REF: https://github.com/dotnet/aspnetcore/blob/release/7.0/src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs
9+
internal sealed class Rfc7231ProblemDetailsWriter : IProblemDetailsWriter
10+
{
11+
private static readonly MediaTypeHeaderValue jsonMediaType = new( "application/json" );
12+
private static readonly MediaTypeHeaderValue problemDetailsJsonMediaType = new( "application/problem+json" );
13+
private readonly IProblemDetailsWriter decorated;
14+
15+
public Rfc7231ProblemDetailsWriter( IProblemDetailsWriter decorated ) => this.decorated = decorated;
16+
17+
public bool CanWrite( ProblemDetailsContext context )
18+
{
19+
if ( decorated.CanWrite( context ) )
20+
{
21+
return true;
22+
}
23+
24+
var httpContext = context.HttpContext;
25+
var accept = httpContext.Request.Headers.Accept;
26+
27+
// REF: https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
28+
if ( accept.Count == 0 )
29+
{
30+
return true;
31+
}
32+
33+
var acceptHeader = httpContext.Request.GetTypedHeaders().Accept;
34+
35+
for ( var i = 0; i < acceptHeader.Count; i++ )
36+
{
37+
var acceptHeaderValue = acceptHeader[i];
38+
39+
if ( jsonMediaType.IsSubsetOf( acceptHeaderValue ) ||
40+
problemDetailsJsonMediaType.IsSubsetOf( acceptHeaderValue ) )
41+
{
42+
return true;
43+
}
44+
}
45+
46+
return false;
47+
}
48+
49+
public ValueTask WriteAsync( ProblemDetailsContext context ) => decorated.WriteAsync( context );
50+
}

src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/AmbiguousApiVersionEndpoint.cs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,25 @@ internal AmbiguousApiVersionEndpoint( ILogger logger )
1717

1818
private static Task OnExecute( HttpContext context, ILogger logger )
1919
{
20-
var services = context.RequestServices;
21-
var factory = services.GetRequiredService<IProblemDetailsFactory>();
2220
var apiVersions = context.ApiVersioningFeature().RawRequestedApiVersions;
23-
var (type, title) = ProblemDetailsDefaults.Ambiguous;
21+
22+
logger.ApiVersionAmbiguous( apiVersions.ToArray() );
23+
context.Response.StatusCode = StatusCodes.Status400BadRequest;
24+
25+
if ( !context.TryGetProblemDetailsService( out var problemDetails ) )
26+
{
27+
return Task.CompletedTask;
28+
}
29+
2430
var detail = string.Format(
2531
CultureInfo.CurrentCulture,
2632
CommonSR.MultipleDifferentApiVersionsRequested,
2733
string.Join( ", ", apiVersions ) );
28-
var problem = factory.CreateProblemDetails(
29-
context.Request,
30-
StatusCodes.Status400BadRequest,
31-
title,
32-
type,
33-
detail );
34-
35-
logger.ApiVersionAmbiguous( apiVersions.ToArray() );
36-
context.Response.StatusCode = StatusCodes.Status400BadRequest;
3734

38-
return context.Response.WriteAsJsonAsync(
39-
problem,
40-
options: default,
41-
contentType: ProblemDetailsDefaults.MediaType.Json );
35+
return problemDetails.WriteAsync(
36+
EndpointProblem.New(
37+
context,
38+
ProblemDetailsDefaults.Ambiguous,
39+
detail ) ).AsTask();
4240
}
4341
}

0 commit comments

Comments
 (0)