Project Icon

ApiEndpoints

ASP.NET Core API开发的简化方案

ApiEndpoints是一个用于ASP.NET Core的框架,采用REPR模式简化API开发。它将API端点的逻辑和模型集中,取代传统MVC控制器,提高代码内聚性和可维护性。该框架支持同步异步处理,与Swagger集成,为开发者提供简洁高效的API构建方法,同时保留ASP.NET Core的全部功能。

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.

Sponsors

A HUGE Thank-You to AWS for sponsoring this project in June 2023 with an annual sponsorship!

Give a Star! :star:

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

Discover a Practical Example

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.

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

Set Up an Empty Web API Project

Overview

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.

Setting Up the Project

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

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

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

Adding Controllers with Ardalis.ApiEndpoints

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

  3. 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();
         }
     }
    
  4. Add Routing Attributes

    Decorate your Handle method with [HttpGet], [HttpPost], or other appropriate HTTP method attributes, specifying the route for your endpoint.

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

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

Example Endpoint Class

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.

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

File Upload Example

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()
    };
  }
}

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}")]
   
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

白日梦AI

白日梦AI提供专注于AI视频生成的多样化功能,包括文生视频、动态画面和形象生成等,帮助用户快速上手,创造专业级内容。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

讯飞绘镜

讯飞绘镜是一个支持从创意到完整视频创作的智能平台,用户可以快速生成视频素材并创作独特的音乐视频和故事。平台提供多样化的主题和精选作品,帮助用户探索创意灵感。

Project Cover

讯飞文书

讯飞文书依托讯飞星火大模型,为文书写作者提供从素材筹备到稿件撰写及审稿的全程支持。通过录音智记和以稿写稿等功能,满足事务性工作的高频需求,帮助撰稿人节省精力,提高效率,优化工作与生活。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

AIWritePaper论文写作

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

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号