当您使用 .NET 5 创建 Web API 时,它会搭建一个名为“ Controllers ”的文件夹。在这个文件夹中是包含动作的类。控制器和动作的组合构成了 API 的端点。动作是一种可以做逻辑工作的方法,或者调用另一个处理逻辑的服务类。它看起来像这样:
解决方案资源管理器显示文件夹“ Controllers ”,其中包含文件WeatherForecastController.cs。该文件包含一个具有相同名称和操作的类。它还包含 API 和路由的配置。
还有一个Startup.cs文件,其中包含项目的配置。例如,依赖注入、实体框架、Swagger 等等。
这就是我们长期以来一直这样做的方式。现在,微软推出了Minimal API。
最小 API 解释
一个最小的 API 基本上删除了Startup.cs并将该代码放在Program.cs中。带有操作的控制器被删除并作为映射放置在Program.cs中。它允许我们将 API 的编码保持在最低限度。
它对每个 API 调用使用 Lambda 表达式。您可以配置路由和请求类型。它看起来像这样:
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/movies"</span>, () => <span style="color:#0000ff">new</span> List<string>() { <span style="color:#800080">"</span><span style="color:#800080">Shrek"</span>, <span style="color:#800080">"</span><span style="color:#800080">The Matrix"</span>, <span style="color:#800080">"</span><span style="color:#800080">Inception"</span>});</span></span>
该应用程序在Startup.cs中定义,即WebApplication
. MapGet
是一种将路由“映射”到 lambda 表达式的方法。在上面的示例中,我想映射路线https://localhost:12345/movies并使其返回电影标题列表。
此外,MapGet
还有MapPost
, MapDelete
, 和MapPut
。这些代表属性HTTPGet
, HTTPPost
, HTTPDelete
, 和HTTPPut
我们用于 .NET 5 API 中的操作。
这是基本的想法。让我们进入一个例子!
创建最小 API
既然我们现在知道了最小 API 背后的基本故事,那么让我们创建一个并看看它是如何工作的。在接下来的章节中,我将向您展示我们开发人员需要完成的最常见的任务。一个简单的映射,一个异步的映射,以及依赖注入的使用。
让我们使用 Visual Studio 2022 创建一个新项目并选择 ASP.NET Core Web API 模板。给它一个好的项目名称。我将调用我的“ MinimalApiExample
”,并且解决方案名称是相同的。
附加信息屏幕需要注意。
Visual Studio 2022 中的每个项目模板都有自己的附加信息。有些选项是相同的,比如框架。您在此处看到的大多数选项都很常见,但如果您仔细观察,您会看到两个新的复选框:
- 使用控制器(取消选中以使用最少的 API)
- 不要使用顶级语句
对于本文,我不关心那些顶级语句。但我确实关心第一个。这是默认选中的,它将为控制器和操作搭建脚手架,就像我们以“旧”方式习惯的那样。如果取消选中,Visual Studio 将不会创建这些控制器,而这正是我们想要的。让我们取消选中它并按创建按钮。
完成加载和创建基本文件后,您可能会注意到项目的结构。我们缺少文件夹“ Controllers ”,这正是我们想要的。
如果您打开Program.cs,您可能还会注意到一些差异。没有app.MapControllers()
,突然之间,有一个app.MapGet(…)
。欢迎使用最小 API!
映射端点
使用最少 API 的想法是您不需要创建带有操作的新控制器。这一切都是用 lambda 表达式完成的。通过映射您的端点,您可以告诉 API 用户/客户端可以输入哪些地址。让我们看一下program.cs中的示例:
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/weatherforecast"</span>, () =>
{
<span style="color:#0000ff">var</span> forecast = Enumerable.Range(<span style="color:#000080">1</span>, <span style="color:#000080">5</span>).Select(index =>
<span style="color:#0000ff">new</span> WeatherForecast
(
DateTime.Now.AddDays(index),
Random.Shared.Next(-20, <span style="color:#000080">55</span>),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
<span style="color:#0000ff">return</span> forecast;
})
.WithName(<span style="color:#800080">"</span><span style="color:#800080">GetWeatherForecast"</span>);</span></span>
此映射用于 (HTTP)GET
请求。端点是 (https://localhost:12345)/weatherforecast。该GET
方法的返回值是 JSON 格式的一系列天气预报。很简单,对吧?
让我们删除所有示例数据,例如映射、变量摘要和内部记录WeatherForecast()
。确保您不会意外删除app.Run()
,因为它在示例数据之间有点偷偷摸摸。
让我们创建一个新对象,名为Movie
. 将此对象放在新文件中或program.cs的底部。然后创建一个包含电影列表的新变量。请注意,变量 movies 需要先声明并填写,然后才能使用它们。我建议将它放在 Swagger 启动下。它看起来像这样:
<span style="color:#000000"><span style="background-color:#fbedbb">List<movie> movies = <span style="color:#0000ff">new</span>()
{
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">1</span>, Rating = <span style="color:#000080">5</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Shrek"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">2</span>, Rating = <span style="color:#000080">1</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Inception"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">3</span>, Rating = <span style="color:#000080">3</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Jaws"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">4</span>, Rating = <span style="color:#000080">1</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">The Green Latern"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">5</span>, Rating = <span style="color:#000080">5</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">The Matrix"</span> },
};
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> Movie
{
<span style="color:#0000ff">public</span> <span style="color:#0000ff">int</span> Id { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; }
<span style="color:#0000ff">public</span> <span style="color:#0000ff">string</span> Title { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; }
<span style="color:#0000ff">public</span> <span style="color:#0000ff">int</span> Rating { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; }
}</span></span>
映射 GET
映射GET
请求并不难。我们刚刚删除了这个例子。让我们创建一个返回所有电影的新映射:
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/"</span>, () =>
{
<span style="color:#0000ff">return</span> Results.Ok(movies);
});</span></span>
这将在调用端点 (https://localhost:1234)/api/movies 时返回完整的电影列表。
MapGet
表示您要创建GET
端点。使用POST
这个端点是行不通的。
如果您想通过 id 或其他参数获取特定电影,只需在使用MapGet
.
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/"</span>, () =>
{
<span style="color:#0000ff">return</span> Results.Ok(movies);
});
app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/{id:int}"</span>, (<span style="color:#0000ff">int</span> id) =>
{
<span style="color:#0000ff">return</span> Results.Ok(movies.<span style="color:#339999">Single</span>(x => x.Id == id));
});</span></span>
通过在端点中添加{id:int},您可以告诉 API 可以预期一个数字。然后,您接下来添加变量声明,您可以在正文中使用它。
您还可以声明id
不带类型的变量,如下所示:
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/{id:int}"</span>, (id) =>
{
<span style="color:#0000ff">return</span> Results.Ok(movies.<span style="color:#339999">Single</span>(x => x.Id == id));
});</span></span>
但这不起作用,因为假设第一个参数是HttpContext
,而不是您尝试使用的参数。使用HttpContext
是管理您自己对任何请求的响应的好方法。但这不是我们想要的。
映射 POST
另一种常用的请求方法是POST
. 它允许客户端将数据发送到 API,在那里可以使用和处理数据。创建POST
具有最少 API 的 a 与 a 没有太大区别GET
,只是您需要引用一个对象,该对象将捕获来自客户端的发布数据。
在 .NET 5 中,我们习惯于在动作的参数中声明一个类型。它的工作原理还是一样的。看看下面的代码:
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapPost(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/"</span>, (Movie movie) =>
{
movies.Add(movie);
<span style="color:#0000ff">return</span> Results.Ok(movies);
});</span></span>
同样,我使用该应用程序来映射一个新的端点。在这种情况下,我使用MapPost
,因为我希望能够将数据发布到 API。然后,我将Movie
电影添加到参数列表中。这会导致 API 将传入的数据从主体映射到Movie
对象。
在正文中,我将新电影添加到电影列表中。出于演示目的,我返回电影列表,包括新添加的电影。
映射删除
我要向您展示的最后一个映射是DELETE
请求或删除。GET
其他请求类型与, POST
, 或基本相同DEL
。
删除需要一个键,或者一些独特的东西。否则,您最终删除的内容可能比您实际想要的要多。所以我们需要一个查询参数,就像id
我在 - 方法中展示的那样MapGet
。
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapDelete(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/{id:int}"</span>, (<span style="color:#0000ff">int</span> id) =>
{
movies.Remove(movies.<span style="color:#339999">Single</span>(x => x.Id == id));
<span style="color:#0000ff">return</span> movies;
});</span></span>
MapGet
它看起来与构造几乎相同,只是MapDelete
使用了 。请注意,我没有使用Results.Ok(…)
. 它不是必需的。像这样返回电影会产生一个 HTTP 200 代码,这很好。
如果您测试 API 并想要删除某些内容,您可以在 URL 中添加要删除的项目的 id。但是,如果您发送GET
请求而不是DELETE
请求,API 将返回具有该给定 id 的电影并且不会删除它。
最小的 API 和依赖注入
您可能希望在 API 中使用依赖注入,这是一种常见的做法。使用控制器时,您可以通过构造函数注入接口。但是我们没有构造函数,我们只有带有 lambda 表达式的映射。
对于这一部分,我创建了一个带有接口的小类。我还将电影列表移至新类:
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> IMovies
{
List<Movie> GetAll();
Movie GetById(<span style="color:#0000ff">int</span> id);
<span style="color:#0000ff">void</span> Delete(<span style="color:#0000ff">int</span> id);
<span style="color:#0000ff">void</span> Insert(Movie movie);
}
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> Movies: IMovies
{
<span style="color:#0000ff">private</span> List<Movie> _movies = <span style="color:#0000ff">new</span>()
{
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">1</span>, Rating = <span style="color:#000080">5</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Shrek"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">2</span>, Rating = <span style="color:#000080">1</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Inception"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">3</span>, Rating = <span style="color:#000080">3</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Jaws"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">4</span>, Rating = <span style="color:#000080">1</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">The Green Latern"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">5</span>, Rating = <span style="color:#000080">5</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">The Matrix"</span> },
};
<span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> Delete(<span style="color:#0000ff">int</span> id)
{
_movies.Remove(_movies.<span style="color:#339999">Single</span>(x => x.Id == id));
}
<span style="color:#0000ff">public</span> List<Movie> GetAll()
{
<span style="color:#0000ff">return</span> _movies;
}
<span style="color:#0000ff">public</span> Movie GetById(<span style="color:#0000ff">int</span> id)
{
<span style="color:#0000ff">return</span> _movies.<span style="color:#339999">Single</span>(x => x.Id == id);
}
<span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> Insert(Movie movie)
{
_movies.Add(movie);
}
}</span></span>
现在我们可以配置接口和实现类。在program.cs中,您可以使用builder.Services
. 要添加IMovies
and Movies
,只需使用以下行:
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#008000"><em>//</em></span><span style="color:#008000"><em> Configuration for dependency injection</em></span>
builder.Services.AddScoped<IMovies, Movies>();</span></span>
将此行放在 之后builder.Services.AddSwaggerGen()
。我们已经配置好了,让我们使用吧!这MapGet
真的很容易:
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/"</span>, (IMovies movies) =>
{
<span style="color:#0000ff">return</span> Results.Ok(movies.GetAll());
})</span></span>
只需将接口和变量名添加到映射的参数列表中。
好,我们来看看第二个get,那个在URL里有id的。
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/{id:int}"</span>, (<span style="color:#0000ff">int</span> id, IMovies movies) =>
{
<span style="color:#0000ff">return</span> Results.Ok(movies.GetById(id));
});</span></span>
是的,就是这样!但是……如果我们切换 id 和 movies 会发生什么?没有什么!嗯,有事发生。它就像上面的例子一样工作。.NET 识别类型,命名约定也有帮助。但是,如果您需要我的建议:在参数列表中保持一致的顺序。我个人喜欢在注入之前添加查询参数。
让我们用 DI 修复最后两个映射:
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapPost(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/"</span>, (Movie movie, IMovies movies) =>
{
movies.Insert(movie);
<span style="color:#0000ff">return</span> Results.Ok(movies.GetAll());
});
app.MapDelete(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/{id:int}"</span>, (<span style="color:#0000ff">int</span> id, IMovies movies) =>
{
movies.Delete(id);
<span style="color:#0000ff">return</span> movies.GetAll();
});</span></span>
使其异步
我们的大多数操作都是异步的,以使 API 处理多个请求并使其工作得更快。我向您展示的示例不是异步的。使映射异步并不难。
对于这一部分,我将类中的方法Movies
设为异步。这不是最好的例子,但它是关于 API,而不是一些基本示例类中的逻辑。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> IMovies
{
Task<List<Movie>> GetAll();
Task<Movie> GetById(<span style="color:#0000ff">int</span> id);
Task Delete(<span style="color:#0000ff">int</span> id);
Task Insert(Movie movie);
}
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> Movies: IMovies
{
<span style="color:#0000ff">private</span> List<Movie> _movies = <span style="color:#0000ff">new</span>()
{
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">1</span>, Rating = <span style="color:#000080">5</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Shrek"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">2</span>, Rating = <span style="color:#000080">1</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Inception"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">3</span>, Rating = <span style="color:#000080">3</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">Jaws"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">4</span>, Rating = <span style="color:#000080">1</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">The Green Latern"</span> },
<span style="color:#0000ff">new</span>() { Id = <span style="color:#000080">5</span>, Rating = <span style="color:#000080">5</span>, Title = <span style="color:#800080">"</span><span style="color:#800080">The Matrix"</span> },
};
<span style="color:#0000ff">public</span> <span style="color:#0000ff">async</span> Task Delete(<span style="color:#0000ff">int</span> id)
{
_movies.Remove(_movies.<span style="color:#339999">Single</span>(x => x.Id == id));
}
<span style="color:#0000ff">public</span> <span style="color:#0000ff">async</span> Task<List<Movie>> GetAll()
{
<span style="color:#0000ff">return</span> _movies;
}
<span style="color:#0000ff">public</span> <span style="color:#0000ff">async</span> Task<Movie> GetById(<span style="color:#0000ff">int</span> id)
{
<span style="color:#0000ff">return</span> _movies.<span style="color:#339999">Single</span>(x => x.Id == id);
}
<span style="color:#0000ff">public</span> <span style="color:#0000ff">async</span> Task Insert(Movie movie)
{
_movies.Add(movie);
}
}</span></span>
我们现在要做的就是使映射异步。这并不难,就像前几章一样。让我们重新开始MapGet
。
<span style="color:#000000"><span style="background-color:#fbedbb">app.MapGet(<span style="color:#800080">"</span><span style="color:#800080">/api/movies/"</span>, <span style="color:#0000ff">async</span> (IMovies movies) =>
{
<span style="color:#0000ff">return</span> Results.Ok(<span style="color:#0000ff">await</span> movies.GetAll());
});</span></span>
看?没那么难。我将 lambda 表达式标记为 async 并在等待的前面添加了等待await movies.GetAll()
。