ASP.NET Core学习笔记-3:Web API 基础、基础组件

本文最后更新于:2025年3月5日 晚上

第一个 Web API 项目!

项目结构

项目结构

Controller

Web API 中的控制器类都继承自ControllerBase类,并为其添加[ApiController]这个Attribute。示例代码中的另外一个[Route("[controller]")],用于设定路由规则,其中的[controller]代表控制器的名字,也就是等于WeatherForecast

Restful

“幂等”操作:对于一个接口采用同样的参数请求一次和请求多次的结果是一致的,不会因多次请求而产生副作用。例如:发表评论成功,服务器返回结果给客户端,但是由于网络波动,客户端没能收到“发表成功”的返回结果,此时再次进行发表评论的操作,如果最终只发表了一次评论,那么这个操作就是幂等的;否则就不是。

Swagger

可以理解成提供了对应接口的文档的内置的 Postman(提供接口测试)。
注意:如果控制器中存在一个没有添加[HttpGet][HttpPost]Attributepublic方法,Swagger 就会报错“Failed to load API definition”。解决办法:对应方法添加:[ApiExplorerSettings(IgnoreApi = true)]``Attribute

ActionResult

ActionResult<T>通过隐式转换重载来完成BadRequestNotFound等方法返回的非泛型的ActionResultActionResult<T>的转换,它也会完成具体值(比如某一实体类)到ActionResult<T>的隐式转换。因此在操作方法中,我们可以直接:return new Book()return NotFound()等。

操作方法的参数来源

URL

[HttpGet][HttpPost]Attribute中使用占位符{ParameterName}来捕捉路径中的内容:

1
2
[HttpGet("bookTitle/{title}/bookAuthor/{author}")]
public ActionResult<Book> GetBookInfo(string title, [FromRoute(Name = "author")]string mainAuthor);

QueryString

对于使用 QueryString 传递的参数,则是使用[FromQuery]来获取值:

1
2
// 每页五条数据,获取第十页数据的 QueryString: pNum=10&pageSize=5
public ActionResult<Book[]> GetBooks([FromQuery(Name = "pNum")]string pageNum, [FromQuery]string pageSize);

请求报文体

注意 Content-Type,在 Web API 开发中一般都是application/json

1
2
[HttpPost]
public ActionResult AddNewPerson(Person person);

允许跨域

在 Program.cs 的var app = builder.Build();前添加:

1
2
string[] urls = new[] { "http://localhost:5173" };
builder.Services.AddCors(options => options.AddDefaultPolicy(builder => builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowCredentials()));

再在app.UseHttpsRedirection();前添加:app.UseCors();

ASP.NET Core 基础组件

ASP.NET Core 中的依赖注入

在 Program.cs 中,WebApplication.CreateBuilder(args)所返回的对象类型为WebApplicationBuilder,它的Services属性的类型则是IServiceCollection,所以也拥有AddScoped()等方法。一般把服务注册到这里就行。

食用指北

首先,创建一个服务类MyExampleService,并随便实现一个方法:

1
2
3
4
5
6
7
public class MyExampleService
{
public IEnumerable<string> GetNames()
{
return new string[] { "Alice", "Joe", "Maple" };
}
}

然后,在 Program.cs 中注册服务:builder.Services.AddScoped<MyExampleService>();
最后,就可以在需要用到该服务的控制器的构造函数(Constructor)中注入服务啦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Route("api/[controller]/[action]")]
[ApiController]
public class PersonsController : ControllerBase
{
private readonly MyExampleService exampleService;
public PersonsController(MyExampleService exampleService)
{
this.exampleService = exampleService;
}

[HttpGet]
public string Test()
{
var names = exampleService.GetNames();
return string.Join(", ", names);
}
}

但是有时候可能遇到特殊情况:如果一个操作方法(比如上面的Test()就是一个操作方法)用到的服务(在上面的例子中,Test()方法需要用到MyExampleService服务)的注入比较消耗资源,而这个操作方法被调用到的频率又很低,那么在调用这个控制器中的其他方法时,又会重复注入这个服务,就很浪费资源。
为了避免这种情况,我们可以把调用方法所用到的服务,通过调用方法的参数来注入:

1
2
3
4
5
6
[HttpGet]
public string Test([FromServices]exampleService)
{
var names = exampleService.GetNames();
return string.Join(", ", names);
}

将配置系统集成到 ASP.NET Core 中

多环境设置

为了确定运行时环境,ASP.NET Core 会从环境变量中读取名字为ASPNETCORE_ENVIRONMENT的值,这个值就是程序运行环境的名字。推荐采用如下3个值来:Development(开发环境)、Staging(测试环境)、Production(生产环境)。如果没有设置,则认为程序运行在生产环境。
我们可以分别为各个环境创建配置文件:appsettings.Development.jsonappsettings.Staging.jsonappsettings.Production.json

用“用户机密”来避免信息泄露

对应项目点击右键,选择“管理用户机密”,Visual Studio 会自动打开secrets.json文件,在这个文件里就可以进行机密信息的配置了。

EF Core 与 ASP.NET Core 的集成

我们可能会将 EF Core 的项目与 ASP.NET 项目分开开发,以达到项目职责的清晰划分的目的,这个时候,我们如果要在 ASP.NET 用上下文,就需要用依赖注入的方式来配置数据库连接了,并添加对 EF Core 项目的依赖。
为了能在 ASP.NET 项目运行的时候再确定上下文对象需要连接到的数据库,我们需要修改一下上下文类:

1
2
3
4
5
6
7
8
9
10
public class MyDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}

可以看到现在的上下文类不再重写OnConfiguring方法来设置数据库了,而是增加了一个MyDbContext(DbContextOptions<MyDbContext>)类型参数的构造方法。DbContextOptions是一个数据库连接配置对象,所以接下来,我们将在 ASP.NET 项目中(通过依赖注入的方法)提供对DbContextOptions的配置:

1
2
3
4
5
builder.Services.AddDbContext<MyDbContext>(opt =>
{
string connStr = builder.Configuration.GetConnectionString("Default");
opt.UseSqlServer(connStr);
});

别忘了在application.json中配置连接字符串:

1
2
3
4
5
{
"ConnectionStrings": {
"Default": "Server=.;Database=demo2;Trusted_Connection=True;TrustServerCertificate=True;"
}
}

如果我们需要进行迁移,就需要为 EF Core 项目额外配置数据库连接。但是由于在 EF Core 项目中很难使用IConfiguration来读取配置,所以我们可以通过添加系统环境变量来配置数据库连接字串,并在项目中添加一个实现IDesignTimeDbContextFactory接口的实现类(数据库迁移工具会调用这个实现类的CreateDbContext方法来获取上下文对象,并使用这个对象来连接数据库)来帮助进行迁移:

1
2
3
4
5
6
7
8
9
10
public class MyDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder<MyDbContext> builder = new();
string connStr = Environment.GetEnvironmentVariable("ConnectString:BookEFCore");
builder.UseSqlServer(connStr);
return new MyDbContext(builder.Options);
}
}

小上下文策略

“不要把项目中所有的实体类都放到同一个上下文类中,而是只把关系紧密的实体类放到同一个上下文类中…也就是项目中存在多个上下文类。”
批量注册上下文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static IServiceCollection AddAllDbContexts(this IServiceCollection services, Action<DbContextOptionsBuilder> builder, IEnumerable<Assembly> assemblies)
{
Type[] types = new Type[]
{
typeof(IServiceCollection),
typeof(Action<IServiceCollection>),
typeof(ServiceLifetime),
typeof(ServiceLifetime)
};
var methodAddDbContext = typeof(EntityFrameworkServiceCollectionExtensions).GetMethod("AddDbContext", 1, types);
foreach (var asmToLoad in assemblies)
{
foreach (var dbCtxType in asmToLoad.GetTypes().Where(t => !t.IsAbstract && typeof(DbContext).IsAssignableFrom(t)))
{
var methodGenericAddDbContext = methodAddDbContext.MakeGenericMethod(dbCtxType);
methodGenericAddDbContext.Invoke(null, new object[] { services, builder, ServiceLifetime.Scoped, ServiceLifetime.Scoped });
}
}
return services;
}

缓存

ASP.NET 不仅提供了把 Web 服务器的内存用作缓存的内存缓存(in-memory cache),还提供了把 Redis、数据库等用作缓存的分布式缓存(distributed cache)。

客户端响应缓存

cache-control响应报文头能够控制浏览器对请求返回内容的缓存时间等,在 ASP.NET 中,我们可以手动控制这个响应报文头,如添加ResponseCacheAttribute

1
2
3
4
5
[HttpGet]
[ResponseCache(Duration=60)]
public DateTime Now(){
return DateTime.Now;
}

这样浏览器就会把相应内容缓存60s。

如果客户端不支持客户端缓存,那么这个设置不会生效。

服务器端响应缓存

如果在 ASP.NET Core 中安装了“响应缓存中间件”(response cache middleware),那么设置[ResponseCache]不仅会设置客户端缓存,还会设置服务端缓存。


这里有一只爱丽丝

希望本文章能够帮到您~


ASP.NET Core学习笔记-3:Web API 基础、基础组件
https://map1e-g.github.io/2024/11/02/asp-net-learning-3/
作者
MaP1e-G
发布于
2024年11月2日
许可协议