ASP.NET Core学习笔记-3:Web API 基础、基础组件
本文最后更新于:2026年1月27日 下午
第一个 Web API 项目!
项目结构

Controller
Web API 中的控制器类都继承自 ControllerBase 类,并为其添加 [ApiController] 这个 Attribute。示例代码中的另外一个 [Route("[controller]")],用于设定路由规则,其中的 [controller] 代表控制器的名字,也就是等于 WeatherForecast。
Restful
“幂等”操作:对于一个接口采用同样的参数请求一次和请求多次的结果是一致的,不会因多次请求而产生副作用。例如:发表评论成功,服务器返回结果给客户端,但是由于网络波动,客户端没能收到“发表成功”的返回结果,此时再次进行发表评论的操作,如果最终只发表了一次评论,那么这个操作就是幂等的;否则就不是。
Swagger
可以理解成提供了对应接口的文档的内置的 Postman(提供接口测试)。
注意:如果控制器中存在一个没有添加 [HttpGet]、[HttpPost] 等 Attribute 的 public 方法,Swagger 就会报错“Failed to load API definition”。解决办法:对应方法添加:[ApiExplorerSettings(IgnoreApi = true)]``Attribute。
ActionResult
ActionResult<T> 通过隐式转换重载来完成 BadRequest、NotFound 等方法返回的非泛型的 ActionResult 到 ActionResult<T> 的转换,它也会完成具体值(比如某一实体类)到 ActionResult<T> 的隐式转换。因此在操作方法中,我们可以直接:return new Book()、return NotFound() 等。
操作方法的参数来源
URL
在 [HttpGet]、[HttpPost]等 Attribute 中使用占位符 {ParameterName} 来捕捉路径中的内容:
1 | |
QueryString
对于使用 QueryString 传递的参数,则是使用 [FromQuery] 来获取值:
1 | |
请求报文体
注意 Content-Type,在 Web API 开发中一般都是 application/json
1 | |
允许跨域
在 Program.cs 的 var app = builder.Build(); 前添加:
1 | |
再在 app.UseHttpsRedirection(); 前添加:app.UseCors();
ASP.NET Core 基础组件
ASP.NET Core 中的依赖注入
在 Program.cs 中,WebApplication.CreateBuilder(args) 所返回的对象类型为 WebApplicationBuilder,它的 Services 属性的类型则是 IServiceCollection,所以也拥有 AddScoped() 等方法。一般把服务注册到这里就行。
食用指北
首先,创建一个服务类MyExampleService,并随便实现一个方法:
1 | |
然后,在 Program.cs 中注册服务:builder.Services.AddScoped<MyExampleService>();
最后,就可以在需要用到该服务的控制器的构造函数(Constructor)中注入服务啦:
1 | |
但是有时候可能遇到特殊情况:如果一个操作方法(比如上面的 Test() 就是一个操作方法)用到的服务(在上面的例子中,Test() 方法需要用到 MyExampleService 服务)的注入比较消耗资源,而这个操作方法被调用到的频率又很低,那么在调用这个控制器中的其他方法时,又会重复注入这个服务,就很浪费资源。
为了避免这种情况,我们可以把调用方法所用到的服务,通过调用方法的参数来注入:
1 | |
将配置系统集成到 ASP.NET Core 中
多环境设置
为了确定运行时环境,ASP.NET Core 会从环境变量中读取名字为 ASPNETCORE_ENVIRONMENT 的值,这个值就是程序运行环境的名字。推荐采用如下3个值来:Development(开发环境)、Staging(测试环境)、Production(生产环境)。如果没有设置,则认为程序运行在生产环境。
我们可以分别为各个环境创建配置文件:appsettings.Development.json、appsettings.Staging.json、appsettings.Production.json
用“用户机密”来避免信息泄露
对应项目点击右键,选择“管理用户机密”,Visual Studio 会自动打开 secrets.json 文件,在这个文件里就可以进行机密信息的配置了。
EF Core 与 ASP.NET Core 的集成
我们可能会将 EF Core 的项目与 ASP.NET 项目分开开发,以达到项目职责的清晰划分的目的,这个时候,我们如果要在 ASP.NET 用上下文,就需要用依赖注入的方式来配置数据库连接了,并添加对 EF Core 项目的依赖。
为了能在 ASP.NET 项目运行的时候再确定上下文对象需要连接到的数据库,我们需要修改一下上下文类:
1 | |
可以看到现在的上下文类不再重写 OnConfiguring 方法来设置数据库了,而是增加了一个 MyDbContext(DbContextOptions<MyDbContext>) 类型参数的构造方法。DbContextOptions 是一个数据库连接配置对象,所以接下来,我们将在 ASP.NET 项目中(通过依赖注入的方法)提供对 DbContextOptions 的配置:
1 | |
别忘了在 application.json 中配置连接字符串:
1 | |
如果我们需要进行迁移,就需要为 EF Core 项目额外配置数据库连接。但是由于在 EF Core 项目中很难使用IConfiguration来读取配置,所以我们可以通过添加系统环境变量来配置数据库连接字串,并在项目中添加一个实现 IDesignTimeDbContextFactory 接口的实现类(数据库迁移工具会调用这个实现类的 CreateDbContext 方法来获取上下文对象,并使用这个对象来连接数据库)来帮助进行迁移:
1 | |
小上下文策略
“不要把项目中所有的实体类都放到同一个上下文类中,而是只把关系紧密的实体类放到同一个上下文类中…也就是项目中存在多个上下文类。”
批量注册上下文:
1 | |
缓存
ASP.NET 不仅提供了把 Web 服务器的内存用作缓存的内存缓存(in-memory cache),还提供了把 Redis、数据库等用作缓存的分布式缓存(distributed cache)。
客户端响应缓存
客户端响应缓存的作用是:当同一个用户在客户端缓存存在时,发起相同的请求,那么请求不会发送至服务端,而是直接返回缓存中的内容。
How to use?
cache-control 响应报文头能够控制浏览器对请求返回内容的缓存时间等,在 ASP.NET 中,我们可以手动控制这个响应报文头,如添加 ResponseCacheAttribute:
1 | |
这样浏览器就会把相应内容缓存60s。
如果客户端不支持客户端缓存,那么这个设置不会生效。
服务器端响应缓存
如果在 ASP.NET Core 中安装了“响应缓存中间件”(response cache middleware),那么设置[ResponseCache]不仅会设置客户端缓存,还会设置服务端缓存。
服务器端响应缓存的作用是:当不同的用户在缓存存在期间,对服务器发起相同的请求,服务器在接收后,会优先返回缓存中的内容,而不是去调用方法。
不建议使用“响应缓存中间件”来设置服务器端响应缓存,建议采用 ASP.NET Core 提供的内存缓存、分布式缓存等机制来进行缓存。
How to use?
像客户端响应缓存那样,我们需要先给方法添加 ResponseCacheAttribute (这个步骤参考“客户端响应缓存”小节),然后再在 Program.cs 的 app.MapControllers 之前加上 app.UseResponseCaching 即可。
如果项目启用了 CORS,请确保 app.UseCors 写在 app.UseResponseCaching 之前。
有可能遇到的问题
- Q: 为什么我启用了响应缓存中间件,并且禁用了浏览器的缓存,但是服务器端响应缓存却没有生效,每次发送请求都会去调用对应方法呢? A: 检查浏览器发送的请求的报文头是否包含了“cache-control: no-cache”
内存缓存
ASP.NET Core 提供了允许开发人员手动进行缓存管理的“内存缓存”机制,它将数据保存在当前运行的网站程序的内存中(可以理解成在“后端”的内存中),与进程相关。
How to use
对于 ASP.NET Core MVC 项目,框架会自动注入内存缓存 服务;对于 ASP.NET Core Web API,则需要在 Program.cs 的 builder.Build 之前添加 builder.Services.AddMemoryCache 来把内存缓存相关服务注册到依赖注入容器中。
IMemoryCache
我们可以通过 IMemoryCache 接口来使用内存缓存,它使用起来有点像是 IDictionary,也是通过键值对来保存内容。以下是该类的几个常用方法:
bool TryGetValue(object key, object value): 尝试获取键为key的缓存值,并保存到value中。成功获取返回true,否则false。void Remove(object key): 删除键为key的缓存值。TItem Set<TItem>(object key, TItem value): 设置键为key的缓存值为value。TItem GetOrCreate<TItem>(object key, Func<ICacheEntry, TItem> factory): 尝试获取键为key的缓存值,方法的返回值为获取到的缓存值。如果缓存中不存在键为key的缓存值,则调用factory来获取数据,把获取到的数据保存到缓存中,同时将其作为方法的返回值。
通常来说,使用GetOrCreate来设置缓存键值对会比较好,因为其factory回调方法中有一个ICacheEntry类型的参数,这个参数能够让我们对缓存项进行一些设置,如:缓存项被清除后的回调函数、设置缓存项的优先级等。缓存在默认情况下是不会过期的,我们必须通过1
2
3
4
5
6
7
8
9[HttpGet]
public async Task<Book[]> GetBooks()
{
var items = await memCache.GetOrCreateAsync("AllBooks", (e) =>
{
return await dbCtx.Books.ToArrayAsync();
});
return items;
}ICacheEntry来为其手动设置绝对过期时间或是滑动过期时间。- 绝对过期时间:经过指定的时间后,清除缓存项。
- 滑动过期时间:若指定的时间期间内该缓存项未被访问过,则清除缓存项;若访问过该缓存项,则自动续期过期时间。
1
2
3
4
5
6
7
8
9
10public async Task<Book[]> GetBooks()
{
var items = await memCache.GetOrCreateAsync("AllBooks", (e) =>
{
e.SlidingExpiration = TimeSpan.FromSeconds(10);
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
return await dbCtx.Books.ToArrayAsync();
});
return items;
}
分布式缓存
在分布式系统中,通常会有一台专门用于保存缓存的服务器,这样来自不同服务器的请求都能够通过同一个服务器来获取缓存,避免造成资源的浪费。.NET Core 为我们提供了统一的分布式缓存服务器的操作接口,IDistributedCache,无论用什么类型的分布式缓存服务器,都能通过使用这个接口来进行缓存管理操作。IDistributedCache 的常用方法如下:
Task<byte[]> GetAsync(string key): 查询键为key的缓存项Task RefreshAsync(string key): 刷新键为key的缓存项Task RemoveAsync(string key): 删除键为key的缓存项Task.SetAsync(string key, byte[] value, DistributedCacheEntryOptions options): 设置键为key的缓存项,value为byte[]类型Task<string> GetStringAsync(string key): 查询键为key的缓存项Task.SetStringAsync(string key, string value, DistributedCacheEntryOptions options): 设置键为key的缓存项,value为string类型
使用 Redis 作为缓存服务器
微软官方提供了在 .NET Core 中对 Redis 的支持,只需要通过 NuGet 安装 Microsoft.Extensions.Caching.StackExchangeRedis 包即可。
要在 ASP.NET Core 应用程序中使用 Redis,请先在 Program.cs 中注册 Redis 缓存服务:
1 | |
接着在需要用到缓存服务的控制器中注入 IDistributedCache 服务即可,很方便。

希望本文章能够帮到您~