@@ -11,7 +11,9 @@ namespace Microsoft.Extensions.DependencyInjection;
1111using Microsoft . Extensions . DependencyInjection . Extensions ;
1212using Microsoft . Extensions . Options ;
1313using System ;
14+ using System . Diagnostics . CodeAnalysis ;
1415using 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}
0 commit comments