Skip to content

Switch to minimal hosting and actions #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
24d9f00
Use WebApplicationBuilder
martincostello Jun 25, 2021
70bcaf5
Use minimal APIs
martincostello Jun 26, 2021
9908370
Bind item Ids as Guids
martincostello Jun 26, 2021
29489fb
Fix HTTP server for top-level programs
martincostello Jun 26, 2021
02778b2
Add workaround for configuration issue
martincostello Jun 27, 2021
232bb7c
Update comment
martincostello Jun 27, 2021
5b2591f
Wait indefinitely when debugging
martincostello Jun 27, 2021
ebb2720
Use Development
martincostello Jun 27, 2021
eabb94e
Simplify setup
martincostello Jun 27, 2021
3771886
Set minimum log level to debug
martincostello Jun 27, 2021
213cfa6
Use JsonResult
martincostello Jun 28, 2021
a386758
Simplify APIs
martincostello Jun 28, 2021
bdb3e4f
Update to a preview 7 daily build
martincostello Jun 30, 2021
74b0537
Remove timeout workaround
martincostello Jun 30, 2021
4a6df18
Use LangVersion latest
martincostello Jun 30, 2021
0263242
Add workaround for Visual Studio error
martincostello Jul 1, 2021
ca7ca66
Add Swashbuckle.AspNetCore
martincostello Jul 1, 2021
4e7584f
Use correct namespace
martincostello Jul 3, 2021
a6c0da6
Add extension method to ignore APIs
martincostello Jul 3, 2021
a6ec5dd
Update .NET SDK
martincostello Jul 4, 2021
12f6d0d
Revert to LangVersion preview
martincostello Jul 4, 2021
61f648b
Add test with TestServer
martincostello Jul 4, 2021
8965ff0
Fix rebase
martincostello Jul 4, 2021
2e9258f
Remove custom Results class
martincostello Jul 5, 2021
6f9aa36
Update .NET SDK
martincostello Jul 6, 2021
b03dfcb
Update .NET SDK
martincostello Jul 8, 2021
dd1cef5
Document status codes
martincostello Jul 8, 2021
a58d232
Update .NET SDK
martincostello Jul 9, 2021
acad411
Shuffle some workarounds
martincostello Jul 11, 2021
6037bc3
Update .NET SDK
martincostello Jul 11, 2021
c8b76e1
Use Results.Problem()
martincostello Jul 11, 2021
331910f
Remove route attributes
martincostello Jul 12, 2021
c2f1e2b
Use target-typed constructor
martincostello Jul 12, 2021
d995ff6
Update .NET SDK
martincostello Jul 14, 2021
17b9fde
Move workaround to properties file
martincostello Jul 14, 2021
c420122
Remove Visual Studio workarounds
martincostello Jul 14, 2021
b352f21
File-scoped namespaces
martincostello Jul 14, 2021
ff5508f
Update .NET SDK to RC1
martincostello Jul 19, 2021
163dca0
Update .NET SDK
martincostello Jul 19, 2021
c5f89a8
Refactor Open API extensions
martincostello Jul 20, 2021
064005f
Tweak formatting
martincostello Jul 20, 2021
14388d5
Update .NET SDK
martincostello Jul 22, 2021
f3651bb
Update .NET SDK
martincostello Jul 23, 2021
1e3a954
Update .NET SDK
martincostello Jul 25, 2021
966b6e6
Remove unused method
martincostello Jul 27, 2021
7dee0c3
Update .NET SDK
martincostello Jul 28, 2021
4dd02a1
Add compiler workaround for Visual Studio 2022
martincostello Jul 29, 2021
eff22a6
Update .NET SDK
martincostello Jul 29, 2021
46c3522
Fix warnings
martincostello Jul 29, 2021
4ef079a
Fix spacing
martincostello Jul 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@
<Copyright>Martin Costello (c) $([System.DateTime]::Now.ToString(yyyy))</Copyright>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<LangVersion>preview</LangVersion>
<LangVersion>latest</LangVersion>
<NeutralLanguage>en-US</NeutralLanguage>
<!--
HACK Disable due to CS0105 for System when using global usings.
See https://github.com/dotnet/roslyn/issues/54255.
-->
<NoWarn>$(NoWarn);CS0105</NoWarn>
<Nullable>enable</Nullable>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/martincostello/dotnet-minimal-api-integration-testing</PackageProjectUrl>
Expand All @@ -27,4 +22,14 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)GlobalUsings.cs" />
</ItemGroup>
<!--
HACK Workaround for issues building with file-scoped
namespaces in Visual Studio 2022 17.0 Preview 2.
-->
<ItemGroup Condition="'$(BuildingInsideVisualStudio)' == 'true'">
<PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="4.0.0-3.21373.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
5 changes: 0 additions & 5 deletions GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// Copyright (c) Martin Costello, 2021. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

global using System;
global using System.Collections.Generic;
global using System.Globalization;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;

#if TEST_PROJECT
global using Shouldly;
Expand Down
2 changes: 2 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<configuration>
<packageSources>
<add key="aspnet-contrib" value="https://www.myget.org/F/aspnet-contrib/api/v3/index.json" />
<add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" />
<add key="roslyn" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
1 change: 1 addition & 0 deletions TodoApp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
global.json = global.json
GlobalUsings.cs = GlobalUsings.cs
LICENSE = LICENSE
NuGet.config = NuGet.config
README.md = README.md
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.100-preview.6.21355.2",
"version": "6.0.100-rc.1.21379.12",
"allowPrerelease": false,
"rollForward": "latestMajor"
}
Expand Down
159 changes: 76 additions & 83 deletions src/TodoApp/ApiModule.cs
Original file line number Diff line number Diff line change
@@ -1,105 +1,98 @@
// Copyright (c) Martin Costello, 2021. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using System.Security.Claims;
using TodoApp.Models;
using TodoApp.Services;

namespace TodoApp
namespace TodoApp;

public static class ApiModule
{
public static class ApiModule
public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder builder)
{
private const string ItemCompletePath = "/api/items/{id}/complete";
private const string ItemPath = "/api/items/{id}";
private const string ItemsPath = "/api/items";

public static IEndpointRouteBuilder MapApiRoutes(this IEndpointRouteBuilder builder)
{
builder.MapPost(ItemsPath, async (context) =>
// Get all Todo items
builder.MapGet("/api/items", async (
ITodoService service,
ClaimsPrincipal user,
CancellationToken cancellationToken) =>
await service.GetListAsync(user.GetUserId(), cancellationToken))
.RequireAuthorization();

// Get a specific Todo item
builder.MapGet("/api/items/{id}", async (
Guid id,
ClaimsPrincipal user,
ITodoService service,
CancellationToken cancellationToken) =>
{
var model = await service.GetAsync(user.GetUserId(), id, cancellationToken);
return model is null ? Results.Problem(statusCode: StatusCodes.Status404NotFound) : Results.Json(model);
})
.Produces<TodoItemModel>()
.ProducesProblem(StatusCodes.Status404NotFound)
.RequireAuthorization();

// Create a new Todo item
builder.MapPost("/api/items", async (
CreateTodoItemModel model,
ClaimsPrincipal user,
ITodoService service,
CancellationToken cancellationToken) =>
{
var model = await context.Request.ReadFromJsonAsync<CreateTodoItemModel>();

if (model is null || string.IsNullOrWhiteSpace(model.Text))
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
return Results.Problem(statusCode: StatusCodes.Status400BadRequest);
}

var userId = context.GetUserId();
var service = context.RequestServices.GetRequiredService<ITodoService>();

var id = await service.AddItemAsync(userId, model.Text, context.RequestAborted);

context.Response.StatusCode = StatusCodes.Status201Created;
context.Response.GetTypedHeaders().Location = new Uri($"/api/items/{id}", UriKind.Relative);

await context.Response.WriteAsJsonAsync(new { id });
}).RequireAuthorization();

builder.MapGet(ItemsPath, async (context) =>
{
var userId = context.GetUserId();
var service = context.GetTodoService();

var model = await service.GetListAsync(userId, context.RequestAborted);

await context.Response.WriteAsJsonAsync(model);
}).RequireAuthorization();

builder.MapGet(ItemPath, async (context) =>
{
var itemId = context.GetItemId();
var userId = context.GetUserId();
var service = context.GetTodoService();

var model = await service.GetAsync(userId, itemId, context.RequestAborted);

if (model is null)
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
return;
}
var id = await service.AddItemAsync(user.GetUserId(), model.Text, cancellationToken);

await context.Response.WriteAsJsonAsync(model);
}).RequireAuthorization();
return Results.Created($"/api/items/{id}", new { id });
})
.Produces(StatusCodes.Status201Created)
.ProducesProblem(StatusCodes.Status400BadRequest)
.RequireAuthorization();

builder.MapPost(ItemCompletePath, async (context) =>
// Mark a Todo item as completed
builder.MapPost("/api/items/{id}/complete", async (
Guid id,
ClaimsPrincipal user,
ITodoService service,
CancellationToken cancellationToken) =>
{
var itemId = context.GetItemId();
var userId = context.GetUserId();
var service = context.GetTodoService();

var result = await service.CompleteItemAsync(userId, itemId, context.RequestAborted);
var wasCompleted = await service.CompleteItemAsync(user.GetUserId(), id, cancellationToken);

context.Response.StatusCode = result switch
return wasCompleted switch
{
true => StatusCodes.Status204NoContent,
false => StatusCodes.Status400BadRequest,
_ => StatusCodes.Status404NotFound,
true => Results.NoContent(),
false => Results.Problem(statusCode: StatusCodes.Status400BadRequest),
_ => Results.Problem(statusCode: StatusCodes.Status404NotFound),
};
}).RequireAuthorization();

builder.MapDelete(ItemPath, async (context) =>
})
.Produces(StatusCodes.Status204NoContent)
.ProducesProblem(StatusCodes.Status400BadRequest)
.ProducesProblem(StatusCodes.Status404NotFound)
.RequireAuthorization();

// Delete a Todo item
builder.MapDelete("/api/items/{id}", async (
Guid id,
ClaimsPrincipal user,
ITodoService service,
CancellationToken cancellationToken) =>
{
var itemId = context.GetItemId();
var userId = context.GetUserId();
var service = context.GetTodoService();

var result = await service.DeleteItemAsync(userId, itemId, context.RequestAborted);

context.Response.StatusCode = result ? StatusCodes.Status204NoContent : StatusCodes.Status404NotFound;
}).RequireAuthorization();

return builder;
}

private static string GetItemId(this HttpContext context)
=> (string)context.GetRouteValue("id")!;

private static ITodoService GetTodoService(this HttpContext context)
=> context.RequestServices.GetRequiredService<ITodoService>();
var wasDeleted = await service.DeleteItemAsync(user.GetUserId(), id, cancellationToken);
return wasDeleted ? Results.NoContent() : Results.Problem(statusCode: StatusCodes.Status404NotFound);
})
.Produces(StatusCodes.Status204NoContent)
.ProducesProblem(StatusCodes.Status404NotFound)
.RequireAuthorization();

// Redirect to Open API/Swagger documentation
builder.MapGet("/api", () => Results.Redirect("/swagger-ui/index.html"))
.ExcludeFromApiExplorer()
.RequireAuthorization();

return builder;
}
}
Loading