A project for supporting API Endpoints in ASP.NET Core web applications.
A HUGE Thank-You to AWS for sponsoring this project in June 2023 with an annual sponsorship!
If you like or are using this project to learn or start your solution, please give it a star. Thanks!
If you're eager to dive into a practical example of using Ardalis.ApiEndpoints, check out our Getting Started guide. This guide walks you through setting up your environment and creating your first API endpoint using the latest features in version 4.x.
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:
EndpointBaseSync or EndpointBaseAsyncWithResponse has been modified to WithResult or WithActionResultThe 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.
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.
2. Introducing ASP.NET Core API Endpoints
10. Projects Using ApiEndpoints
11. Success Stories and Testimonials
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.
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.
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.
When starting a new Web API project using .NET, you might want to begin with an empty project structure to have more control over dependencies and configurations.
Create the Project:
Start by creating a new empty web API project. You can do this using the .NET CLI:
dotnet new web -n MyWebApi cd MyWebApi
This creates a basic project structure for a web application.
Update Program.cs:
Open the Program.cs file, which serves as the entry point for your application. By default, it might contain the following code:
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // app.MapGet("/", () => "Hello World!"); app.Run();
Remove the app.MapGet("/", () => "Hello World!"); line as it sets up a minimal endpoint which we'll replace with controller routing.
Configure Services and Routing:
Modify the Program.cs file to include controller services and routing:
var builder = WebApplication.CreateBuilder(args); // Add controllers builder.Services.AddControllers(); var app = builder.Build(); // Map controllers to endpoints app.MapControllers(); app.Run();
builder.Services.AddControllers(): Adds services required for controllers to handle HTTP requests.app.MapControllers(): Maps controllers to appropriate endpoints based on routing attributes and conventions.These two methods are required for the endpoint to work.
Install Ardalis.ApiEndpoints NuGet Package
Add the Ardalis.ApiEndpoints package to your ASP.NET Core web project. You can do this using the NuGet Package Manager or via the .NET CLI:
dotnet add package Ardalis.ApiEndpoints
Create Endpoint Classes
Create your endpoint classes by inheriting from EndpointBaseSync or EndpointBaseAsync, and add .WithRequest<TRequest> or .WithResult<TResponse>, or both, depending on whether your endpoint accepts input (POST) or simply returns a response (GET).
Implement Handle Method
Implement the Handle method from the base class (EndpointBaseSync) in your endpoint class. This method contains the logic to process the request and return the response.
public class MyEndpoint : EndpointBaseSync .WithRequest<string> .WithActionResult<IActionResult> { [HttpGet("my-endpoint")] public override ActionResult<IActionResult> Handle(string request) { // Your logic here return Ok(); } }
Add Routing Attributes
Decorate your Handle method with [HttpGet], [HttpPost], or other appropriate HTTP method attributes, specifying the route for your endpoint.
Define Request and Response Types
Define your TRequest and TResponse types in the same file as your endpoint class or in separate files as per your project structure.
Test Your API Endpoint
Test your ASP.NET Core API endpoint. If you're using Swagger/OpenAPI, it should automatically document your endpoint based on the attributes provided.
Here's an example of a GET endpoint that returns a list of books using Ardalis.ApiEndpoints:
public class ListBooksEndpoint : EndpointBaseSync .WithoutRequest .WithResult<IList<BookDto>> { private readonly IRepository<Book> repository; private readonly IMapper mapper; public ListBooksEndpoint( IRepository<Book> repository, IMapper mapper) { this.repository = repository; this.mapper = mapper; } [HttpGet("/books")] public override IList<BookDto> Handle() { var books = repository.ListAll(); var bookDtos = books.Select(book => mapper.Map<BookDto>(book)).ToList(); return bookDtos; } }
This endpoint demonstrates listing books and uses the HttpGet annotation.
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:
dotnet add package Swashbuckle.AspNetCore.Annotations
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); c.EnableAnnotations(); });
[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
See Issue 170 for more details
using Ardalis.ApiEndpoints; using Microsoft.AspNetCore.Mvc; namespace SampleEndpointApp.Endpoints.Authors; public class File : EndpointBaseAsync .WithRequest<IFormFile> .WithResult<ActionResult<string[]>> { /// <summary> /// Post author's photo or something /// </summary> [HttpPost("api/[namespace]/file")] public override async Task<ActionResult<string[]>> HandleAsync( IFormFile file, CancellationToken cancellationToken = default) { string filePath = Path.GetTempFileName(); using (var fileStream = System.IO.File.Create(filePath)) { await file.CopyToAsync(fileStream, cancellationToken); } return new[] { filePath, file.FileName, file.ContentType, file.ContentDisposition, file.Length.ToString() }; } }

Below are what I expect will be some common questions:
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).
Technically, yes. But don't do that. If you really want that, you should just use a Controller.
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}")]


最适合小白的AI自动化工作流平台
无需编码,轻松生成可复用、可变现的AI自动化工作流

大模型驱动的Excel数据处理工具
基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。


AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。


AI论文写作指导平台
AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。


AI一键生成PPT,就用博思AIPPT!
博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。


AI赋能电商视觉革命,一站式智能商拍平台
潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。


企业专属的AI法律顾问
iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。


稳定高效的流量提升解决方案,助力品牌曝光
稳定高效的流量提升解决方案,助力品牌曝光


最新版Sora2模型免费使用,一键生成无水印视频
最新版Sora2模型免费使用,一键生成无水印视频


实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号