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.
+
+
+
+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`.
+
+
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