在Mac下创建ASP.NET Core Web API

在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)

个人博客

我的个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,446评论 0 6
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,515评论 6 13
  • 一、概念(载录于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434阅读 8,295评论 6 152
  • 近几日,小西因工作忙,外加单位离家路途有点远,来回奔波,很费时间,所以小西的午餐基本都是外面买了带回家吃。 她边...
    毛毛虫也有春天阅读 82评论 0 1