Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/ardalis/apiendpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
ardalis committed Jun 5, 2020
2 parents 3025d60 + 05c92f0 commit c8fbe8e
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 10 deletions.
58 changes: 53 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,63 @@ Razor Pages group each page's razor markup, its related action(s), and its model

## Introducing ASP.NET Core API Endpoints

ASP.NET Core API Endpoints are essentially Razor Pages for APIs. They break apart bloated controllers and group the API models used by individual endpoints with the endpoint logic itself. They provide a simple way to have a single file for the logic and linked files for the model types.
**ASP.NET Core API Endpoints are essentially Razor Pages for APIs.** They break apart bloated controllers and group the API models used by individual endpoints with the endpoint logic itself. They provide a simple way to have a single file for the logic and linked files for the model types.

When working with ASP.NET Core API Endpoints your project won't need any Controller classes. You can organize the Endpoints however you want. By feature. In a giant Endpoints folder. It doesn't matter - they'll work regardless of where you put them.

Most REST APIs have groups of endpoints for a given resource. In Controller-based projects you would have a controller per resource. When using API Endpoints you can simply create a folder per resource, just as you would use folders to group related pages in Razor Pages.

Instead of Model-View-Controller (MVC) the pattern becomes Request-EndPoint-Response(REPR). The REPR (reaper) pattern is much simpler and groups everything that has to do with a particular API endpoint together. It follows SOLID principles, in particular SRP and OCP. It also has all the benefits of feature folders and better follows the Common Closure Principle by grouping together things that change together.
**Instead of Model-View-Controller (MVC) the pattern becomes Request-EndPoint-Response(REPR). The REPR (reaper) pattern is much simpler and groups everything that has to do with a particular API endpoint together.** It follows SOLID principles, in particular SRP and OCP. It also has all the benefits of feature folders and better follows the Common Closure Principle by grouping together things that change together.

## Getting Started

I'll look to add detailed documentation in the future but for now here's all you need to get started (you can also check the sample project):

1. Add the Ardalis.ApiEndpoints NuGet package to your ASP.NET Core web project.
2. Create Endpoint classes by inheriting from either `BaseEndpoint<TRequest,TResponse>` (for endpoints that accept a model as input) or `BaseEndpoint<TResponse>` (for endpoints that simply return a response). For example, a POST endpoint that creates a resource and then returns the newly created record would use version that includes both a Request and a Response. A GET endpoint that just returns a list of records and doesn't accept any arguments would use the second version.
1. Add the [Ardalis.ApiEndpoints NuGet package](https://www.nuget.org/packages/Ardalis.ApiEndpoints/) to your ASP.NET Core web project.
2. Create Endpoint classes by inheriting from either `BaseEndpoint<TRequest,TResponse>` (for endpoints that accept a model as input) or `BaseEndpoint<TResponse>` (for endpoints that simply return a response). For example, a POST endpoint that creates a resource and then returns the newly created record would use the version that includes both a Request and a Response. A GET endpoint that just returns a list of records and doesn't accept any arguments would use the second version.
3. Implement the base class's abstract `Handle()` method.
4. Make sure to add a `[HttpGet]` or similar attribute to your `Handle()` method, specifying its route.
5. Define your `TResponse` type in a file in the same folder as its corresponding endpoint.
5. Define your `TResponse` type in a file in the same folder as its corresponding endpoint (or in the same file if you prefer).
6. Define your `TRequest` type (if any) just like the `TResponse` class.
7. Test your ASP.NET Core API Endpoint. If you're using Swagger/OpenAPI it should just work with it automatically.

### Adding common endpoint groupings using Swagger

In a standard Web API controller, methods in the same class are grouped together in the Swagger UI. To add this same functionality for endpoints:

1. Install the Swashbuckle.AspNetCore.Annotations
``` bash
dotnet add package Swashbuckle.AspNetCore.Annotations
```
2. Add EnableAnnotations to the Swagger configuration in Startup.cs
``` csharp
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.EnableAnnotations();
});
```
3. Add the following attribtue to endpoint methods
``` csharp
[HttpPost("/authors")]
[SwaggerOperation(
Summary = "Creates a new Author",
Description = "Creates a new Author",
OperationId = "Author.Create",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<CreateAuthorResult>> HandleAsync([FromBody]CreateAuthorCommand request)
{
var author = new Author();
_mapper.Map(request, author);
await _repository.AddAsync(author);

var result = _mapper.Map<CreateAuthorResult>(author);
return Ok(result);
}
```

Examples of the configuration can be found in the sample API project

## Animated Screenshots

### Working with Endpoints, Requests, and Results in Visual Studio
Expand Down Expand Up @@ -84,3 +121,14 @@ One thing that Controllers do have is built-in support in the framework to use t

- [Moving from Controllers and Actions to Endpoints](https://ardalis.com/moving-from-controllers-and-actions-to-endpoints-with-mediatr)

## Related / Similar Projects

- [SimpleEndpoints](https://github.com/dasiths/SimpleEndpoints)
- [FunctionMonkey](https://github.com/JamesRandall/FunctionMonkey) A similar approach for Azure Functions.
- [https://github.com/Kahbazi/MediatR.AspNetCore.Endpoints](https://github.com/Kahbazi/MediatR.AspNetCore.Endpoints) A similar approach using MediatR and middleware.

## Projects Using ApiEndpoints

If you're using them or find one not in this list, feel free to add it here via a pull request!

- [CleanArchitecture](https://github.com/ardalis/CleanArchitecture): A solution template for ASP.NET 3.x solutions using Clean Architecture.
7 changes: 7 additions & 0 deletions sample/SampleEndpointApp/AuthorEndpoints/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using SampleEndpointApp.DomainModel;
using Swashbuckle.AspNetCore.Annotations;
using System.Threading.Tasks;

namespace SampleEndpointApp.Authors
Expand All @@ -19,6 +20,12 @@ public Create(IAsyncRepository<Author> repository,
}

[HttpPost("/authors")]
[SwaggerOperation(
Summary = "Creates a new Author",
Description = "Creates a new Author",
OperationId = "Author.Create",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<CreateAuthorResult>> HandleAsync([FromBody]CreateAuthorCommand request)
{
var author = new Author();
Expand Down
8 changes: 8 additions & 0 deletions sample/SampleEndpointApp/AuthorEndpoints/Delete.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using SampleEndpointApp.DomainModel;
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;

namespace SampleEndpointApp.Authors
{
public class Delete : BaseAsyncEndpoint<int, DeletedAuthorResult>
Expand All @@ -15,6 +17,12 @@ public Delete(IAsyncRepository<Author> repository)
}

[HttpDelete("/authors/{id}")]
[SwaggerOperation(
Summary = "Deletes an Author",
Description = "Deletes an Author",
OperationId = "Author.Delete",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<DeletedAuthorResult>> HandleAsync(int id)
{
var author = await _repository.GetByIdAsync(id);
Expand Down
9 changes: 9 additions & 0 deletions sample/SampleEndpointApp/AuthorEndpoints/Get.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using SampleEndpointApp.DomainModel;
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;

namespace SampleEndpointApp.Authors
{
public class Get : BaseAsyncEndpoint<int, AuthorResult>
Expand All @@ -19,6 +21,13 @@ public Get(IAsyncRepository<Author> repository,
}

[HttpGet("/authors/{id}")]

[SwaggerOperation(
Summary = "Get a specific Author",
Description = "Get a specific Author",
OperationId = "Author.Get",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<AuthorResult>> HandleAsync(int id)
{
var author = await _repository.GetByIdAsync(id);
Expand Down
8 changes: 8 additions & 0 deletions sample/SampleEndpointApp/AuthorEndpoints/List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Linq;
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;

namespace SampleEndpointApp.Authors
{
public class List : BaseAsyncEndpoint
Expand All @@ -21,6 +23,12 @@ public List(IAsyncRepository<Author> repository,
}

[HttpGet("/authors")]
[SwaggerOperation(
Summary = "List all Authors",
Description = "List all Authors",
OperationId = "Author.List",
Tags = new[] { "AuthorEndpoint" })
]
public async Task<ActionResult> HandleAsync([FromQuery] int page = 1, int perPage = 10)
{
var result = (await _repository.ListAllAsync(perPage, page))
Expand Down
9 changes: 9 additions & 0 deletions sample/SampleEndpointApp/AuthorEndpoints/Update.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using SampleEndpointApp.DomainModel;
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;

namespace SampleEndpointApp.Authors
{
public class Update : BaseAsyncEndpoint<UpdateAuthorCommand, UpdatedAuthorResult>
Expand All @@ -19,6 +21,13 @@ public Update(IAsyncRepository<Author> repository,
}

[HttpPut("/authors")]

[SwaggerOperation(
Summary = "Updates an existing Author",
Description = "Updates an existing Author",
OperationId = "Author.Update",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<UpdatedAuthorResult>> HandleAsync([FromBody]UpdateAuthorCommand request)
{
var author = await _repository.GetByIdAsync(request.Id);
Expand Down
4 changes: 3 additions & 1 deletion sample/SampleEndpointApp/SampleEndpointApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
<PackageReference Include="SQLite" Version="3.13.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.0" />
<PackageReference Include="NETStandard.Library" Version="2.0.3" />

<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.4.0" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 4 additions & 2 deletions sample/SampleEndpointApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ public void ConfigureServices(IServiceCollection services)

services.AddControllers();

services.AddSwaggerGen(c =>
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }));
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.EnableAnnotations();
});

services.AddAutoMapper(typeof(Startup));

Expand Down
4 changes: 2 additions & 2 deletions src/Ardalis.ApiEndpoints/Ardalis.ApiEndpoints.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<Summary>Controllers promote creating bloated classes that lack cohesion. This project provides a simpler alternative that follows SOLID principles. An alternative to Controllers for ASP.NET Core API Endpoints.</Summary>
<RepositoryUrl>https://github.com/ardalis/ApiEndpoints</RepositoryUrl>
<PackageTags>aspnet asp.net aspnetcore asp.net core api web api rest endpoint controller</PackageTags>
<PackageReleaseNotes>Added async support.</PackageReleaseNotes>
<Version>0.9.4</Version>
<PackageReleaseNotes>Added querystring parameter support. 1.0.0 release.</PackageReleaseNotes>
<Version>1.0.0</Version>
<AssemblyName>Ardalis.ApiEndpoints</AssemblyName>
<PackageIconUrl>https://user-images.githubusercontent.com/782127/33497760-facf6550-d69c-11e7-94e4-b3856da259a9.png</PackageIconUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
Expand Down

0 comments on commit c8fbe8e

Please sign in to comment.