• Stars
    star
    2,934
  • Rank 15,449 (Top 0.4 %)
  • Language
    C#
  • License
    MIT License
  • Created almost 5 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A project for supporting API Endpoints in ASP.NET Core web applications.

dotnet core - build Nuget Nuget

Follow @ardalis   Follow @nimblepros

ASP.NET Core API Endpoints

A project for supporting API Endpoints in ASP.NET Core web applications.

Give a Star!

If you like or are using this project to learn or start your solution, please give it a star. Thanks!

Upgrade to 4.x Notes

The fluent generics and base types involved in ApiEndpoints were updated in version 4.x, resulting in breaking changes. The updates required should be pretty straightforward, and have a few additional features that weren't supported in previous versions.

The two main changes introduced in v4 are:

  • Base classes should now use EndpointBaseSync or EndpointBaseAsync
  • WithResponse has been modified to WithResult or WithActionResult

The result of an endpoint corresponds to the return type from the Handle method. Since ASP.NET Core MVC refers to these as some variation of ActionResult, that's the term we are using in this package now as well. The Response your endpoint may return refers to any data/DTO that is being sent to the client as part of the Result. If you wish to preserve your existing v3 functionality that specified WithResponse<T> you should be able to replace all such occurrences with WithActionResult<T>. However, if you need to specify a different kind of Result, such as a FileResult, you can now use something like WithResult<FileResult> to achieve this.

An endpoint that previously inherited from the synchronous BaseEndpoint should now inherit from EndpointBaseSync. Additionally, the WithResponse option now has optional non-generic versions, but if you were intending to return an ActionResult<T> you would now use WithActionResult<T> in your class definition, like so:

- public class ForecastEndpoint : BaseEndpoint
-     .WithRequest<ForecastRequestDto>
-     .WithResponse<IEnumerable<WeatherForecast>>
+ public class ForecastEndpoint : EndpointBaseSync
+     .WithRequest<ForecastRequestDto>
+     .WithActionResult<IEnumerable<WeatherForecast>>

The above change typically would not require any change to the Handle method. Endpoints that inherited from BaseAsyncEndpoint would now use EndpointBaseAsync. You can also just inherit from EndpointBase directly (without the .With* additions) which will provide you with a controller with a single Handle method without restrictions on parameter amount and type, if you need more flexibility than the fluent generic interface provides.

Upgrade to 3.x Notes

For version 3.0 we implemented a new way to define the base classes using "fluent generics". You can watch a video of what you need to know to apply them to your site here.

Table of Contents

1. Motivation

2. Introducing ASP.NET Core API Endpoints

3. Getting Started

4. Animated Screenshots

5. Open Questions

6. Roadmap

7. Related Articles

8. Videos and Podcasts

9. Related / Similar Projects

10. Projects Using ApiEndpoints

11. Success Stories and Testimonials

1. Motivation

MVC Controllers are essentially an antipattern. They're dinosaurs. They are collections of methods that never call one another and rarely operate on the same state. They're not cohesive. They tend to become bloated and to grow out of control. Their private methods, if any, are usually only called by a single public method. Most developers recognize that controllers should be as small as possible (unscientific poll), but they're the only solution offered out of the box, so that's the tool 99% of ASP.NET Core developers use.

You can use tools like MediatR to mitigate the problem. You can read a detailed article about how to migrate from Controllers to Endpoints using MediatR. The short version is that MediatR enables you to have single-line action methods that route commands to handlers. This is objectively a better approach, resulting in more cohesive classes that better follow OO principles. But what if you didn't even need that extra plumbing?

That's what ASP.NET Core API Endpoints are all about.

Side note: Razor Pages

The .NET team already did this exact thing with razor pages. They recognized that dealing with Views, ViewModels, Controllers, and Actions was way more complicated than necessary. It required a developer to jump around between at least 3 (and often more) different folders in order to add or modify a new page/view to their project. Razor pages addressed this by rethinking the model for page-based ASP.NET Core MVC endpoints.

Razor Pages group each page's razor markup, its related action(s), and its model into two linked files. It uses the same MVC features as the rest of the platform, so you still get routing, model binding, model validation, filters, the works. You literally give up nothing. But now when you need to add or modify a page you need to look at exactly 2 files, which are linked in the IDE so you don't need to scroll around the file system looking for them.

2. 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.

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.

3. 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 EndpointBaseSync<TRequest,TResponse> (for endpoints that accept a model as input) or EndpointBaseSync<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 (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
dotnet add package Swashbuckle.AspNetCore.Annotations
  1. Add EnableAnnotations to the Swagger configuration in Startup.cs
services.AddSwaggerGen(c => {
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
    c.EnableAnnotations();
});
  1. Add the following attribute to endpoint methods
[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);
}

Option to use service dependency injection instead of constructor

// File: sample/SampleEndpointApp/Endpoints/Authors/List.cs
public class List : BaseAsyncEndpoint
    .WithRequest<AuthorListRequest>
    .WithResponse<IList<AuthorListResult>>
{
    private readonly IAsyncRepository<Author> repository;
    private readonly IMapper mapper;

    public List(
        IAsyncRepository<Author> repository,
        IMapper mapper)
    {
        this.repository = repository;
        this.mapper = mapper;
    }
    [HttpGet("/authors")]
    [SwaggerOperation(
        Summary = "List all Authors",
        Description = "List all Authors",
        OperationId = "Author_List",
        Tags = new[] { "AuthorEndpoint" })
    ]
    public override async Task<ActionResult<IList<AuthorListResult>>> HandleAsync(

        [FromQuery] AuthorListRequest request,
        CancellationToken cancellationToken = default)
    {
        if (request.PerPage == 0)
        {
            request.PerPage = 10;
        }
        if (request.Page == 0)
        {
            request.Page = 1;
        }
        var result = (await repository.ListAllAsync(request.PerPage, request.Page, cancellationToken))
            .Select(i => mapper.Map<AuthorListResult>(i));

        return Ok(result);
    }
}

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

4. Animated Screenshots

Working with Endpoints, Requests, and Results in Visual Studio

api-endpoints-2

5. Open Questions

Below are what I expect will be some common questions:

How do I use shared routing conventions?

If you want to create a common route template for all or some subset of your Endpoints, simply create a EndpointBaseSync of your own that inherits from Ardalis.Api.Endpoints.EndpointBaseSync and add a [Route] attribute to it.

After refactoring to use the fluent generics pattern, there is no longer a way to use a base class for a default route. Instead, you should define your routes as constants which you can store in a central file or in each Request DTO (the sample shows this approach).

Can I add more than one public routable method to an Endpoint class?

Technically, yes. But don't do that. If you really want that, you should just use a Controller.

How can I bind parameters from multiple locations to my model?

To do this, you'll need to decorate the properties of your model with the proper route attributes:

public class NewArticleRequest
{
    [FromRoute(Name = "username")] public string Username { get; set; }
    [FromRoute(Name ="category")] public string Category { get; set; }

    [FromBody] public Article Article { get; set; }
}

Then, it's very important to include [FromRoute] in the method declaration in your endpoint using that model:

public override Task<ActionResult> HandleAsync([FromRoute] NewArticleRequest request)

Note the [Route("/article")] and [HttpPost("{username}/{category}")] lines below. These lines form the route string used in the NewArticleRequest class above.

[Route("/article")]
public class Post : BaseAsyncEndpoint
    .WithRequest<NewArticleRequest>
    .WithoutResponse
{
    [HttpPost("{username}/{category}")]
    [SwaggerOperation(
        Summary = "Submit a new article",
        Description = "Enables the submission of new articles",
        OperationId = "Article_Create",
        Tags = new[] {"Article"})
    ]
    public override Task<ActionResult> HandleAsync([FromRoute] NewArticleRequest request,
        CancellationToken cancellationToken = new CancellationToken())
    {
        //// your implementation
    }
}

For more information, take a look at this discussion and this issue. Thank you to @garywoodfine and @matt-lethargic.

How can I return a File result from an ApiEndpoint?

There's an example in the sample app that shows how to set this up and return a File actionresult. For the base type, just use the WithoutResponse option and in the endpoint handler return File().

How can I use model binding to pull values from multiple places, like [FromRoute], [FromBody], etc.?

#172 The base endpoints only expose a single model type which is used on the Handle method, so you can't easily add additional parameters to the Handle method. However, you can put as many properties on the associated Request DTO as you want, and model binding allows you to set the same attributes per property as you would have set per parameter on the action method. See Model Binding Docs and discussion here in issue 42

How can I use streaming from server to client?

There's an example in the sample app that shows how to set this up and return an IAsyncEnumerable<T>. For the base type, just use the WithAsyncEnumerableResult<T> option and in the endpoint handler yeld return after awaiting your async code.

Note: streaming with IAsyncEnumerable does not work within Swagger Ui. Use curl to test this functionality

curl -X "GET" "https://localhost:44338/api/Authors/stream" -H "accept: text/plain" 

6. Roadmap

The following are some things I'd like to add to the project/package.

Item Template

Visual Studio and/or CLI item templates would make it much easier to create Endpoints and their associated models, with the correct naming so they're linked in the IDE.

Route Conventions

One thing that Controllers do have is built-in support in the framework to use their name in routes (e.g. "[controller]/{id?}"). Currently in the sample app routes are hard-coded strings. It would be nice if there were an easy way to use a convention based on foldername or namespace or something (using foldername would align with how Razor Pages routing works).

7. Related Articles

8. Videos and Podcasts

9. Related / Similar Projects

10. 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: A solution template for ASP.NET 3.x solutions using Clean Architecture.
  • PayrollProcessor: A smorgasbord of modern .NET tech written with functional and asynchronous patterns.
  • eShopOnWeb: Sample ASP.NET Core reference application, powered by Microsoft

11. Success Stories and Testimonials

"I have implemented in my team your API endpoint solution and I must tell you that was a pretty good investment! in particular how maintainable and testable the solution became!"

Nuno Santos

More Repositories

1

CleanArchitecture

Clean Architecture Solution Template: A starting point for Clean Architecture with ASP.NET Core
C#
16,284
star
2

GuardClauses

A simple package with guard clause extensions.
C#
3,063
star
3

SmartEnum

A base class for quickly and easily creating strongly typed enum replacements in C#.
C#
2,178
star
4

Specification

Base class with tests for adding specifications to a DDD model
C#
1,922
star
5

pluralsight-ddd-fundamentals

Sample code for the Pluralsight DDD Fundamentals course by Julie Lerman and Steve "ardalis" Smith
CSS
841
star
6

Result

A result abstraction that can be mapped to HTTP response codes if needed.
C#
819
star
7

CleanArchitecture.WorkerService

A solution template using Clean Architecture for building a .NET Core Worker Service.
C#
720
star
8

ddd-guestbook

A DDD guestbook example written for ASP.NET Core
C#
691
star
9

kata-catalog

My list of code katas
C#
670
star
10

DesignPatternsInCSharp

Samples associated with Pluralsight design patterns in c# courses.
C#
518
star
11

DDD-NoDuplicates

Some design approaches to enforcing a business rule requiring no duplicates. Domain driven design.
C#
512
star
12

SolidSample

C#
474
star
13

new-software-project-checklist

The ultimate new software project decision/question checklist
443
star
14

ddd-vet-sample

A sample meant to demonstrate domain driven design using a veterinary hospital management system.
C#
305
star
15

OrganizingAspNetCore

Offers several different ways to organize content in ASP.NET Core MVC and Razor Pages projects.
C#
207
star
16

Ardalis.Extensions

Some random C# extension methods I've found useful. Published as Ardalis.Extensions on Nuget.
C#
147
star
17

WebApiBestPractices

Resources related to my Pluralsight course on this topic.
C#
144
star
18

CachedRepository

A sample demonstrating the CachedRepository pattern
C#
104
star
19

HttpClientTestExtensions

Extensions for testing HTTP endpoints and deserializing the results. Currently works with XUnit.
C#
92
star
20

CertExpirationCheck

A simple xUnit C# test showing how to verify a certificate for a domain has at least 30 days before it expires.
C#
79
star
21

DotNetDataAccessTour

A tour of different data access approaches in .NET 8+.
C#
73
star
22

AspNetCoreStartupServices

A simple demo listing all services available to an app at startup
C#
69
star
23

GettingStartedWithFilters

Filters samples associated with MSDN article
C#
67
star
24

AspNetCoreRouteDebugger

An ASP.NET Core Route Debugger implemented as a Razor Page
C#
67
star
25

Ardalis.SharedKernel

Some useful base classes, mainly used with the CleanArchitecture template. Also, a template to make your own SharedKernel nuget package.
C#
56
star
26

MediatRAspNetCore

Sample showing MediatR with ASP.NET Core
C#
50
star
27

DevIQ-gatsby

JavaScript
48
star
28

DomainEventsConsole

A console app showing domain events in action using .NET 5
C#
41
star
29

BuilderTestSample

Show how to use a builder with unit tests.
C#
40
star
30

EFCore.Extensions

Extension methods to make working with EF Core easier.
C#
38
star
31

CSharpGenerics

Some samples using C# generics for a Pluralsight course.
C#
38
star
32

Ardalis.ApiClient

Some classes to make working with APIs easier.
C#
33
star
33

TestPatterns

Examples of approaches to unit testing different kinds of code in C#.
C#
33
star
34

StatePattern

An example of the State design pattern in C#
C#
30
star
35

MinimalWebApi

A minimal Web API for ASP.NET Core
C#
29
star
36

BlazorAuth

A sample showing how to add ASP.NET Core Identity auth options to the basic Blazor app.
C#
27
star
37

EditorConfig

A sample editorconfig file for use with .NET / C# applications
27
star
38

DevResources

A list of links to resources.
26
star
39

NotFoundMiddlewareSample

Middleware for detecting and correcting website 404 errors.
C#
25
star
40

DomainModeling

Some examples showing domain modeling tips and traps
C#
21
star
41

AggregateEvents

A sample showing how to use domain events within aggregates
C#
19
star
42

Specification.EFCore

Some utilities for EF Core to use Specifications
C#
16
star
43

LazyLoading

C#
16
star
44

TestSecureApiSample

A sample showing how to test a secure API endpoint using xunit, identityserver4, and environment variables
JavaScript
15
star
45

EulerCSharpStarter

A starting point for solving Project Euler problems using C#.
C#
14
star
46

EnumAlternative

A sample demonstrating strongly typed C# alternatives to enums.
C#
13
star
47

RouteAndBodyModelBinding

A model binder for ASP.NET Core that supports pulling in values from the route and body of a request.
C#
13
star
48

DoubleDispatchSamples

Some examples of double dispatch in C# and how to use in domain models.
C#
13
star
49

ardalis-com-gatsby

Back end content for ardalis.com running with Netlify and Gatsby.
JavaScript
12
star
50

GildedRoseStarter

A starting point for the Gilded Rose kata using dotnet core, C#, and xunit.
C#
11
star
51

ValueObjectsDemo

C#
10
star
52

MongoDbDotNetHelloWorld

Demonstrating how to get started with MongoDB as quickly as possible in dotnet
C#
10
star
53

TestingLogging

A sample showing how to test logging is working as expected in ASP.NET Core apps.
C#
10
star
54

GuardClauses.Analyzers

A project for holding Roslyn analyzers that leverage Ardalis.GuardClauses
C#
10
star
55

ValidateModel

A very small library/package that includes a model validation attribute for ASP.NET Core projects.
C#
9
star
56

RegExLib.com

Source code for the regexlib.com regular expression library site.
C#
9
star
57

SoftwareQualityWorkshop2020

Public resources supporting private software quality workshops I'm delivering in 2020.
C#
9
star
58

RepoMultiImplementation

C#
8
star
59

MediatREndpointsSample

A sample using MediatR to map endpoints using minimal apis
C#
8
star
60

DomainEventsDemo

A sample project showing how to wire up Domain Events
C#
8
star
61

MessagingDemo

C#
8
star
62

RefactoringKataAttempts2024

C#
8
star
63

AspNetCoreNewIn21

Samples showing what's new in ASP.NET Core 2.1
JavaScript
7
star
64

CSharpPropertiesExamples

Some examples of when and how C# properties work in different application scenarios.
C#
6
star
65

EulerNodeStarter

A starting point for solving Project Euler problems using node.js
JavaScript
6
star
66

RefactoringSamples

C#
6
star
67

UnitTestPerf

Some projects used to demonstrate the performance of different unit testing frameworks for .NET Core
C#
6
star
68

AccessTokenSample

A small sample showing how to create, manage, use access tokens.
C#
6
star
69

IntegrationEventSample

Sample showing Domain Events and Integration Events with Web API 2
C#
6
star
70

azure-cloud-native-book

5
star
71

ExtensionMethodSample

Some simple extension method samples
C#
5
star
72

Cachify

A .NET library to optimize data and reflection-based operations using caching.
PowerShell
5
star
73

Refactoring-To-SOLID-Pluralsight

Resources for my Pluralsight course on Refactoring to SOLID C# Code
C#
5
star
74

CodinGameFall2022

C#
5
star
75

EFCoreOwnedEntityTests

A unit test project that tests owned entities in EF Core
C#
5
star
76

EFCoreStringInterpolationDemo

Showing new EF Core 2.0 feature for raw SQL queries
C#
5
star
77

Diagrams

Text and images from UML and similar diagrams
5
star
78

RedisDotNetHelloWorld

Getting started with Redis in dotnet
C#
5
star
79

WhenAllTest

A simple app showing perf gains of using WhenAll for parallel task execution.
C#
5
star
80

AdventOfCode2022

Working on this: https://adventofcode.com for 2022
C#
4
star
81

ardalis

4
star
82

StructureMapLoggingSample

C#
4
star
83

ConsoleLoggingSample

A sample using NLog to create custom loggers that include app-specific data in the output.
C#
4
star
84

ValidationRules

A collection of validation rules for VS Web Tests
C#
3
star
85

CraftsmanshipCalendarIdeas

Ideas for next year's software craftsmanship calendar
3
star
86

CodinGameSpring2021

C#
3
star
87

Logging

Some logging utilities for .NET Core
C#
3
star
88

MigrateDotNetWithIIS

JavaScript
3
star
89

CastleWindsorSample

A console app showing the basic setup of Castle Windsor for IOC
C#
3
star
90

GeekDinnerSample

An incomplete sample showing clean code and DI in ASP.NET Core
C#
3
star
91

RegExLib

Regexlib.com source
C#
3
star
92

MontyHallMvc

An ASP.NET MVC application that lets users try out the Monty Hall problem and shows their stats.
C#
3
star
93

yarp-passthrough

The simplest YARP ASP.NET Core app that just passes everything through to another domain.
C#
3
star
94

AggregatePatterns

C#
2
star
95

EulerNode

A node.js implementation of Project Euler problems.
JavaScript
2
star
96

TennisGameKatas

C#
2
star
97

AspNetCoreLabs

2
star
98

EFCoreFeatureTests

Tests demonstrating EF Core features across versions.
C#
2
star
99

TestRepo

Just a repo for testing some things
2
star
100

FizzBuzzVS2015

A sample FizzBuzz kata implementation using VS2015
Smalltalk
2
star