Skip to content

Commit 489de75

Browse files
authored
Add public metadata types. (#49068)
* Add public metadata types.
1 parent 82e1738 commit 489de75

File tree

70 files changed

+218
-1243
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+218
-1243
lines changed

src/Shared/RoutingMetadata/AcceptsMetadata.cs renamed to src/Http/Http.Abstractions/src/Metadata/AcceptsMetadata.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,21 @@
33

44
#nullable enable
55

6-
using System;
7-
using System.Collections.Generic;
8-
96
namespace Microsoft.AspNetCore.Http.Metadata;
107

118
/// <summary>
129
/// Metadata that specifies the supported request content types.
1310
/// </summary>
14-
internal sealed class AcceptsMetadata : IAcceptsMetadata
11+
public sealed class AcceptsMetadata : IAcceptsMetadata
1512
{
16-
/// <summary>
17-
/// Creates a new instance of <see cref="AcceptsMetadata"/>.
18-
/// </summary>
19-
public AcceptsMetadata(string[] contentTypes)
20-
{
21-
ArgumentNullException.ThrowIfNull(contentTypes);
22-
23-
ContentTypes = contentTypes;
24-
}
25-
2613
/// <summary>
2714
/// Creates a new instance of <see cref="AcceptsMetadata"/> with a type.
2815
/// </summary>
29-
public AcceptsMetadata(Type? type, bool isOptional, string[] contentTypes)
16+
/// <param name="contentTypes">Content types that are accepted by endpoint.</param>
17+
/// <param name="type">The type being read from the request.</param>
18+
/// <param name="isOptional">Whether the request body is optional.</param>
19+
public AcceptsMetadata(string[] contentTypes, Type? type = null, bool isOptional = false)
3020
{
31-
ArgumentNullException.ThrowIfNull(type);
3221
ArgumentNullException.ThrowIfNull(contentTypes);
3322

3423
RequestType = type;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using Microsoft.AspNetCore.Http.Metadata;
6+
using Microsoft.Net.Http.Headers;
7+
8+
namespace Microsoft.AspNetCore.Http;
9+
10+
/// <summary>
11+
/// Specifies the type of the value and status code returned by the action.
12+
/// </summary>
13+
public sealed class ProducesResponseTypeMetadata : IProducesResponseTypeMetadata
14+
{
15+
/// <summary>
16+
/// Initializes an instance of <see cref="ProducesResponseTypeMetadata"/>.
17+
/// </summary>
18+
/// <param name="statusCode">The HTTP response status code.</param>
19+
/// <param name="type">The <see cref="Type"/> of object that is going to be written in the response.</param>
20+
/// <param name="contentTypes">Content types supported by the response.</param>
21+
public ProducesResponseTypeMetadata(int statusCode, Type? type = null, string[]? contentTypes = null)
22+
{
23+
this.StatusCode = statusCode;
24+
this.Type = type;
25+
26+
if (contentTypes is null || contentTypes.Length == 0)
27+
{
28+
this.ContentTypes = Enumerable.Empty<string>();
29+
}
30+
else
31+
{
32+
for (var i = 0; i < contentTypes.Length; i++)
33+
{
34+
MediaTypeHeaderValue.Parse(contentTypes[i]);
35+
ValidateContentType(contentTypes[i]);
36+
}
37+
38+
this.ContentTypes = contentTypes;
39+
}
40+
41+
static void ValidateContentType(string type)
42+
{
43+
if (type.Contains('*', StringComparison.OrdinalIgnoreCase))
44+
{
45+
throw new InvalidOperationException($"Could not parse '{type}'. Content types with wildcards are not supported.");
46+
}
47+
}
48+
}
49+
50+
// Only for internal use where validation is unnecessary.
51+
private ProducesResponseTypeMetadata(int statusCode, Type? type, IEnumerable<string> contentTypes)
52+
{
53+
Type = type;
54+
StatusCode = statusCode;
55+
ContentTypes = contentTypes;
56+
}
57+
58+
/// <summary>
59+
/// Gets or sets the type of the value returned by an action.
60+
/// </summary>
61+
public Type? Type { get; private set; }
62+
63+
/// <summary>
64+
/// Gets or sets the HTTP status code of the response.
65+
/// </summary>
66+
public int StatusCode { get; private set; }
67+
68+
/// <summary>
69+
/// Gets or sets the content types associated with the response.
70+
/// </summary>
71+
public IEnumerable<string> ContentTypes { get; private set; }
72+
73+
internal static ProducesResponseTypeMetadata CreateUnvalidated(Type? type, int statusCode, IEnumerable<string> contentTypes) => new(statusCode, type, contentTypes);
74+
}

src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@ Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult
66
Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
77
Microsoft.AspNetCore.Http.HttpValidationProblemDetails.Errors.set -> void
88
Microsoft.AspNetCore.Http.IProblemDetailsService.TryWriteAsync(Microsoft.AspNetCore.Http.ProblemDetailsContext! context) -> System.Threading.Tasks.ValueTask<bool>
9+
Microsoft.AspNetCore.Http.Metadata.AcceptsMetadata
10+
Microsoft.AspNetCore.Http.Metadata.AcceptsMetadata.AcceptsMetadata(string![]! contentTypes, System.Type? type = null, bool isOptional = false) -> void
11+
Microsoft.AspNetCore.Http.Metadata.AcceptsMetadata.ContentTypes.get -> System.Collections.Generic.IReadOnlyList<string!>!
12+
Microsoft.AspNetCore.Http.Metadata.AcceptsMetadata.IsOptional.get -> bool
13+
Microsoft.AspNetCore.Http.Metadata.AcceptsMetadata.RequestType.get -> System.Type?
914
Microsoft.AspNetCore.Http.Metadata.IRouteDiagnosticsMetadata
1015
Microsoft.AspNetCore.Http.Metadata.IRouteDiagnosticsMetadata.Route.get -> string!
1116
Microsoft.AspNetCore.Http.ProblemDetailsContext.Exception.get -> System.Exception?
1217
Microsoft.AspNetCore.Http.ProblemDetailsContext.Exception.init -> void
1318
*REMOVED*Microsoft.AspNetCore.Http.ProblemDetailsContext.ProblemDetails.init -> void
1419
Microsoft.AspNetCore.Http.ProblemDetailsContext.ProblemDetails.set -> void
20+
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata
21+
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.ContentTypes.get -> System.Collections.Generic.IEnumerable<string!>!
22+
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.ProducesResponseTypeMetadata(int statusCode, System.Type? type = null, string![]? contentTypes = null) -> void
23+
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.StatusCode.get -> int
24+
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Type.get -> System.Type?
1525
Microsoft.AspNetCore.Mvc.ProblemDetails.Extensions.set -> void
1626
static Microsoft.AspNetCore.Http.EndpointFilterInvocationContext.Create(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> Microsoft.AspNetCore.Http.EndpointFilterInvocationContext!
1727
static Microsoft.AspNetCore.Http.EndpointFilterInvocationContext.Create<T1, T2, T3, T4, T5, T6, T7, T8>(Microsoft.AspNetCore.Http.HttpContext! httpContext, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) -> Microsoft.AspNetCore.Http.EndpointFilterInvocationContext!

src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,16 +247,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
247247
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
248248
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
249249

250-
if (hasFormBody || hasJsonBody)
251-
{
252-
codeWriter.WriteLine(RequestDelegateGeneratorSources.AcceptsMetadataType);
253-
}
254-
255-
if (hasResponseMetadata)
256-
{
257-
codeWriter.WriteLine(RequestDelegateGeneratorSources.ProducesResponseTypeMetadataType);
258-
}
259-
260250
if (hasFormBody || hasJsonBody || hasResponseMetadata)
261251
{
262252
codeWriter.WriteLine(RequestDelegateGeneratorSources.ContentTypeConstantsType);

src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -30,55 +30,6 @@ file static class GeneratedMetadataConstants
3030
public static readonly string[] FormContentType = new[] { "multipart/form-data", "application/x-www-form-urlencoded" };
3131
}
3232
33-
""";
34-
35-
public static string ProducesResponseTypeMetadataType => $$"""
36-
{{GeneratedCodeAttribute}}
37-
file sealed class GeneratedProducesResponseTypeMetadata : IProducesResponseTypeMetadata
38-
{
39-
public GeneratedProducesResponseTypeMetadata(Type? type, int statusCode, string[] contentTypes)
40-
{
41-
Type = type;
42-
StatusCode = statusCode;
43-
ContentTypes = contentTypes;
44-
}
45-
46-
public Type? Type { get; }
47-
48-
public int StatusCode { get; }
49-
50-
public IEnumerable<string> ContentTypes { get; }
51-
}
52-
53-
""";
54-
55-
public static string AcceptsMetadataType => $$"""
56-
{{GeneratedCodeAttribute}}
57-
file sealed class GeneratedAcceptsMetadata : IAcceptsMetadata
58-
{
59-
public GeneratedAcceptsMetadata(string[] contentTypes)
60-
{
61-
ArgumentNullException.ThrowIfNull(contentTypes);
62-
63-
ContentTypes = contentTypes;
64-
}
65-
66-
public GeneratedAcceptsMetadata(Type? type, bool isOptional, string[] contentTypes)
67-
{
68-
ArgumentNullException.ThrowIfNull(type);
69-
ArgumentNullException.ThrowIfNull(contentTypes);
70-
71-
RequestType = type;
72-
ContentTypes = contentTypes;
73-
IsOptional = isOptional;
74-
}
75-
76-
public IReadOnlyList<string> ContentTypes { get; }
77-
78-
public Type? RequestType { get; }
79-
80-
public bool IsOptional { get; }
81-
}
8233
""";
8334

8435
public static string PopulateEndpointMetadataMethod => """

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,11 @@ private static void EmitBuiltinResponseTypeMetadata(this Endpoint endpoint, Code
192192

193193
if (responseType.SpecialType == SpecialType.System_String)
194194
{
195-
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new GeneratedProducesResponseTypeMetadata(type: null, statusCode: StatusCodes.Status200OK, contentTypes: GeneratedMetadataConstants.PlaintextContentType));");
195+
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, contentTypes: GeneratedMetadataConstants.PlaintextContentType));");
196196
}
197197
else
198198
{
199-
codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new GeneratedProducesResponseTypeMetadata(type: typeof({responseType.ToDisplayString(EmitterConstants.DisplayFormatWithoutNullability)}), statusCode: StatusCodes.Status200OK, contentTypes: GeneratedMetadataConstants.JsonContentType));");
199+
codeWriter.WriteLine($$"""options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof({{responseType.ToDisplayString(EmitterConstants.DisplayFormatWithoutNullability)}}), contentTypes: GeneratedMetadataConstants.JsonContentType));""");
200200
}
201201
}
202202

@@ -264,11 +264,11 @@ public static void EmitFormAcceptsMetadata(this Endpoint endpoint, CodeWriter co
264264

265265
if (hasFormFiles)
266266
{
267-
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new GeneratedAcceptsMetadata(contentTypes: GeneratedMetadataConstants.FormFileContentType));");
267+
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.FormFileContentType));");
268268
}
269269
else
270270
{
271-
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new GeneratedAcceptsMetadata(contentTypes: GeneratedMetadataConstants.FormContentType));");
271+
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.FormContentType));");
272272
}
273273
}
274274

@@ -292,7 +292,7 @@ public static void EmitJsonAcceptsMetadata(this Endpoint endpoint, CodeWriter co
292292

293293
if (explicitBodyParameter != null)
294294
{
295-
codeWriter.WriteLine($$"""options.EndpointBuilder.Metadata.Add(new GeneratedAcceptsMetadata(type: typeof({{explicitBodyParameter.Type.ToDisplayString(EmitterConstants.DisplayFormatWithoutNullability)}}), isOptional: {{(explicitBodyParameter.IsOptional ? "true" : "false")}}, contentTypes: GeneratedMetadataConstants.JsonContentType));""");
295+
codeWriter.WriteLine($$"""options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: typeof({{explicitBodyParameter.Type.ToDisplayString(EmitterConstants.DisplayFormatWithoutNullability)}}), isOptional: {{(explicitBodyParameter.IsOptional ? "true" : "false")}}, contentTypes: GeneratedMetadataConstants.JsonContentType));""");
296296
}
297297
else if (potentialImplicitBodyParameters.Count > 0)
298298
{
@@ -311,14 +311,14 @@ public static void EmitJsonAcceptsMetadata(this Endpoint endpoint, CodeWriter co
311311
codeWriter.StartBlock();
312312
codeWriter.WriteLine("if (!serviceProviderIsService.IsService(type))");
313313
codeWriter.StartBlock();
314-
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new GeneratedAcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType));");
314+
codeWriter.WriteLine("options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type: type, isOptional: isOptional, contentTypes: GeneratedMetadataConstants.JsonContentType));");
315315
codeWriter.WriteLine("break;");
316316
codeWriter.EndBlock();
317317
codeWriter.EndBlock();
318318
}
319319
else
320320
{
321-
codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new GeneratedAcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType));");
321+
codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes: GeneratedMetadataConstants.JsonContentType));");
322322
}
323323
}
324324

src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
<Compile Include="$(SharedSourceRoot)PropertyAsParameterInfo.cs" LinkBase="Shared" />
2020
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" LinkBase="Shared" />
2121
<Compile Include="$(SharedSourceRoot)ApiExplorerTypes\*.cs" LinkBase="Shared" />
22-
<Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
2322
<Compile Include="$(SharedSourceRoot)TypeNameHelper\TypeNameHelper.cs" LinkBase="Shared" />
2423
<Compile Include="$(SharedSourceRoot)ProblemDetails\ProblemDetailsDefaults.cs" LinkBase="Shared" />
2524
<Compile Include="$(SharedSourceRoot)ValueStringBuilder\**\*.cs" LinkBase="Shared" />

src/Http/Http.Extensions/src/RequestDelegateFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1915,7 +1915,7 @@ private static void AddInferredAcceptsMetadata(RequestDelegateFactoryContext fac
19151915
return;
19161916
}
19171917

1918-
factoryContext.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type, factoryContext.AllowEmptyRequestBody, contentTypes));
1918+
factoryContext.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes, type, factoryContext.AllowEmptyRequestBody));
19191919
}
19201920

19211921
private static void InferFormAcceptsMetadata(RequestDelegateFactoryContext factoryContext)

0 commit comments

Comments
 (0)