Skip to content

Commit 2b44d67

Browse files
committed
Updated for latest rc.1 changes
1 parent b5f19ea commit 2b44d67

21 files changed

+207
-359
lines changed

src/MinimalApiPlayground/MinimalApiPlayground.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>net6.0</TargetFramework>
55
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
67
</PropertyGroup>
78

89
<ItemGroup>
@@ -12,7 +13,7 @@
1213
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1314
</PackageReference>
1415
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.0-rc.1.*" />
15-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
16+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
1617
<PackageReference Include="MinimalValidation" Version="0.1.0-pre" />
1718
</ItemGroup>
1819

src/MinimalApiPlayground/Program.cs

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
11
using System.ComponentModel.DataAnnotations;
22
using Microsoft.AspNetCore.Mvc;
33
using Microsoft.EntityFrameworkCore;
4+
using System.IO;
45

56
var builder = WebApplication.CreateBuilder(args);
67

78
var connectionString = builder.Configuration.GetConnectionString("Todos") ?? "Data Source=todos.db";
89

9-
builder.Services.AddSqlite<TodoDb>(connectionString);
10-
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
10+
builder.Services.AddSqlite<TodoDb>(connectionString)
11+
.AddDatabaseDeveloperPageExceptionFilter();
1112

1213
var app = builder.Build();
1314

14-
if (app.Environment.IsDevelopment())
15-
{
16-
app.UseDeveloperExceptionPage();
17-
}
18-
else
15+
if (!app.Environment.IsDevelopment())
1916
{
2017
app.UseExceptionHandler("/error");
2118
}
2219

23-
// Issues with UseExceptionHandler() if this isn't explicitly called: https://github.com/dotnet/aspnetcore/issues/34146
24-
app.UseRouting();
25-
2620
app.MapGet("/", () => "Hello World")
27-
.WithMetadata(new EndpointNameMetadata("HelloWorldApi"));
21+
.WithMetadata(new EndpointNameAttribute("HelloWorldApi"));
2822

2923
app.MapGet("/hello", () => new { Hello = "World" });
3024

@@ -36,29 +30,31 @@
3630
<h1>Hello World</h1>
3731
<p>The time on the server is {DateTime.Now.ToString("O")}</p>
3832
</body>
39-
</html>"));
33+
</html>"))
34+
.ExcludeFromDescription();
4035

4136
app.MapGet("/throw", () => { throw new Exception("uh oh"); })
42-
.Ignore();
37+
.ExcludeFromDescription();
4338

4439
app.MapGet("/error", () => Results.Problem("An error occurred.", statusCode: 500))
45-
.ProducesProblem()
46-
.Ignore();
40+
.ExcludeFromDescription();
4741

4842
app.MapGet("/todos/sample", () => new[] {
4943
new Todo { Id = 1, Title = "Do this" },
5044
new Todo { Id = 2, Title = "Do this too" }
5145
})
52-
.Ignore();
46+
.ExcludeFromDescription();
5347

5448
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
5549
.WithName("GetAllTodos");
5650

5751
app.MapGet("/todos/incompleted", async (TodoDb db) => await db.Todos.Where(t => !t.IsComplete).ToListAsync())
58-
.WithName("GetIncompletedTodos");
52+
.WithName("GetIncompletedTodos")
53+
.Produces<Todo[]>();
5954

6055
app.MapGet("/todos/completed", async (TodoDb db) => await db.Todos.Where(t => t.IsComplete).ToListAsync())
61-
.WithName("GetCompletedTodos");
56+
.WithName("GetCompletedTodos")
57+
.Produces<List<Todo>>();
6258

6359
app.MapGet("/todos/{id}", async (int id, TodoDb db) =>
6460
{
@@ -68,7 +64,7 @@ is Todo todo
6864
: Results.NotFound();
6965
})
7066
.WithName("GetTodoById")
71-
.Produces<Todo>()
67+
.Produces<List<Todo>>()
7268
.Produces(StatusCodes.Status404NotFound);
7369

7470
app.MapPost("/todos", async (Todo todo, TodoDb db) =>
@@ -85,6 +81,37 @@ is Todo todo
8581
.ProducesValidationProblem()
8682
.Produces<Todo>(StatusCodes.Status201Created);
8783

84+
// Example of manually supporting more than JSON for input/output
85+
app.MapPost("/todos/xmlorjson", async (HttpRequest request, TodoDb db) =>
86+
{
87+
string contentType = request.Headers["Content-Type"];
88+
89+
var todo = contentType switch
90+
{
91+
"application/json" => await request.Body.ReadAsJsonAsync<Todo>(),
92+
"application/xml" => await request.Body.ReadAsXmlAsync<Todo>(request.ContentLength),
93+
_ => null,
94+
};
95+
96+
if (todo is null)
97+
{
98+
return Results.StatusCode(StatusCodes.Status415UnsupportedMediaType);
99+
}
100+
101+
if (!MinimalValidation.TryValidate(todo, out var errors))
102+
return Results.ValidationProblem(errors);
103+
104+
db.Todos.Add(todo);
105+
await db.SaveChangesAsync();
106+
107+
return AppResults.Created(todo, contentType);
108+
})
109+
.WithName("AddTodoXmlOrJson")
110+
.Accepts<Todo>("application/json", "application/xml")
111+
.Produces(StatusCodes.Status415UnsupportedMediaType)
112+
.ProducesValidationProblem()
113+
.Produces<Todo>(StatusCodes.Status201Created, "application/json", "application/xml");
114+
88115
// Example of adding the above endpoint but using attributes to describe it instead
89116
app.MapPost("/todos-local-func", AddTodoFunc);
90117

@@ -184,7 +211,7 @@ async Task<IResult> AddTodoFunc(Todo todo, TodoDb db)
184211

185212
app.Run();
186213

187-
class Todo
214+
public class Todo
188215
{
189216
public int Id { get; set; }
190217
[Required] public string? Title { get; set; }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Microsoft.AspNetCore.Mvc.Formatters;
2+
3+
namespace Microsoft.AspNetCore.Mvc.ApiExplorer;
4+
5+
public class ConsumesRequestTypeAttribute : ConsumesAttribute, IApiRequestMetadataProvider2
6+
{
7+
public ConsumesRequestTypeAttribute(string contentType, params string[] otherContentTypes)
8+
: base(contentType, otherContentTypes)
9+
{
10+
11+
}
12+
13+
public ConsumesRequestTypeAttribute(Type type)
14+
: base("")
15+
{
16+
Type = type ?? throw new ArgumentNullException(nameof(type));
17+
ContentTypes = new MediaTypeCollection();
18+
}
19+
20+
public Type? Type { get; set; }
21+
}
22+
23+
public interface IApiRequestMetadataProvider2 : IApiRequestMetadataProvider
24+
{
25+
Type? Type { get; }
26+
}
Lines changed: 17 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,35 @@
1-
using Microsoft.AspNetCore.Http.Extensions;
2-
using Microsoft.AspNetCore.Mvc;
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
33

44
namespace Microsoft.AspNetCore.Http;
55

6-
public static class OpenApiEndpointConventionBuilderExtensions
6+
public static class EndpointConventionBuilderExtensions
77
{
8-
private static readonly EndpointIgnoreMetadata IgnoreMetadata = new();
9-
10-
/// <summary>
11-
/// Adds an EndpointNameMetadata item to the Metadata for all endpoints produced by the builder.<br />
12-
/// The name is used to lookup the endpoint during link generation and as an operationId when generating OpenAPI documentation.<br />
13-
/// The name must be unique per endpoint.
14-
/// </summary>
15-
/// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
16-
/// <param name="name">The endpoint name.</param>
17-
/// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
18-
public static IEndpointConventionBuilder WithName(this IEndpointConventionBuilder builder, string name)
19-
{
20-
// Once Swashbuckle issue is fixed this will set operationId in the swagger doc
21-
// https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2165
22-
builder.WithMetadata(new EndpointNameMetadata(name));
23-
24-
return builder;
25-
}
26-
27-
/// <summary>
28-
/// Adds an EndpointGroupNameMetadata item to the Metadata for all endpoints produced by the builder.
29-
/// </summary>
30-
/// <param name="builder"></param>
31-
/// <param name="groupName"></param>
32-
/// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
33-
public static IEndpointConventionBuilder WithGroupName(this IEndpointConventionBuilder builder, string groupName)
8+
public static MinimalActionEndpointConventionBuilder Accepts(this MinimalActionEndpointConventionBuilder builder, string contentType, params string[] otherContentTypes)
349
{
35-
// Swashbuckle uses group name to match APIs with OpenApi documents by default
36-
// See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs#L59
37-
// Minimal APIs currently doesn't populate the ApiDescription with a group name but we will change that so this can work as intended.
38-
// Note that EndpointGroupNameMetadata doesn't exist in ASP.NET Core today so we'll have to add that too.
39-
builder.WithMetadata(new EndpointGroupNameMetadata(groupName));
10+
builder.WithMetadata(new ConsumesAttribute(contentType, otherContentTypes));
4011

4112
return builder;
4213
}
4314

44-
/// <summary>
45-
/// Adds metadata indicating an endpoint should be ignored by consumers of endpoint metadata, e.g. when generating OpenAPI documentation.
46-
/// </summary>
47-
/// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
48-
/// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
49-
public static IEndpointConventionBuilder Ignore(this IEndpointConventionBuilder builder)
15+
public static MinimalActionEndpointConventionBuilder Accepts<T>(this MinimalActionEndpointConventionBuilder builder, string? contentType = null, params string[] otherContentTypes)
5016
{
51-
// Swashbuckle won't include an API in a given document if it has a group name set and it doesn't match the document name,
52-
// so setting the group name to a random value effectively hides the API from all OpenAPI documents by default
53-
// See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs#L59
54-
// We may instead want to add a more first-class piece of metadata to indicate the endpoint should be ignored from metadata readers,
55-
// e.g. https://github.com/dotnet/aspnetcore/issues/34068, which of course will require updating Swashbuckle to honor this too.
56-
builder.WithMetadata(IgnoreMetadata);
17+
Accepts(builder, typeof(T), contentType, otherContentTypes);
5718

5819
return builder;
5920
}
6021

61-
/// <summary>
62-
/// Adds metadata indicating the type of response an endpoint produces.
63-
/// </summary>
64-
/// <typeparam name="TResponse">The type of the response.</typeparam>
65-
/// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
66-
/// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status200OK.</param>
67-
/// <param name="contentType">The response content type. Defaults to "application/json"</param>
68-
/// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
69-
/// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
70-
public static IEndpointConventionBuilder Produces<TResponse>(this IEndpointConventionBuilder builder,
71-
int statusCode = StatusCodes.Status200OK,
72-
string? contentType = "application/json",
73-
params string[] additionalContentTypes)
74-
{
75-
return Produces(builder, statusCode, typeof(TResponse), contentType, additionalContentTypes);
76-
}
77-
78-
/// <summary>
79-
/// Adds metadata indicating the type of response an endpoint produces.
80-
/// </summary>
81-
/// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
82-
/// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status200OK.</param>
83-
/// <param name="responseType">The type of the response. Defaults to null.</param>
84-
/// <param name="contentType">The response content type. Defaults to "application/json" if responseType is not null, otherwise defaults to null.</param>
85-
/// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
86-
/// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
87-
public static IEndpointConventionBuilder Produces(this IEndpointConventionBuilder builder,
88-
int statusCode = StatusCodes.Status200OK,
89-
Type? responseType = null,
90-
string? contentType = null,
91-
params string[] additionalContentTypes)
22+
public static MinimalActionEndpointConventionBuilder Accepts(this MinimalActionEndpointConventionBuilder builder, Type requestType, string? contentType = null, params string[] otherContentTypes)
9223
{
93-
if (responseType is Type && string.IsNullOrEmpty(contentType))
94-
{
95-
contentType = "application/json";
96-
}
97-
98-
builder.WithMetadata(new ProducesMetadataAttribute(responseType, statusCode, contentType, additionalContentTypes));
24+
builder.WithMetadata(
25+
contentType is object
26+
? new ConsumesRequestTypeAttribute(contentType, otherContentTypes)
27+
{
28+
Type = requestType
29+
}
30+
: new ConsumesRequestTypeAttribute(requestType)
31+
);
9932

10033
return builder;
10134
}
102-
103-
/// <summary>
104-
/// Adds metadata indicating that the endpoint produces a Problem Details response.
105-
/// </summary>
106-
/// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
107-
/// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status500InternalServerError.</param>
108-
/// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
109-
/// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
110-
public static IEndpointConventionBuilder ProducesProblem(this IEndpointConventionBuilder builder,
111-
int statusCode = StatusCodes.Status500InternalServerError,
112-
string contentType = "application/problem+json")
113-
{
114-
return Produces<ProblemDetails>(builder, statusCode, contentType);
115-
}
116-
117-
/// <summary>
118-
/// Adds metadata indicating that the endpoint produces a Problem Details response including validation errors.
119-
/// </summary>
120-
/// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
121-
/// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status400BadRequest.</param>
122-
/// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
123-
/// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
124-
public static IEndpointConventionBuilder ProducesValidationProblem(this IEndpointConventionBuilder builder,
125-
int statusCode = StatusCodes.Status400BadRequest,
126-
string contentType = "application/problem+json")
127-
{
128-
return Produces<HttpValidationProblemDetails>(builder, statusCode, contentType);
129-
}
130-
}
35+
}

src/MinimalApiPlayground/Properties/EndpointIgnoreMetadata.cs

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

src/MinimalApiPlayground/Properties/EndpointName.cs

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

0 commit comments

Comments
 (0)