diff --git a/docs/public/images/tutorial-scalar.png b/docs/public/images/tutorial-scalar.png new file mode 100644 index 0000000..98298b5 Binary files /dev/null and b/docs/public/images/tutorial-scalar.png differ diff --git a/docs/src/content/docs/getting-started/quick-start.mdx b/docs/src/content/docs/getting-started/quick-start.mdx index 0ece881..1dfaf3b 100644 --- a/docs/src/content/docs/getting-started/quick-start.mdx +++ b/docs/src/content/docs/getting-started/quick-start.mdx @@ -1,32 +1,36 @@ --- title: "Quick Start" -description: "Set up AxisEndpoints in your ASP.NET Core project and create your first endpoint." +description: "Create a minimal AxisEndpoints project from scratch and verify it with Scalar." --- import { Steps } from '@astrojs/starlight/components'; -1. **Create a project** +1. **Create a new project** ```sh - dotnet new webapi -n MyApi - cd MyApi + dotnet new webapi -n AxisEndpoints.Tutorial + cd AxisEndpoints.Tutorial ``` -2. **Install AxisEndpoints** +2. **Install the packages** ```sh dotnet add package AxisEndpoints + dotnet add package Scalar.AspNetCore ``` -3. **Set up Program.cs** + `AxisEndpoints` provides endpoint discovery and mapping. `Scalar.AspNetCore` gives you a browser-based API reference so you can verify the endpoint immediately. - Update `Program.cs` as shown below. `AddAxisEndpoints()` scans the entry assembly and automatically registers every class that implements the `IEndpoint` interface in the DI container. `MapAxisEndpoints()` maps the registered endpoints to the Minimal API pipeline. +3. **Replace `Program.cs`** - Filter classes that implement `IEndpointFilter` are also registered automatically through assembly scanning. + Use the same minimal setup as [`tests/AxisEndpoints.Tutorial/Program.cs`](https://github.com/sheepla/AxisEndpoints/blob/master/tests/AxisEndpoints.Tutorial/Program.cs): + + `AddAxisEndpoints()` scans the entry assembly and registers endpoint classes in DI. `MapAxisEndpoints()` then maps those endpoints into the Minimal API pipeline. ```csharp using AxisEndpoints.Extensions; + using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); @@ -37,63 +41,113 @@ import { Steps } from '@astrojs/starlight/components'; app.MapOpenApi(); app.MapAxisEndpoints(); + app.MapScalarApiReference(); app.Run(); ``` -4. **Create your first endpoint** +4. **Add your first endpoint** + + Create `Features/Hello/HelloEndpoint.cs` and paste in the tutorial endpoint below. - Create a new class `CreateUserEndpoint` that implements `IEndpoint>` at `CreateUserEndpoint.cs`. This endpoint will handle POST requests to create a new user. + ```text + AxisEndpoints.Tutorial/ + |- Features/ + | \- Hello/ + | \- HelloEndpoint.cs + \- Program.cs + ``` ```csharp - public class CreateUserRequest + using AxisEndpoints; + using System.Net; + + namespace AxisEndpoints.Tutorial.Features.Hello; + + public record HelloRequest { - public required string Name { get; init; } - public required string Email { get; init; } + public required string Name { get; set; } = string.Empty; } - public class CreateUserResponse + public record HelloResponse { - public required int Id { get; init; } + public required string Message { get; set; } = string.Empty; } - public class CreateUserEndpoint : IEndpoint> + public class HelloEndpoint(ILogger logger) : IEndpoint { - private readonly IUserRepository _repository; - - public CreateUserEndpoint(IUserRepository repository) => _repository = repository; - public void Configure(IEndpointConfiguration config) { - config.Post("/users") - .Tags("Users") - .Summary("Create a new user"); + config + .Get("/hello") + .ProducesSuccess() + .ProducesError(HttpStatusCode.BadRequest) + .Summary("Hello") + .Description("This endpoint takes a name as input and returns a greeting message."); } - public async Task> HandleAsync( - CreateUserRequest request, + public Task HandleAsync( + HelloRequest request, CancellationToken cancel) { - var id = await _repository.CreateAsync(request.Name, request.Email, cancel); - - return new Response + if (string.IsNullOrWhiteSpace(request.Name)) { - StatusCode = HttpStatusCode.Created, - Headers = [("Location", $"/users/{id}")], - Body = new CreateUserResponse { Id = id }, - }; + logger.LogWarning("Rejected request to /hello because the name was missing."); + return Task.FromResult(Results.Problem( + title: "Name is required", + detail: "Provide a non-empty name query parameter.", + statusCode: StatusCodes.Status400BadRequest + )); + } + + logger.LogInformation("Received request to /hello with name: {Name}", request.Name); + + return Task.FromResult( + Results.Json(new HelloResponse + { + Message = $"Hello, {request.Name}!" + }) + ); } } ``` -5. **Run and verify** + Because this is a `GET` endpoint, `HelloRequest.Name` is bound from the query string. A request to `/hello?name=Alice` will populate `request.Name` with `Alice`. + +5. **Run the app** ```sh dotnet run ``` - Send a POST request to `http://localhost:5000/users` to verify that the endpoint works. +6. **Verify the endpoint** + + Open the Scalar API reference at `http://localhost:{port}/scalar` and try `GET /hello` with a `name` query parameter. + + You can also call the endpoint directly: + + ```sh + curl "http://localhost:{port}/hello?name=Alice" + ``` + + The response should look like this: + + ```json + { + "message": "Hello, Alice!" + } + ``` -For more details on defining endpoints, see the [Defining Endpoints](/guides/defining-endpoints/) guide. +## What You Should See + +The tutorial project included in this repository uses the same setup and exposes the Scalar UI after startup. + +Scalar API reference showing the tutorial hello endpoint + +For more details on endpoint structure and request binding, see [Defining Endpoints](/guides/defining-endpoints/) and [Request Binding](/guides/request-binding/). diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 1055f5c..b1806b9 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -14,38 +14,50 @@ hero: variant: minimal --- -import { Card, CardGrid } from '@astrojs/starlight/components'; +import { Card, CardGrid } from "@astrojs/starlight/components"; ## About **AxisEndpoints** is a DSL for implementing the Request-Endpoint-Response (REPR) pattern in ASP.NET Core. It consolidates each API endpoint into a self-contained class with a clear, explicit programming interface. ```csharp -public class CreateUserEndpoint : IEndpoint> +using AxisEndpoints; + +namespace AxisEndpoints.Tutorial.Features.Hello; + +public record HelloRequest { - private readonly IUserRepository _repository; + public required string Name { get; set; } = string.Empty; +} - public CreateUserEndpoint(IUserRepository repository) => _repository = repository; +public record HelloResponse +{ + public required string Message { get; set; } = string.Empty; +} +public class HelloEndpoint(ILogger logger) + : IEndpoint> +{ public void Configure(IEndpointConfiguration config) { - config.Post("/users") - .Tags("Users") - .Summary("Create a new user"); + config + .Get("/hello") + .Summary("Hello") + .Description("This endpoint takes a name as input and returns a greeting message."); } - public async Task> HandleAsync( - CreateUserRequest request, + public Task> HandleAsync( + HelloRequest request, CancellationToken cancel) { - var id = await _repository.CreateAsync(request.Name, request.Email, cancel); - - return new Response - { - StatusCode = HttpStatusCode.Created, - Headers = [("Location", $"/users/{id}")], - Body = new CreateUserResponse { Id = id }, - }; + logger.LogInformation("Received request to /hello with name: {Name}", request.Name); + + return Task.FromResult( + new Response + { + Body = new HelloResponse { Message = $"Hello, {request.Name}!" }, + } + ); } } ``` @@ -65,7 +77,7 @@ ASP.NET Core offers three approaches to building Web APIs. Each involves differe | | Minimal API | Controller | REPR Pattern (AxisEndpoints) | | ------------------- | --------------------------------------------- | ------------------------------------------- | ---------------------------------------------- | | **Structure** | Functions registered inline | Methods grouped in a class | One class per endpoint | -| **Scalability** | ⚠ Can become hard to manage as endpoints grow | ⚠ Controllers can grow bloated over time | ✅ Each endpoint stays self-contained | +| **Scalability** | ⚠ Can become hard to manage as endpoints grow | ⚠ Controllers can grow bloated over time | ✅ Each endpoint stays self-contained | | **Coupling** | Low — but no enforced structure | Medium — CRUD operations share a controller | Low — slices are independent by design | | **Learning curve** | Low | Medium (MVC conventions) | Medium (built on Minimal API) | | **Best suited for** | Small services, prototypes | CRUD-heavy APIs familiar to MVC developers | Feature-rich APIs, Vertical Slice Architecture | @@ -82,16 +94,20 @@ To implement the REPR pattern in ASP.NET Core, you can either write a thin wrapp ## Next steps - - Get AxisEndpoints installed in your project. See the [Installation guide](/getting-started/installation/). - - - Learn how to define endpoints, bind requests, handle responses, and more. See the [Guides](/guides/defining-endpoints/). - - - Add typed CSV import and export to your endpoints with the optional CsvHelper extension. See the [CSV Helper extension](/extensions/csv-helper/). - - - Find answers to common questions in the [FAQ](/faq/). - + + Get AxisEndpoints installed in your project. See the [Installation + guide](/getting-started/installation/). + + + Learn how to define endpoints, bind requests, handle responses, and more. + See the [Guides](/guides/defining-endpoints/). + + + Add typed CSV import and export to your endpoints with the optional + CsvHelper extension. See the [CSV Helper + extension](/extensions/csv-helper/). + + + Find answers to common questions in the [FAQ](/faq/). + diff --git a/tests/AxisEndpoints.Example/Program.cs b/tests/AxisEndpoints.Example/Program.cs index bb92a2b..1389e4b 100644 --- a/tests/AxisEndpoints.Example/Program.cs +++ b/tests/AxisEndpoints.Example/Program.cs @@ -18,7 +18,7 @@ if (app.Environment.IsDevelopment()) { app.MapOpenApi(); - // OpenAPI document available at /scalar/v1 + // OpenAPI document available at /scalar app.MapScalarApiReference(); } diff --git a/tests/AxisEndpoints.Tests/AxisEndpoints.Tests.csproj.lscache b/tests/AxisEndpoints.Tests/AxisEndpoints.Tests.csproj.lscache index adf0896..50c2596 100644 --- a/tests/AxisEndpoints.Tests/AxisEndpoints.Tests.csproj.lscache +++ b/tests/AxisEndpoints.Tests/AxisEndpoints.Tests.csproj.lscache @@ -54,7 +54,7 @@ TemporaryDependencyNodeTargetIdentifier=net10.0 /warnaserror+:NU1605,SYSLIB0011 [sourceFiles] -../../.dotnet-cli/.nuget/packages/microsoft.net.test.sdk/17.14.1/build/net8.0/Microsoft.NET.Test.Sdk.Program.cs +/microsoft.net.test.sdk/17.14.1/build/net8.0/Microsoft.NET.Test.Sdk.Program.cs Integration/ EndpointIntegrationTests.cs TestEndpoints.cs @@ -70,56 +70,6 @@ Unit/ ServiceRegistrationTests.cs [metadataReferences] -../../.dotnet-cli/.nuget/packages/ - fluentassertions/8.9.0/lib/net6.0/FluentAssertions.dll - microsoft.aspnetcore.mvc.testing/10.0.7/lib/net10.0/Microsoft.AspNetCore.Mvc.Testing.dll - microsoft.aspnetcore.openapi/10.0.0/lib/net10.0/Microsoft.AspNetCore.OpenApi.dll - microsoft.aspnetcore.testhost/10.0.7/lib/net10.0/Microsoft.AspNetCore.TestHost.dll - microsoft.codecoverage/17.14.1/lib/net8.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll - microsoft.extensions.configuration.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll - microsoft.extensions.configuration.binder/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll - microsoft.extensions.configuration.commandline/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.CommandLine.dll - microsoft.extensions.configuration.environmentvariables/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll - microsoft.extensions.configuration.fileextensions/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.FileExtensions.dll - microsoft.extensions.configuration.json/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.Json.dll - microsoft.extensions.configuration.usersecrets/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.UserSecrets.dll - microsoft.extensions.configuration/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.dll - microsoft.extensions.dependencyinjection.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll - microsoft.extensions.dependencyinjection/10.0.7/lib/net10.0/Microsoft.Extensions.DependencyInjection.dll - microsoft.extensions.dependencymodel/10.0.7/lib/net10.0/Microsoft.Extensions.DependencyModel.dll - microsoft.extensions.diagnostics.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll - microsoft.extensions.diagnostics/10.0.7/lib/net10.0/Microsoft.Extensions.Diagnostics.dll - microsoft.extensions.fileproviders.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.dll - microsoft.extensions.fileproviders.physical/10.0.7/lib/net10.0/Microsoft.Extensions.FileProviders.Physical.dll - microsoft.extensions.filesystemglobbing/10.0.7/lib/net10.0/Microsoft.Extensions.FileSystemGlobbing.dll - microsoft.extensions.hosting.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Hosting.Abstractions.dll - microsoft.extensions.hosting/10.0.7/lib/net10.0/Microsoft.Extensions.Hosting.dll - microsoft.extensions.logging.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll - microsoft.extensions.logging.configuration/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Configuration.dll - microsoft.extensions.logging.console/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Console.dll - microsoft.extensions.logging.debug/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Debug.dll - microsoft.extensions.logging.eventlog/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.EventLog.dll - microsoft.extensions.logging.eventsource/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.EventSource.dll - microsoft.extensions.logging/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.dll - microsoft.extensions.options.configurationextensions/10.0.7/lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll - microsoft.extensions.options/10.0.7/lib/net10.0/Microsoft.Extensions.Options.dll - microsoft.extensions.primitives/10.0.7/lib/net10.0/Microsoft.Extensions.Primitives.dll - microsoft.openapi/2.0.0/lib/net8.0/Microsoft.OpenApi.dll - microsoft.testplatform.testhost/17.14.1/lib/net8.0/ - Microsoft.TestPlatform.CommunicationUtilities.dll - Microsoft.TestPlatform.CoreUtilities.dll - Microsoft.TestPlatform.CrossPlatEngine.dll - Microsoft.TestPlatform.PlatformAbstractions.dll - Microsoft.TestPlatform.Utilities.dll - Microsoft.VisualStudio.TestPlatform.Common.dll - Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - testhost.dll - newtonsoft.json/13.0.3/lib/net6.0/Newtonsoft.Json.dll - system.diagnostics.eventlog/10.0.7/lib/net10.0/System.Diagnostics.EventLog.dll - xunit.abstractions/2.0.3/lib/netstandard2.0/xunit.abstractions.dll - xunit.assert/2.9.3/lib/net6.0/xunit.assert.dll - xunit.extensibility.core/2.9.3/lib/netstandard1.1/xunit.core.dll - xunit.extensibility.execution/2.9.3/lib/netstandard1.1/xunit.execution.dotnet.dll ../../src/AxisEndpoints/obj/Debug/net10.0/ref/AxisEndpoints.dll /packs/Microsoft.AspNetCore.App.Ref/10.0.2/ref/net10.0/ Microsoft.AspNetCore.Antiforgery.dll @@ -402,15 +352,58 @@ Unit/ System.Xml.XPath.dll System.Xml.XPath.XDocument.dll WindowsBase.dll +/ + fluentassertions/8.9.0/lib/net6.0/FluentAssertions.dll + microsoft.aspnetcore.mvc.testing/10.0.7/lib/net10.0/Microsoft.AspNetCore.Mvc.Testing.dll + microsoft.aspnetcore.openapi/10.0.0/lib/net10.0/Microsoft.AspNetCore.OpenApi.dll + microsoft.aspnetcore.testhost/10.0.7/lib/net10.0/Microsoft.AspNetCore.TestHost.dll + microsoft.codecoverage/17.14.1/lib/net8.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll + microsoft.extensions.configuration.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll + microsoft.extensions.configuration.binder/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll + microsoft.extensions.configuration.commandline/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.CommandLine.dll + microsoft.extensions.configuration.environmentvariables/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll + microsoft.extensions.configuration.fileextensions/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.FileExtensions.dll + microsoft.extensions.configuration.json/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.Json.dll + microsoft.extensions.configuration.usersecrets/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.UserSecrets.dll + microsoft.extensions.configuration/10.0.7/lib/net10.0/Microsoft.Extensions.Configuration.dll + microsoft.extensions.dependencyinjection.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll + microsoft.extensions.dependencyinjection/10.0.7/lib/net10.0/Microsoft.Extensions.DependencyInjection.dll + microsoft.extensions.dependencymodel/10.0.7/lib/net10.0/Microsoft.Extensions.DependencyModel.dll + microsoft.extensions.diagnostics.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll + microsoft.extensions.diagnostics/10.0.7/lib/net10.0/Microsoft.Extensions.Diagnostics.dll + microsoft.extensions.fileproviders.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.FileProviders.Abstractions.dll + microsoft.extensions.fileproviders.physical/10.0.7/lib/net10.0/Microsoft.Extensions.FileProviders.Physical.dll + microsoft.extensions.filesystemglobbing/10.0.7/lib/net10.0/Microsoft.Extensions.FileSystemGlobbing.dll + microsoft.extensions.hosting.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Hosting.Abstractions.dll + microsoft.extensions.hosting/10.0.7/lib/net10.0/Microsoft.Extensions.Hosting.dll + microsoft.extensions.logging.abstractions/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll + microsoft.extensions.logging.configuration/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Configuration.dll + microsoft.extensions.logging.console/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Console.dll + microsoft.extensions.logging.debug/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.Debug.dll + microsoft.extensions.logging.eventlog/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.EventLog.dll + microsoft.extensions.logging.eventsource/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.EventSource.dll + microsoft.extensions.logging/10.0.7/lib/net10.0/Microsoft.Extensions.Logging.dll + microsoft.extensions.options.configurationextensions/10.0.7/lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll + microsoft.extensions.options/10.0.7/lib/net10.0/Microsoft.Extensions.Options.dll + microsoft.extensions.primitives/10.0.7/lib/net10.0/Microsoft.Extensions.Primitives.dll + microsoft.openapi/2.0.0/lib/net8.0/Microsoft.OpenApi.dll + microsoft.testplatform.testhost/17.14.1/lib/net8.0/ + Microsoft.TestPlatform.CommunicationUtilities.dll + Microsoft.TestPlatform.CoreUtilities.dll + Microsoft.TestPlatform.CrossPlatEngine.dll + Microsoft.TestPlatform.PlatformAbstractions.dll + Microsoft.TestPlatform.Utilities.dll + Microsoft.VisualStudio.TestPlatform.Common.dll + Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + testhost.dll + newtonsoft.json/13.0.3/lib/net6.0/Newtonsoft.Json.dll + system.diagnostics.eventlog/10.0.7/lib/net10.0/System.Diagnostics.EventLog.dll + xunit.abstractions/2.0.3/lib/netstandard2.0/xunit.abstractions.dll + xunit.assert/2.9.3/lib/net6.0/xunit.assert.dll + xunit.extensibility.core/2.9.3/lib/netstandard1.1/xunit.core.dll + xunit.extensibility.execution/2.9.3/lib/netstandard1.1/xunit.execution.dotnet.dll [analyzerReferences] -../../.dotnet-cli/.nuget/packages/ - microsoft.aspnetcore.openapi/10.0.0/analyzers/dotnet/cs/Microsoft.AspNetCore.OpenApi.SourceGenerators.dll - microsoft.extensions.logging.abstractions/10.0.7/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll - microsoft.extensions.options/10.0.7/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll - xunit.analyzers/1.18.0/analyzers/dotnet/cs/ - xunit.analyzers.dll - xunit.analyzers.fixes.dll /packs/Microsoft.AspNetCore.App.Ref/10.0.2/analyzers/dotnet/cs/ Microsoft.AspNetCore.App.Analyzers.dll Microsoft.AspNetCore.App.CodeFixes.dll @@ -427,6 +420,13 @@ Unit/ /sdk/10.0.102/Sdks/Microsoft.NET.Sdk/analyzers/ Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll Microsoft.CodeAnalysis.NetAnalyzers.dll +/ + microsoft.aspnetcore.openapi/10.0.0/analyzers/dotnet/cs/Microsoft.AspNetCore.OpenApi.SourceGenerators.dll + microsoft.extensions.logging.abstractions/10.0.7/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll + microsoft.extensions.options/10.0.7/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll + xunit.analyzers/1.18.0/analyzers/dotnet/cs/ + xunit.analyzers.dll + xunit.analyzers.fixes.dll [analyzerConfigFiles] /sdk/10.0.102/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_10_default.globalconfig diff --git a/tests/AxisEndpoints.Tutorial/AxisEndpoints.Tutorial.csproj b/tests/AxisEndpoints.Tutorial/AxisEndpoints.Tutorial.csproj new file mode 100644 index 0000000..baa2dad --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/AxisEndpoints.Tutorial.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + enable + enable + + + + + + + + + diff --git a/tests/AxisEndpoints.Tutorial/AxisEndpoints.Tutorial.http b/tests/AxisEndpoints.Tutorial/AxisEndpoints.Tutorial.http new file mode 100644 index 0000000..1d1bf68 --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/AxisEndpoints.Tutorial.http @@ -0,0 +1,6 @@ +@AxisEndpoints.Tutorial_HostAddress = http://localhost:5173 + +GET {{AxisEndpoints.Tutorial_HostAddress}}/hello?name=Alice +Accept: application/json + +### diff --git a/tests/AxisEndpoints.Tutorial/Features/Hello/HelloEndpoint.cs b/tests/AxisEndpoints.Tutorial/Features/Hello/HelloEndpoint.cs new file mode 100644 index 0000000..4306cba --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/Features/Hello/HelloEndpoint.cs @@ -0,0 +1,51 @@ +using System.Net; +using AxisEndpoints; + +namespace AxisEndpoints.Tutorial.Features.Hello; + +public record HelloRequest +{ + public required string Name { get; set; } = string.Empty; +} + +public record HelloResponse +{ + public required string Message { get; set; } = string.Empty; +} + +public class HelloEndpoint(ILogger logger) : IEndpoint +{ + public void Configure(IEndpointConfiguration config) + { + config + .Get("/hello") + .ProducesSuccess() + .ProducesError(HttpStatusCode.BadRequest) + .Summary("Hello") + .Description("This endpoint takes a name as input and returns a greeting message."); + } + + public Task HandleAsync(HelloRequest request, CancellationToken cancel) + { + if (string.IsNullOrWhiteSpace(request.Name)) + { + logger.LogWarning("Rejected request to /hello because the name was missing."); + return Task.FromResult( + Results.Problem( + title: "Name is required", + detail: "Provide a non-empty name query parameter.", + statusCode: StatusCodes.Status400BadRequest + ) + ); + } + + logger.LogInformation("Received request to /hello with name: {Name}", request.Name); + + return Task.FromResult( + Results.Json(new HelloResponse + { + Message = $"Hello, {request.Name}!", + }) + ); + } +} diff --git a/tests/AxisEndpoints.Tutorial/Program.cs b/tests/AxisEndpoints.Tutorial/Program.cs new file mode 100644 index 0000000..e7b61bc --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/Program.cs @@ -0,0 +1,21 @@ +using AxisEndpoints.Extensions; +using Scalar.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddOpenApi(); +builder.Services.AddAxisEndpoints(); + +var app = builder.Build(); + +// Maps the OpenAPI endpoint at /openapi.json +app.MapOpenApi(); + +// Maps all endpoints defined in the application, including HelloEndpoint, +// at their respective routes (e.g., /hello for HelloEndpoint) +app.MapAxisEndpoints(); + +// Maps the Scalar API reference endpoint at /scalar +app.MapScalarApiReference(); + +app.Run(); diff --git a/tests/AxisEndpoints.Tutorial/Properties/launchSettings.json b/tests/AxisEndpoints.Tutorial/Properties/launchSettings.json new file mode 100644 index 0000000..e24962e --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5173", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7250;http://localhost:5173", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/tests/AxisEndpoints.Tutorial/README.md b/tests/AxisEndpoints.Tutorial/README.md new file mode 100644 index 0000000..75a8caa --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/README.md @@ -0,0 +1,105 @@ +# AxisEndpoints Quick Tutorial + +This project is a minimal tutorial for trying out AxisEndpoints with Scalar API reference. + +## Requirements + +You need .NET SDK 10.0 or later. + +## Start From Scratch + +```sh +mkdir AxisEndpoints.Tutorial +cd AxisEndpoints.Tutorial +dotnet new webapi +dotnet add package AxisEndpoints +dotnet add package Scalar.AspNetCore +``` + +## Program.cs + +```csharp +using AxisEndpoints.Extensions; +using Scalar.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddOpenApi(); +builder.Services.AddAxisEndpoints(); + +var app = builder.Build(); + +app.MapOpenApi(); +app.MapAxisEndpoints(); +app.MapScalarApiReference(); + +app.Run(); +``` + +## HelloEndpoint.cs + +Create `Features/Hello/HelloEndpoint.cs` with the following content: + +```csharp +using AxisEndpoints; +using System.Net; + +namespace AxisEndpoints.Tutorial.Features.Hello; + +public record HelloRequest +{ + public required string Name { get; set; } = string.Empty; +} + +public record HelloResponse +{ + public required string Message { get; set; } = string.Empty; +} + +public class HelloEndpoint(ILogger logger) : IEndpoint +{ + public void Configure(IEndpointConfiguration config) + { + config + .Get("/hello") + .ProducesSuccess() + .ProducesError(HttpStatusCode.BadRequest) + .Summary("Hello") + .Description("This endpoint takes a name as input and returns a greeting message."); + } + + public Task HandleAsync(HelloRequest request, CancellationToken cancel) + { + if (string.IsNullOrWhiteSpace(request.Name)) + { + logger.LogWarning("Rejected request to /hello because the name was missing."); + return Task.FromResult( + Results.Problem( + title: "Name is required", + detail: "Provide a non-empty name query parameter.", + statusCode: StatusCodes.Status400BadRequest + ) + ); + } + + logger.LogInformation("Received request to /hello with name: {Name}", request.Name); + + return Task.FromResult( + Results.Json(new HelloResponse + { + Message = $"Hello, {request.Name}!", + }) + ); + } +} +``` + +## Run It + +```sh +dotnet run +``` + +Open the Scalar API reference at `http://localhost:{port}/scalar`, then try `GET /hello?name=Alice`. + +![Scalar API Reference](./assets/scalar.png) diff --git a/tests/AxisEndpoints.Tutorial/appsettings.Development.json b/tests/AxisEndpoints.Tutorial/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/tests/AxisEndpoints.Tutorial/appsettings.json b/tests/AxisEndpoints.Tutorial/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/tests/AxisEndpoints.Tutorial/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/tests/AxisEndpoints.Tutorial/assets/scalar.png b/tests/AxisEndpoints.Tutorial/assets/scalar.png new file mode 100644 index 0000000..98298b5 Binary files /dev/null and b/tests/AxisEndpoints.Tutorial/assets/scalar.png differ