diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 174e5edd..e81737c7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": ".NET in Codespaces", - "image": "mcr.microsoft.com/dotnet/sdk:8.0", + "image": "mcr.microsoft.com/dotnet/sdk:9.0", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/github-cli:1": { @@ -15,8 +15,8 @@ "ghcr.io/devcontainers/features/common-utils:2": {}, "ghcr.io/devcontainers/features/dotnet:2": { "version": "none", - "dotnetRuntimeVersions": "7.0", - "aspNetCoreRuntimeVersions": "7.0" + "dotnetRuntimeVersions": "8.0", + "aspNetCoreRuntimeVersions": "8.0" } }, "customizations": { @@ -42,7 +42,7 @@ }, "remoteEnv": { "DOTNET_MULTILEVEL_LOOKUP": "0", - "TARGET": "net8.0" + "TARGET": "net9.0" }, "portsAttributes": { "8080": { diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..06ac4a31 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: Build Backend and Frontend + +on: + pull_request: + branches: + - main + +jobs: + build: + name: Build All Projects + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + project: + - SampleApp/BackEnd/BackEnd.csproj + - SampleApp/FrontEnd/FrontEnd.csproj + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Restore dependencies + run: dotnet restore ${{ matrix.project }} + + - name: Build project + run: dotnet build ${{ matrix.project }} --no-restore --configuration Release \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 4bc6f5f5..dc541dbb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,25 +8,25 @@ "order": 1 }, "configurations": [ - "Back End", - "Front End" + "BackEnd", + "FrontEnd" ] } ], "configurations": [ { - "name": "Back End", + "name": "BackEnd", "type": "coreclr", "request": "launch", "preLaunchTask": "build backend", - "program": "${workspaceFolder}/SampleApp/BackEnd/bin/Debug/net8.0/BackEnd.dll", + "program": "${workspaceFolder}/SampleApp/BackEnd/bin/Debug/net9.0/BackEnd.dll", "args": [], "cwd": "${workspaceFolder}/SampleApp/BackEnd", "stopAtEntry": false, "serverReadyAction": { "action": "openExternally", "pattern": "\\bNow listening on:\\s+(https?://\\S+)", - "uriFormat": "%s/swagger" + "uriFormat": "%s/scalar" }, "env": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -36,11 +36,11 @@ } }, { - "name": "Front End", + "name": "FrontEnd", "type": "coreclr", "request": "launch", "preLaunchTask": "build frontend", - "program": "${workspaceFolder}/SampleApp/FrontEnd/bin/Debug/net8.0/FrontEnd.dll", + "program": "${workspaceFolder}/SampleApp/FrontEnd/bin/Debug/net9.0/FrontEnd.dll", "args": [], "cwd": "${workspaceFolder}/SampleApp/FrontEnd", "stopAtEntry": false, diff --git a/SampleApp/BackEnd/BackEnd.csproj b/SampleApp/BackEnd/BackEnd.csproj index 7ae4f5a0..f5c98ff1 100644 --- a/SampleApp/BackEnd/BackEnd.csproj +++ b/SampleApp/BackEnd/BackEnd.csproj @@ -1,15 +1,15 @@ - net8.0 + net9.0 enable enable true - - + + diff --git a/SampleApp/BackEnd/Program.cs b/SampleApp/BackEnd/Program.cs index 284b7004..22ccf8d7 100644 --- a/SampleApp/BackEnd/Program.cs +++ b/SampleApp/BackEnd/Program.cs @@ -1,19 +1,32 @@ +using Microsoft.AspNetCore.OpenApi; +using Scalar.AspNetCore; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(options => +{ + // current workaround for port forwarding in codespaces + // https://github.com/dotnet/aspnetcore/issues/57332 + options.AddDocumentTransformer((document, context, ct) => + { + document.Servers = []; + return Task.CompletedTask; + }); +}); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(); + app.MapOpenApi(); + app.MapScalarApiReference(); } +app.UseHttpsRedirection(); + var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" @@ -31,8 +44,7 @@ .ToArray(); return forecast; }) -.WithName("GetWeatherForecast") -.WithOpenApi(); +.WithName("GetWeatherForecast"); app.Run(); diff --git a/SampleApp/FrontEnd/Data/WeatherForecast.cs b/SampleApp/FrontEnd/Data/WeatherForecast.cs index 57d51507..15463daf 100644 --- a/SampleApp/FrontEnd/Data/WeatherForecast.cs +++ b/SampleApp/FrontEnd/Data/WeatherForecast.cs @@ -1,13 +1,12 @@ -namespace FrontEnd.Data +namespace FrontEnd.Data; + +public class WeatherForecast { - public class WeatherForecast - { - public DateOnly Date { get; set; } + public DateOnly Date { get; set; } - public int TemperatureC { get; set; } + public int TemperatureC { get; set; } - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - public string? Summary { get; set; } - } -} \ No newline at end of file + public string? Summary { get; set; } +} diff --git a/SampleApp/FrontEnd/Data/WeatherForecastClient.cs b/SampleApp/FrontEnd/Data/WeatherForecastClient.cs index 00917bc7..37c894a6 100644 --- a/SampleApp/FrontEnd/Data/WeatherForecastClient.cs +++ b/SampleApp/FrontEnd/Data/WeatherForecastClient.cs @@ -1,17 +1,16 @@ -namespace FrontEnd.Data -{ - public class WeatherForecastClient - { - private HttpClient _httpClient; - private ILogger _logger; +namespace FrontEnd.Data; - public WeatherForecastClient(HttpClient httpClient, ILogger logger) - { - _httpClient = httpClient; - _logger = logger; - } +public class WeatherForecastClient +{ + private HttpClient _httpClient; + private ILogger _logger; - public async Task GetForecastAsync(DateTime? startDate) - => await _httpClient.GetFromJsonAsync($"WeatherForecast?startDate={startDate}"); + public WeatherForecastClient(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; } -} \ No newline at end of file + + public async Task GetForecastAsync(DateTime? startDate) + => await _httpClient.GetFromJsonAsync($"WeatherForecast?startDate={startDate}") ?? []; +} diff --git a/SampleApp/FrontEnd/FrontEnd.csproj b/SampleApp/FrontEnd/FrontEnd.csproj index 1b28a01c..6568b3dc 100644 --- a/SampleApp/FrontEnd/FrontEnd.csproj +++ b/SampleApp/FrontEnd/FrontEnd.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable diff --git a/SampleApp/FrontEnd/Pages/Error.cshtml.cs b/SampleApp/FrontEnd/Pages/Error.cshtml.cs index eb52375f..3facc117 100644 --- a/SampleApp/FrontEnd/Pages/Error.cshtml.cs +++ b/SampleApp/FrontEnd/Pages/Error.cshtml.cs @@ -2,26 +2,25 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using System.Diagnostics; -namespace FrontEnd.Pages +namespace FrontEnd.Pages; + +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel { - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - [IgnoreAntiforgeryToken] - public class ErrorModel : PageModel - { - public string? RequestId { get; set; } + public string? RequestId { get; set; } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - private readonly ILogger _logger; + private readonly ILogger _logger; - public ErrorModel(ILogger logger) - { - _logger = logger; - } + public ErrorModel(ILogger logger) + { + _logger = logger; + } - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; } -} \ No newline at end of file +} diff --git a/images/Swagger.png b/images/Swagger.png deleted file mode 100644 index 14ee7c00..00000000 Binary files a/images/Swagger.png and /dev/null differ diff --git a/images/scalar.png b/images/scalar.png new file mode 100644 index 00000000..1194db90 Binary files /dev/null and b/images/scalar.png differ diff --git a/readme.md b/readme.md index 010ec754..8b592cf4 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ -# GitHub Codespaces ♥️ .NET 8 +# GitHub Codespaces ♥️ .NET -Want to try out the latest performance improvements coming with .NET 8 for web development? +Want to try out the latest performance improvements coming with .NET for web development? -This repo builds a Weather API using Minimal APIs, opens Swagger so you can call and test the API, and displays the data in a web application using Blazor with .NET 8. +This repo builds a Weather API, OpenAPI integration to test with [Scalar](https://learn.microsoft.com/aspnet/core/fundamentals/openapi/using-openapi-documents?view=aspnetcore-9.0#use-scalar-for-interactive-api-documentation), and displays the data in a web application using Blazor with .NET. We've given you both a frontend and backend to play around with and where you go from here is up to you! @@ -14,7 +14,7 @@ Everything you do here is contained within this one codespace. There is no repos [![Open in Dev Container](https://img.shields.io/static/v1?style=for-the-badge&label=Dev+Container&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/github/dotnet-codespaces) You can also run this repository locally by following these instructions: -1. Clone the repo to your local machine `git clone https://github.com/bradygaster/dotnet-codespace` +1. Clone the repo to your local machine `git clone https://github.com/github/dotnet-codespace` 1. Open repo in VS Code ## Getting started @@ -22,19 +22,20 @@ You can also run this repository locally by following these instructions: 1. **📤 One-click setup**: [Open a new Codespace](https://codespaces.new/github/dotnet-codespaces), giving you a fully configured cloud developer environment. 2. **▶️ Run all, one-click again**: Use VS Code's built-in *Run* command and open the forwarded ports *8080* and *8081* in your browser. -![](images/RunAll.png) +![Debug menu in VS Code showing Run All](images/RunAll.png) -3. The Blazor web app and Swagger tabs should now be open on your browser. On Swagger, click "Try it out" and "Execute" to call and test the API. +3. The Blazor web app and Scalar can be open by heading to **/scalar** in your browser. On Scalar, head to the backend API and click "Test Request" to call and test the API. -![](images/BlazorApp.png) -![](images/Swagger.png) +![A website showing weather](images/BlazorApp.png) + +!["UI showing testing an API"](images/scalar.png) 4. **🔄 Iterate quickly:** Codespaces updates the server on each save, and VS Code's debugger lets you dig into the code execution. 5. To stop running, return to VS Code, and click Stop twice in the debug toolbar. -![](images/StopRun.png) +![VS Code stop debuggin on both backend and frontend](images/StopRun.png) ## Contributing