Skip to content

Commit 06d3d62

Browse files
Simplify the setup of Error Objects. Related to #1072
1 parent f9aa66d commit 06d3d62

File tree

2 files changed

+90
-30
lines changed

2 files changed

+90
-30
lines changed

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

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ namespace Microsoft.Extensions.DependencyInjection;
1111
using Microsoft.Extensions.DependencyInjection.Extensions;
1212
using Microsoft.Extensions.Options;
1313
using System;
14+
using System.Diagnostics.CodeAnalysis;
1415
using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor;
16+
using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
1517

1618
/// <summary>
1719
/// Provides extension methods for the <see cref="IServiceCollection"/> interface.
@@ -75,6 +77,65 @@ public static IApiVersioningBuilder EnableApiVersionBinding( this IApiVersioning
7577
return builder;
7678
}
7779

80+
/// <summary>
81+
/// Adds error object support in problem details.
82+
/// </summary>
83+
/// <param name="services">The <see cref="IServiceCollection">services</see> available in the application.</param>
84+
/// <param name="setup">The <see cref="JsonOptions">JSON options</see> setup <see cref="Action{T}"/> to perform, if any.</param>
85+
/// <returns>The original <paramref name="services"/>.</returns>
86+
/// <remarks>
87+
/// <para>
88+
/// This method is only intended to provide backward compatibility with previous library versions by converting
89+
/// <see cref="Microsoft.AspNetCore.Mvc.ProblemDetails"/> into Error Objects that conform to the
90+
/// <a ref="https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses">Error Responses</a>
91+
/// in the Microsoft REST API Guidelines and
92+
/// <a ref="https://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#_Toc38457793">OData Error Responses</a>.
93+
/// </para>
94+
/// <para>
95+
/// This method should be called before <see cref="ProblemDetailsServiceCollectionExtensions.AddProblemDetails(IServiceCollection)"/>.
96+
/// </para>
97+
/// </remarks>
98+
public static IServiceCollection AddErrorObjects( this IServiceCollection services, Action<JsonOptions>? setup = default ) =>
99+
AddErrorObjects<ErrorObjectWriter>( services, setup );
100+
101+
/// <summary>
102+
/// Adds error object support in problem details.
103+
/// </summary>
104+
/// <typeparam name="TWriter">The type of <see cref="ErrorObjectWriter"/>.</typeparam>
105+
/// <param name="services">The <see cref="IServiceCollection">services</see> available in the application.</param>
106+
/// <param name="setup">The <see cref="JsonOptions">JSON options</see> setup <see cref="Action{T}"/> to perform, if any.</param>
107+
/// <returns>The original <paramref name="services"/>.</returns>
108+
/// <remarks>
109+
/// <para>
110+
/// This method is only intended to provide backward compatibility with previous library versions by converting
111+
/// <see cref="Microsoft.AspNetCore.Mvc.ProblemDetails"/> into Error Objects that conform to the
112+
/// <a ref="https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses">Error Responses</a>
113+
/// in the Microsoft REST API Guidelines and
114+
/// <a ref="https://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#_Toc38457793">OData Error Responses</a>.
115+
/// </para>
116+
/// <para>
117+
/// This method should be called before <see cref="ProblemDetailsServiceCollectionExtensions.AddProblemDetails(IServiceCollection)"/>.
118+
/// </para>
119+
/// </remarks>
120+
public static IServiceCollection AddErrorObjects<[DynamicallyAccessedMembers( PublicConstructors )] TWriter>(
121+
this IServiceCollection services,
122+
Action<JsonOptions>? setup = default )
123+
where TWriter : ErrorObjectWriter
124+
{
125+
ArgumentNullException.ThrowIfNull( services );
126+
127+
services.TryAddEnumerable( Singleton<IProblemDetailsWriter, TWriter>() );
128+
services.Configure( setup ?? DefaultErrorObjectJsonConfig );
129+
130+
// TODO: remove with TryAddErrorObjectJsonOptions in 9.0+
131+
services.AddTransient<ErrorObjectsAdded>();
132+
133+
return services;
134+
}
135+
136+
private static void DefaultErrorObjectJsonConfig( JsonOptions options ) =>
137+
options.SerializerOptions.TypeInfoResolverChain.Insert( 0, ErrorObjectWriter.ErrorObjectJsonContext.Default );
138+
78139
private static void AddApiVersioningServices( IServiceCollection services )
79140
{
80141
ArgumentNullException.ThrowIfNull( services );
@@ -180,23 +241,46 @@ static Rfc7231ProblemDetailsWriter NewProblemDetailsWriter( IServiceProvider ser
180241
new( (IProblemDetailsWriter) serviceProvider.GetRequiredService( decoratedType ) );
181242
}
182243

244+
// TODO: retain for 8.1.x back-compat, but remove in 9.0+ in favor of AddErrorObjects for perf
183245
private static void TryAddErrorObjectJsonOptions( IServiceCollection services )
184246
{
185247
var serviceType = typeof( IProblemDetailsWriter );
186248
var implementationType = typeof( ErrorObjectWriter );
249+
var markerType = typeof( ErrorObjectsAdded );
250+
var hasErrorObjects = false;
251+
var hasErrorObjectsJsonConfig = false;
187252

188253
for ( var i = 0; i < services.Count; i++ )
189254
{
190255
var service = services[i];
191256

192-
// inheritance is intentionally not considered here because it will require a user-defined
193-
// JsonSerlizerContext and IConfigureOptions<JsonOptions>
194-
if ( service.ServiceType == serviceType &&
195-
service.ImplementationType == implementationType )
257+
if ( !hasErrorObjects &&
258+
service.ServiceType == serviceType &&
259+
implementationType.IsAssignableFrom( service.ImplementationType ) )
196260
{
197-
services.TryAddEnumerable( Singleton<IConfigureOptions<JsonOptions>, ErrorObjectJsonOptionsSetup>() );
198-
return;
261+
hasErrorObjects = true;
262+
263+
if ( hasErrorObjectsJsonConfig )
264+
{
265+
break;
266+
}
199267
}
268+
else if ( service.ServiceType == markerType )
269+
{
270+
hasErrorObjectsJsonConfig = true;
271+
272+
if ( hasErrorObjects )
273+
{
274+
break;
275+
}
276+
}
277+
}
278+
279+
if ( hasErrorObjects && !hasErrorObjectsJsonConfig )
280+
{
281+
services.Configure<JsonOptions>( DefaultErrorObjectJsonConfig );
200282
}
201283
}
284+
285+
private sealed class ErrorObjectsAdded { }
202286
}

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

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy