@@ -11,7 +11,9 @@ namespace Microsoft.Extensions.DependencyInjection;
11
11
using Microsoft . Extensions . DependencyInjection . Extensions ;
12
12
using Microsoft . Extensions . Options ;
13
13
using System ;
14
+ using System . Diagnostics . CodeAnalysis ;
14
15
using static Microsoft . Extensions . DependencyInjection . ServiceDescriptor ;
16
+ using static System . Diagnostics . CodeAnalysis . DynamicallyAccessedMemberTypes ;
15
17
16
18
/// <summary>
17
19
/// Provides extension methods for the <see cref="IServiceCollection"/> interface.
@@ -75,6 +77,65 @@ public static IApiVersioningBuilder EnableApiVersionBinding( this IApiVersioning
75
77
return builder ;
76
78
}
77
79
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
+
78
139
private static void AddApiVersioningServices ( IServiceCollection services )
79
140
{
80
141
ArgumentNullException . ThrowIfNull ( services ) ;
@@ -180,23 +241,46 @@ static Rfc7231ProblemDetailsWriter NewProblemDetailsWriter( IServiceProvider ser
180
241
new ( ( IProblemDetailsWriter ) serviceProvider . GetRequiredService ( decoratedType ) ) ;
181
242
}
182
243
244
+ // TODO: retain for 8.1.x back-compat, but remove in 9.0+ in favor of AddErrorObjects for perf
183
245
private static void TryAddErrorObjectJsonOptions ( IServiceCollection services )
184
246
{
185
247
var serviceType = typeof ( IProblemDetailsWriter ) ;
186
248
var implementationType = typeof ( ErrorObjectWriter ) ;
249
+ var markerType = typeof ( ErrorObjectsAdded ) ;
250
+ var hasErrorObjects = false ;
251
+ var hasErrorObjectsJsonConfig = false ;
187
252
188
253
for ( var i = 0 ; i < services . Count ; i ++ )
189
254
{
190
255
var service = services [ i ] ;
191
256
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 ) )
196
260
{
197
- services . TryAddEnumerable ( Singleton < IConfigureOptions < JsonOptions > , ErrorObjectJsonOptionsSetup > ( ) ) ;
198
- return ;
261
+ hasErrorObjects = true ;
262
+
263
+ if ( hasErrorObjectsJsonConfig )
264
+ {
265
+ break ;
266
+ }
199
267
}
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 ) ;
200
282
}
201
283
}
284
+
285
+ private sealed class ErrorObjectsAdded { }
202
286
}
0 commit comments