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)