在Mac下创建ASP.NET Core Web API

2023-07-31,,

在Mac下创建ASP.NET Core Web API

这系列文章是参考了.NET Core文档和源码,可能有人要问,直接看官方的英文文档不就可以了吗,为什么还要写这些文章呢?

原因如下:

官方文档涉及的内容相当全面,属于那种大而全的知识仓库,不太适合初学者,很容易让人失去重要,让人掉入到具体的细节之中。
对于大多数人来讲开发语言只是工具,程序员都有一个通病,就是死磕工具,把工具学深。我认为在工具上没有必要投入太多时间,以能高效地完成日常的工作项目为准即可。要需求驱动学习,你需要什么学什么。如果你学的新技术新特性只是屠龙之技或者只需要用到的时候去查一下即可的话,这种死磕这又有什么用。没有必要花120%的时间去学100%的知识,你只需要花20%的时间去学习80%的知识就可以了,剩下的等实际的项目中用到的时候去查就可以了,工具只是工具,不是工作本身。
目前基本所有的文章都是基于Windows平台的Visual Studio IDE来介绍的。而我用的是一台Mac,所以我将基于Mac平台的Visual Studio Code讲解适合我们实际项目中遇到的知识。
还有一点,就是这是我个人的学习总结。

这系列文章就是让你去花20%的时间去学80%的东西,剩下的20%再去看官方文档。

在.NET Core里面MVC和WebAPI两者被整合成一个框架,分享同一套代码和管线。这样我们就可以更方便地开发MVC应用程序和Web API接口。

创建项目

在这篇文章中我们将要创建的API如下:

API 描述
GET /api/user 获取所有的用户信息
GET /api/user/{id} 根据ID获取指定的用户
POST /api/user 添加新的用户
PUT /api/user/{id} 更新用户信息
PATCH /api/user/{id} 更新用户信息
DELETE /api/user/{id} 删除用户信息

根据上一篇文章,我们通过Yeoman创建一个WebAPI项目,命名为UserWebAPI:

添加模型类

然后在项目根目录下面新建一个Models文件夹,在该文件夹下面利用yo aspnet:class UserItem新建一个UserItem类。

namespace UserWebAPI.Models
{
public class UserItem
{
public string Key { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}

添加仓储类

Repository类是封装了数据层的对象,包含了获取数据、并映射到实体模型类的业务逻辑。

首先我们在Models文件夹下面定义一个IUserRepositoryrepository接口。

通过运行yo aspnet:interface IUserRepository命令来创建该接口。

namespace UserWebAPI.Models
{
public interface IUserRepository
{
void Add(UserItem item);
IEnumerable<UserItem> GetAll();
UserItem Find(string key);
UserItem Remove(string key);
void Update(UserItem item);
}
}

接着再添加一个UserRepository类来实现IUserRepository接口。

namespace UserWebAPI.Models
{
public class UserRepository : IUserRepository
{
private static ConcurrentDictionary<string, UserItem> _users
= new ConcurrentDictionary<string, UserItem>(); public UserRepository()
{
Add(new UserItem { Name = "Charlie", Age = 18 });
} public void Add(UserItem item)
{
item.Key = Guid.NewGuid().ToString();
_users[item.Key] = item;
} public UserItem Find(string key)
{
UserItem user;
_users.TryGetValue(key, out user);
return user;
} public IEnumerable<UserItem> GetAll()
{
return _users.Values;
} public UserItem Remove(string key)
{
UserItem user;
_users.TryRemove(key, out user);
return user;
} public void Update(UserItem item)
{
_users[item.Key] = item;
}
}
}

注册仓储

通过定义一个repository接口,我们从使用它的MVC Controller来解耦该repository类。我们在此将通过注入一个UserRepository来代替直接在Controller里面实例化一个UserRepository类。

为了注入一个repository到controller,我们必须通过DI容器来注册它,打开Startup.cs文件,在ConfigureServices方法添加如下代码:

添加控制器

控制器是用于处理HTTP请求并创建HTTP响应的对象,这里通过运行yo aspnet:webapicontroller UserController命令生成UserController控制器。

namespace UserWebAPI.Controllers
{
[Route("api/[controller]")]
public class UserController : Controller
{
public IUserRepository UserItems { get; set; } public UserController(IUserRepository userItems)
{
UserItems = userItems;
}
}
}

获取用户信息

[HttpGet]
public IEnumerable<UserItem> GetAll()
{
return UserItems.GetAll();
} [HttpGet("{id}", Name = "GetUser")]
public IActionResult GetById(string id)
{
var item = UserItems.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

上述两个方法实现了两个GET方法:

GET /api/user
GET /api/user/{id}

运行dotnet restoredotnet run之后,应用程序将会在本机启动,并在http://localhost:5000上开启监听服务。

然后在Postman上测试你的API接口是否能正确运行。

GetById方法中:

[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)

其中"{id}"是UserItem的ID占位符,当GetById被调用时,URL中的“{id}”值会被分配给该方法的id参数。

Name = "GetTodo"创建了一个命名的路由,并允许你在HTTP响应中链接到该路由。

GetAll方法返回了一个IEnumerable,MVC会自动将对象序列化成JSON并将JSON写入到响应消息的正文中。该方法的响应状态码为200,假设没有发生任何未处理异常。

GetById方法返回的是一个更为通用的IActionResult类型。该方法有两种不同的返回类型:

如果没有项匹配指定的请求ID,该方法通过返回NotFound表示一个404错误。
否则,该方法返回一个JSON响应正文和200响应码,通过返回ObjectResult来表示。

添加新用户

[HttpPost]
public IActionResult Create([FromBody]UserItem item)
{
if (item == null)
{
return BadRequest();
}
UserItems.Add(item);
return CreatedAtRoute("GetUser", new { id = item.Key }, item);
}

通过[HttpPost] attribute 标明这个一个HTTP POST方法,[FromBody] attribute 告诉MVC从HTTP 请求的正文中获取用户UserItem值。

CreatedAtRoute方法返回一个201响应状态码(实际上是CreatedAtRouteResult对象),201状态码是通过POST方法在服务器上成功创建了一个新的资源时的标准响应码。CreateAtRoute也在响应里面添加了一个Location头信息,这个头信息指定了最新创建的User URI。

/// <summary>
/// Creates a <see cref="CreatedAtRouteResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object routeValues,
object value)
{
return new CreatedAtRouteResult(routeName, routeValues, value);
}
/// <summary>
/// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
/// provided.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The value to format in the entity body.</param>
public CreatedAtRouteResult(string routeName, object routeValues, object value)
: base(value)
{
RouteName = routeName;
RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
StatusCode = StatusCodes.Status201Created;
}

通过查看CreatedAtRouteResult的构造函数可以看到StatusCode(从ObjectResult对象继承而来)被直接设置成了Status201Created枚举值。

201状态码是当你在用POST/PUT在服务器端成功创建了一个新的资源时,服务器就应当返回201 Created同时在响应头添加一个Location来指定刚刚创建好的资源的URI。

通过Postman来发送Create请求

刚服务器接收到请求,会在VS Code的控制台显示出相应的信息:

点击Headers tab可以看到Location的值显示刚刚创建好的资源的URI。

更新用户信息(HTTP PUT)

[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody] UserItem item)
{
if (item == null || item.Key != id)
{
return BadRequest();
}
var user=UserItems.Find(id);
if(user==null)
{
return NotFound();
} UserItems.Update(item);
return new NoContentResult();
}

采用了HTTP PUT标记Update方法,并且响应状态码设置为204(No Content)。根据HTTP规范,PUT请求要求客户端发送整个被更新实体,而不是增量更新部分。如果要支持局部更新,则需要使用HTTP PATCH。

204(No Content)状态码表示服务器已经成功处理了你的请求,但不需要返回具体的数据。浏览器不用刷新页面,也不用重定向到新的页面,会保留发送了该请求的页面,不产生任何文档视图上的变化,只停留在当前页面。由于204响应被禁止包含任何消息体,因此它始终以消息头后的第一个空行结尾。对提交到服务器进行处理的数据,如果只需要返回是否成功的话,可考虑使用状态码204来作为返回信息,从而减少多余的数据传输。

NoContentResult类在构造函数中调用了父类的构造函数并把Status204NoContent传给了该类。

namespace Microsoft.AspNetCore.Mvc
{
public class NoContentResult : StatusCodeResult
{
public NoContentResult()
: base(StatusCodes.Status204NoContent)
{
}
}
}

更新用户信息(HTTP PATCH)

[HttpPatch("{id}")]
public IActionResult Update([FromBody] UserItem item, string id)
{
if (item == null)
{
return BadRequest();
} var user = UserItems.Find(id);
if (user == null)
{
return NotFound();
}
item.Key = user.Key; UserItems.Update(item);
return new NoContentResult();
}

删除用户

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var user = UserItems.Find(id);
if (user == null)
{
return NotFound();
}
UserItems.Remove(id);
return new NoContentResult();
}

这个响应状态码同样是204(No Content)

个人博客

我的个人博客

在Mac下创建ASP.NET Core Web API的相关教程结束。

《在Mac下创建ASP.NET Core Web API.doc》

下载本文的Word格式文档,以方便收藏与打印。