使用ASP.NET Core和Ion构建优雅的REST API
你好!在这个仓库中,我们提供了一个用C#和ASP.NET Core 1.1编写的示例API。它利用Ion超媒体规范作为起点,设计出一个一致且简洁的REST API,完全拥抱了HATEOAS原则。
这个例子在我的演讲《使用ASP.NET Core构建美观的RESTful API》中被用到(查看链接以获取幻灯片)。
深入学习视频课程
想要深入探讨REST、HATEOAS、Ion、ASP.NET Core以及更多相关知识,可以查看我在LinkedIn Learning上的课程在ASP.NET Core中构建和保护RESTful API。如果你没有LinkedIn Learning或Lynda的订阅,可以通过电子邮件联系我,我会为你提供优惠券!
如何试用
- 克隆此仓库。
- 使用Visual Studio或命令行(使用
dotnet build
)构建解决方案。 - 运行项目。API将在http://localhost:50647或http://localhost:5000(使用
dotnet run
)启动。 - 使用HTTP客户端如Postman或Fiddler进行测试,发送
GET http://localhost:50647
请求。 - 尽享HATEOAS。
- 获得收益!:moneybag:
建立RESTful API在ASP.NET Core中的技巧
这个示例包含许多我在ASP.NET Core上构建API时学到的技巧。如果有关于改进的建议,请告诉我!
在内存中使用Entity Framework Core实现快速原型
借助Entity Framework Core的内存提供者,你可以快速地进行原型设计而不必关心数据库设置。你可以建立并测试一个快速的内存存储,并在准备就绪时切换到实际的数据库。
安装Microsoft.EntityFrameworkCore.InMemory包后,创建一个DbContext
:
public class ApiDbContext : DbContext
{
public ApiDbContext(DbContextOptions<ApiDbContext> options)
: base(options)
{
}
// DbSet...
}
与其他“正常”的DbContext
唯一的区别是在构造函数中添加了接受DbContextOptions<>
参数的需求。这是内存提供者所要求的。
然后,在Startup.ConfigureServices
中配置内存提供者:
services.AddDbContext<ApiDbContext>(options =>
{
// 使用随机数据库名的内存数据库进行测试
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
});
应用启动时,数据库将为空。为了使原型设计和测试更容易,你可以在Startup.cs
中添加测试数据:
// 在Configure()中
var dbContext = app.ApplicationServices.GetRequiredService<ApiDbContext>();
AddTestData(dbContext);
private static void AddTestData(ApiDbContext context)
{
context.Conversations.Add(new Models.ConversationEntity
{
Id = Guid.Parse("6f1e369b-29ce-4d43-b027-3756f03899a1"),
CreatedAt = DateTimeOffset.UtcNow,
Title = "谁是最酷的复仇者?"
});
// 别忘了保存更改!
context.SaveChanges();
}
通过Ion模型化链接、资源和集合
Ion为JSON中的REST对象提供了简单框架。这些Ion对象可以用C#的POCO来表示。这是一个Link对象的例子:
public class Link
{
public string Href { get; set; }
// 由于ASP.NET Core默认使用JSON.NET,因此可以通过JSON.NET属性调整序列化
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore)]
[DefaultValue(GetMethod)]
public string Method { get; set; }
[JsonProperty(PropertyName = "rel", NullValueHandling = NullValueHandling.Ignore)]
public string[] Relations { get; set; }
}
资源和集合的建模同样简便:
// 资源也是(自引用)链接
public abstract class Resource : Link
{
// 在响应管道中通过LinkRewritingFilter重写
[JsonIgnore]
public Link Self { get; set; }
}
// 集合也是资源
public class Collection<T> : Resource
{
public const string CollectionRelation = "collection";
public T[] Value { get; set; }
}
这些基类使得从API返回响应变得非常简洁。
基础API控制器与路由
ASP.NET Core中的API控制器继承自Controller
类,并使用属性定义路由。常见的模式是将控制器命名为<RouteName>Controller
,并将/[controller]
作为属性值,这样会自动根据控制器名称命名路由:
// 处理所有在/comments下的路由
[Route("/[controller]")]
public class CommentsController : Controller
{
// 方法...
}
控制器的方法处理特定的HTTP动词和子路由。返回IActionResult
使您可以灵活地返回HTTP状态码和对象负载:
// 处理路由:
// GET /comments
[HttpGet]
public async Task<IActionResult> GetCommentsAsync(CancellationToken ct)
{
return NotFound(); // 404
return Ok(data); // 200带有JSON负载
}
// 处理路由:
// GET /comments/{commentId}
// 并将{commentId}绑定到方法签名中的参数
[HttpGet("{commentId}"]
public async Task<IActionResult> GetCommentByIdAsync(Guid commentId, CancellationToken ct)
{
// ...
}
命名路由模式
如果你需要稍后在代码中引用特定的路由,你可以使用Name
属性在路由属性中提供唯一名称。我喜欢使用nameof
以方法自身相同的描述性名称来命名路由:
[HttpGet(Name = nameof(GetCommentsAsync))]
public async Task<IActionResult> GetCommentsAsync(CancellationToken ct)
{
// ...
}
这样,编译器会确保路由名称总是正确的。
异步/等待最佳实践
ASP.NET Core在堆栈全过程中支持异步/等待。任何进行网络或数据库调用的控制器和服务都应该async
。Entity Framework Core提供了像SingleAsync
和ToListAsync
这样的异步数据库方法。
向路由方法添加CancellationToken
参数允许ASP.NET Core通知你的异步任务取消(例如,如果浏览器关闭了连接)。
保持控制器精简
我喜欢尽可能让控制器保持简洁,只关注以下方面:
- 验证模型绑定(或者不验证,见下文!)
- 检查null值,尽早返回
- 协调对服务的请求
- 返回友好的结果
避免业务逻辑!保持控制器的精简使其更易于测试和维护。精简的控制器也更适合更复杂的模式,如CQRS或Mediator。
通过ActionFilter验证模型绑定
大多数路由都需要在继续之前验证输入值是否有效。这可以通过一行代码完成:
if (!ModelState.IsValid) return BadRequest(ModelState);
而不是在每个路由方法的开头都有这行代码,你可以把它作为一个属性提取到ActionFilter中:
[HttpGet(Name = nameof(GetCommentsAsync))]
[ValidateModel]
public async Task<IActionResult> GetCommentsAsync(...)
ModelState
字典包含了描述错误的消息(特别是当模型用到了验证属性)。你可以把所有的错误返回给用户,也可以遍历字典来取出第一条错误:
var firstErrorIfAny = modelState
.FirstOrDefault(x => x.Value.Errors.Any())
.Value?.Errors?.FirstOrDefault()?.ErrorMessage
提供根路由
除非API有一个清晰的入口点,否则它不是HATEOAS。根文档可以定义为一组链接的简单资源:
public class RootResource : Resource
{
public Link Conversations { get; set; }
public Link Comments { get; set; }
}
并且可以被很容易地返回:
// 省略实现
这就是为什么你应该考虑使用这个开源项目。它不仅提供了实用的代码示例,还涵盖了REST API设计的最佳实践。无论你是新手还是经验丰富的开发者,这个项目都会让你的API设计更加高效、易于理解和扩展。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考