Skip to content

Commit 630c75b

Browse files
Simplify APIs
Pre-empt changes coming in a future preview, as it reduces the amount of code in the endpoints and gets rid of the need to cast to IResult for resolving ambiguity.
1 parent 6b17180 commit 630c75b

File tree

3 files changed

+89
-63
lines changed

3 files changed

+89
-63
lines changed

src/TodoApp/ApiModule.cs

Lines changed: 10 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Security.Claims;
55
using Microsoft.AspNetCore.Builder;
6-
using Microsoft.AspNetCore.Http;
76
using Microsoft.AspNetCore.Mvc;
87
using Microsoft.AspNetCore.Routing;
98
using TodoApp.Models;
@@ -19,10 +18,8 @@ public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder buil
1918
builder.MapGet("/api/items", async (
2019
ITodoService service,
2120
ClaimsPrincipal user,
22-
CancellationToken cancellationToken) =>
23-
{
24-
return await service.GetListAsync(user.GetUserId(), cancellationToken);
25-
}).RequireAuthorization();
21+
CancellationToken cancellationToken) => await service.GetListAsync(user.GetUserId(), cancellationToken))
22+
.RequireAuthorization();
2623

2724
// Get a specific Todo item
2825
builder.MapGet("/api/items/{id}", async (
@@ -32,13 +29,7 @@ public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder buil
3229
CancellationToken cancellationToken) =>
3330
{
3431
var model = await service.GetAsync(user.GetUserId(), id, cancellationToken);
35-
36-
if (model is null)
37-
{
38-
return new NotFoundResult();
39-
}
40-
41-
return new JsonResult(model) as IResult;
32+
return model is null ? Results.NotFound() : Results.Json(model);
4233
}).RequireAuthorization();
4334

4435
// Create a new Todo item
@@ -50,12 +41,12 @@ public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder buil
5041
{
5142
if (model is null || string.IsNullOrWhiteSpace(model.Text))
5243
{
53-
return new BadRequestResult();
44+
return Results.BadRequest();
5445
}
5546

5647
var id = await service.AddItemAsync(user.GetUserId(), model.Text, cancellationToken);
5748

58-
return new CreatedAtResult($"/api/items/{id}", new { id }) as IResult;
49+
return Results.Created($"/api/items/{id}", new { id });
5950
}).RequireAuthorization();
6051

6152
// Mark a Todo item as completed
@@ -67,14 +58,12 @@ public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder buil
6758
{
6859
var wasCompleted = await service.CompleteItemAsync(user.GetUserId(), id, cancellationToken);
6960

70-
var statusCode = wasCompleted switch
61+
return wasCompleted switch
7162
{
72-
true => StatusCodes.Status204NoContent,
73-
false => StatusCodes.Status400BadRequest,
74-
_ => StatusCodes.Status404NotFound,
63+
true => Results.NoContent(),
64+
false => Results.BadRequest(),
65+
_ => Results.NotFound(),
7566
};
76-
77-
return new StatusCodeResult(statusCode);
7867
}).RequireAuthorization();
7968

8069
// Delete a Todo item
@@ -85,36 +74,10 @@ public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder buil
8574
CancellationToken cancellationToken) =>
8675
{
8776
var wasDeleted = await service.DeleteItemAsync(user.GetUserId(), id, cancellationToken);
88-
89-
return new StatusCodeResult(wasDeleted ? StatusCodes.Status204NoContent : StatusCodes.Status404NotFound);
77+
return wasDeleted ? Results.NoContent() : Results.NotFound();
9078
}).RequireAuthorization();
9179

9280
return builder;
9381
}
94-
95-
// HACK Custom result types until CreatedAtResult/ObjectResult implements IResult.
96-
// See https://github.com/dotnet/aspnetcore/issues/32565.
97-
98-
private sealed class CreatedAtResult : IResult
99-
{
100-
private readonly string _location;
101-
private readonly object? _value;
102-
103-
internal CreatedAtResult(string location, object? value)
104-
{
105-
_location = location;
106-
_value = value;
107-
}
108-
109-
public async Task ExecuteAsync(HttpContext httpContext)
110-
{
111-
var response = httpContext.Response;
112-
113-
response.Headers.Location = _location;
114-
response.StatusCode = StatusCodes.Status201Created;
115-
116-
await response.WriteAsJsonAsync(_value, httpContext.RequestAborted);
117-
}
118-
}
11982
}
12083
}

src/TodoApp/AuthenticationModule.cs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using Microsoft.AspNetCore.Authentication;
77
using Microsoft.AspNetCore.Authentication.Cookies;
88
using Microsoft.AspNetCore.Builder;
9-
using Microsoft.AspNetCore.Http;
109
using Microsoft.AspNetCore.Routing;
1110
using Microsoft.Extensions.Configuration;
1211
using Microsoft.Extensions.DependencyInjection;
@@ -68,23 +67,19 @@ public static string GetUserName(this ClaimsPrincipal user)
6867

6968
public static IEndpointRouteBuilder MapAuthenticationRoutes(this IEndpointRouteBuilder builder)
7069
{
71-
// TODO Remove cast once supported by C# 10
72-
builder.MapGet(DeniedPath, (Action<HttpContext>)(context => context.Response.Redirect(RootPath + "?denied=true")));
73-
builder.MapGet(SignOutPath, (Action<HttpContext>)(context => context.Response.Redirect(RootPath)));
70+
builder.MapGet(DeniedPath, () => Results.Redirect(RootPath + "?denied=true"));
71+
builder.MapGet(SignOutPath, () => Results.Redirect(RootPath));
7472

75-
builder.MapPost(SignInPath, async context =>
76-
{
77-
await context.ChallengeAsync(
78-
GitHubAuthenticationDefaults.AuthenticationScheme,
79-
new AuthenticationProperties { RedirectUri = RootPath });
80-
});
73+
builder.MapPost(SignInPath, () =>
74+
Results.Challenge(
75+
new AuthenticationProperties { RedirectUri = RootPath },
76+
GitHubAuthenticationDefaults.AuthenticationScheme));
8177

82-
builder.MapPost(SignOutPath, async context =>
83-
{
84-
await context.SignOutAsync(
85-
CookieAuthenticationDefaults.AuthenticationScheme,
86-
new AuthenticationProperties { RedirectUri = RootPath });
87-
}).RequireAuthorization();
78+
builder.MapPost(SignOutPath, () =>
79+
Results.SignOut(
80+
new AuthenticationProperties { RedirectUri = RootPath },
81+
CookieAuthenticationDefaults.AuthenticationScheme))
82+
.RequireAuthorization();
8883

8984
return builder;
9085
}

src/TodoApp/Results.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) Martin Costello, 2021. All rights reserved.
2+
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3+
4+
using Microsoft.AspNetCore.Authentication;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Mvc;
7+
8+
namespace TodoApp
9+
{
10+
// Will be redundant when changes are available in a later preview.
11+
// See https://github.com/dotnet/aspnetcore/pull/33843.
12+
13+
internal sealed class Results
14+
{
15+
public static IResult BadRequest()
16+
=> new BadRequestResult();
17+
18+
public static IResult Challenge(AuthenticationProperties properties, params string[] authenticationSchemes)
19+
=> new ChallengeResult(authenticationSchemes, properties);
20+
21+
public static IResult Created(string url, object? value)
22+
=> new CreatedAtResult(url, value);
23+
24+
public static IResult Json(object? data)
25+
=> new JsonResult(data);
26+
27+
public static IResult NoContent()
28+
=> new NoContentResult();
29+
30+
public static IResult NotFound()
31+
=> new NotFoundResult();
32+
33+
public static IResult Redirect(string url)
34+
=> new RedirectResult(url);
35+
36+
public static IResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes)
37+
=> new SignOutResult(authenticationSchemes, properties);
38+
39+
public static IResult StatusCode(int statusCode)
40+
=> new StatusCodeResult(statusCode);
41+
42+
// HACK Custom result types until CreatedAtResult/ObjectResult implements IResult.
43+
// See https://github.com/dotnet/aspnetcore/issues/32565
44+
// and https://github.com/dotnet/aspnetcore/pull/33843.
45+
46+
private sealed class CreatedAtResult : IResult
47+
{
48+
private readonly string _location;
49+
private readonly object? _value;
50+
51+
internal CreatedAtResult(string location, object? value)
52+
{
53+
_location = location;
54+
_value = value;
55+
}
56+
57+
public async Task ExecuteAsync(HttpContext httpContext)
58+
{
59+
var response = httpContext.Response;
60+
61+
response.Headers.Location = _location;
62+
response.StatusCode = StatusCodes.Status201Created;
63+
64+
await response.WriteAsJsonAsync(_value, httpContext.RequestAborted);
65+
}
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)