如何在 .NET Core 中创建具有 api 个端点的中间件

How create a middleware with api endpoints in .NET Core

我已经用 web api 创建了 web 应用程序。该应用程序包含一些 Controllers 例如 TodoController:

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    public class TodoController : Controller
    {
        private readonly TodoContext _context;

        public TodoController(TodoContext context)
        {
            _context = context;
        }       

        [HttpGet]
        public IEnumerable<TodoItem> GetAll()
        {
            return _context.TodoItems.ToList();
        }
    }
}

如果我创建 GET 请求 - /api/todo - 我从数据库中获取待办事项列表。

我有一个控制器列表和 api 端点,如上所示。

我想将此 api 分发到另一个应用程序,最好像中间件一样 - 我的想法是在 Startup.cs 中注册,如下所示:

public void ConfigureServices(IServiceCollection services)
{
  services.AddTodoApi();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  app.UseTodoApi();
}

这对我的 api 来说是很棒的用例,但我不知道这个控制器 api 端点如何像中间件和 return 相同的 JSON 数据一样重写方法就像使用经典 Controllers.

如何在 .NET Core 中写入 middleware 以创建 API endpoints

您可以将 MVC 中间件配置为从另一个程序集中发现控制器,而不是单独的中间件:

// using System.Reflection;

public void ConfigureServices(IServiceCollection services)
{
    ...
    services
      .AddMvc()
      .AddApplicationPart(typeof(TodoController).GetTypeInfo().Assembly);

控制器是 MVC 中间件的一部分,它们不是请求管道的独立部分(但这就是中间件)。当您注册自定义中间件时,默认情况下它会在每个请求上调用并且您将 HttpContext context 作为输入参数来工作 with/edit Request/Response 数据。但是 ASP.NET 核心 provides Map* extensions 被用作分支管道的约定。

Map branches the request pipeline based on matches of the given request path. If the request path starts with the given path, the branch is executed.

示例:

private static void HandleMapTodo(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("/api/todo was handled");
    });
}

public void Configure(IApplicationBuilder app)
{
    app.Map("/api/todo", HandleMapTodo);
}

请注意,由于中间件对 MVC 中间件一无所知,您只能访问 "raw" 请求,而没有模型绑定或 MVC 操作过滤器等​​功能。

因为它看起来像是完美的微服务方法(类似于我的团队现在正在做的)我会创建一个可以使用您的 API 的客户端程序集,其中包含您的 TodoController,如果您为此 API 定义契约和接口,您可以在其他程序集中注册它,因为它是一个中间件,您也可以在单元测试中模拟该行为。

所以,正如我所说,您可以在 ConfigureServices 方法中注入您的客户端,您可以创建:

public static IServiceCollection AddTodoRestClient(this IServiceCollection services)
{
    services.AddSingleton<ITodoRestClient, TodoRestClient>();
    return services;
}

还要考虑到您需要提供 enpoint,它可能看起来像:

public static IServiceCollection AddConfiguredTodoClient(this IServiceCollection services, string todoEndpoint)
{
    AddTodoClient(services);
    ITodoRestClient todoRestClient = services.BuildServiceProvider().GetService<ITodoRestClient>();
    // Imagine you have a configure method...
    todoRestClient.Configure(services, todoEndpoint);
    return services;
}

您可以在 TodoRestClientInjector class 中创建这些方法,并在启动时在 Configure 方法中使用它们。

希望对你有帮助

--- 回复评论的更多细节---

对我来说,TodoClient 是一个 Rest 客户端库,它实现了对 ToDo API 的调用,(我已经将之前的代码编辑为 TodoRestClient)方法,例如 CreateTodoItem(TodoDto todoItem) 实现将调用到 TodoController.Post([FromBody] item) 或 GetTodos() which wuold call TodoController.Get() 等等....

关于 enpoints... 这种方法意味着有(至少)两个不同的应用程序(.NET Core 应用程序),一方面是 ASP 具有 TodoController 的 NET Core 应用程序,另一方面另一方面,控制台应用程序或另一个 ASP NET Core API 在启动时 class 你将执行 inyection 和 Rest 客户端(Todo Rest 客户端)配置 ...

在使用 docker 的微服务方法中,在开发环境中,您将使用 docker-compose-yml,但在传统方法中,您将使用具体端口来定义端点...

所以,假设您在第二个服务中有一个需要使用 TodoController 的控制器来实现,所以我将使用上面的方法,"SecondController" 看起来像:

  public class SecondController : Controller
    {
        private readonly SecondContext _context;
        private readonly TodoRestClient _todoRestClient;

        public TodoController(SecondContext context, ITodoRestClient todoRestClient)
        {
            _context = context;
            _todoRestClient= todoRestClient;
        }       

// Whatever logic in this second controller... but the usage would be like:

_todoRestClient.GetTodos()

}

最后几个提示:减少服务之间的调用是关键,因为它会增加延迟,如果发生在级联上,延迟会越来越多。还要考虑 Docker 用法,看起来很有挑战性,但它很容易上手,而且实际上被认为可用于您介绍的场景和像我这样的解决方案。

再说一遍,希望对你有帮助。

胡安