就算开发 Android APP 也得懂一点API接口开发(二)之从零开始

承接上一篇文章,接下来会说些非常简单的例子,也比较符合 Restful API 的规范,并且会选用 .NET Core 作为后台开发平台,使用 C# 作为开发语言,.NET Core 目前最新版本是 2.1 ,很多人质疑 .NET 和 C#,觉得太老了,市场上已经没有什么占有率了,学校也不怎么交了,工作也不怎么好找了,被 Java 吊着打之类的。在网络中,或是在工作中,我使用的技术栈总是会被各种人 Diss,这其中有使用 Java的,有使用 Python 的,有使用 Go Lang 的,有使用 Ruby 的,甚至也有使用 Nodejs 的。每一种语言或平台或 Framework 都有它的不足,觉得自己顺手的才是最好的。质疑 .NET 也完全没必要,在企业应用中,.NET 还占据很大的市场,甚至腾讯的支付清算网关也已经使用了 .NET Core 重写了,如果现在开始学习 .NET 的话,我觉得从 .NET Core 会是一个绝好的开端。并且在我上家公司中,我建议了公司采用了 .NET Core + Nancy + SQLServer 完全重写了服务后端和数据分析平台,并且服务也是运行在 Cent OS 上,稳定性也非常高。

.NET Core 入门非常简单,简单到什么程序呢,我觉得任何使用过 Nodejs 和 Python 的都能上手,官网地址是:

https://www.microsoft.com/net/learn/get-started-with-dotnet-tutorial

开发工具也非常丰富,假如以前是用 Notepad++ 或 Sublime 写 PHP 的码农,可以使用 Vscode ,这是官方指定的编辑器,或者继续使用 Notepad+。如果是 .NET 老码农,可以继续使用宇宙第一的 IDE Visual Studio 2018 community。如果是 Java 或者 Android 或者习惯脑浆喷射全家桶 (Jetbrains) 的码农,可以使用 Rider,所以无论你以前使用什么语言,都能找到顺手的工具。

这里,我使用 Rider,因为和 Android Sutdio 界面基本相同,都是 Jetbrains 家的,并确保你已经安装了最新的 .NET Core,安装完成后,使用 dotnet --version 查看一下版本号,并且我会使用 Fancy 作为 Web 框架,而不是使用 .NET Core ,这有一个好处,就是可以在 Windows、Linux、Mac OS 下都可以运行,并且不论是 IIS 还是 Owin 承载方式都能运行起来,甚至丢在一个虚拟主机上也能完整的运行起来。我之前写了一个轻博客系统用来同步我的简书文章,就是使用 Nancy 作为框架,支持任何 .NET 运行环境和服务器软件,甚至可以生成静态页面,支持 GitPages 和任何静态页面服务。

参见 http://1ll.co

让我们先从一个学生管理系统说起,这也是大多数例子常用的场景,首先创建一个 Solution,类型为 .NET Core Console Application。确保右侧的选项卡中,Language 为 C#,Framework 为 netcoreapp2.x。

之后我们会在解决方案资源管理器中看见以下工程结构:

Program.cs 就是整个工程的入口点,也是 Main 方法所在的类,就像是 MainActivity 一样(虽然不太恰当),可以看到右侧编辑器中代码内容如下:

using System;

namespace StudentManagementSystem
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

点击工具栏上的绿色三角形就可以启动我们的第一个 .NET Core Console Application 了,旁边的小虫子图标就是 Debug,搞过 Android 开发用过 Android Studio 的都知道。

之后会弹出下面的对话框,让你做第一次运行配置,如果不知道下面的每一项代表什么意思,那就直接点击 Run 就可以。

然后就可以在状态窗口中看到结果输出:

如果能正常进行到这一步,我觉得是时候配置 Web 容器(框架)了,就像 Jetty 或者 Springboot 里的内置 Tomcat 一样,它不直接处理业务逻辑,而是处理 HTTP 请求,路由等,然后由 Nancy 根据对应的路由来处理对应的业务,最终发布自身可以完成端口注册监听,不过在实际生产环境中都会使用反向代理服务器。

好的,扯远了,在 .NET Core 中,我建议使用 Kestrel 作为 Web 容器服务器,项目地址:

KestrelHttpServer: https://github.com/aspnet/KestrelHttpServer

Kestrel 是微软开发的一个跨平台的 Web 容器服务器,它基于 libuv 这个网络库开发的。libuv 是一个抽象层,它在 Linux 上是由 libev 实现,在 Windows 上是由 IOCP 实现。

要引用第三方库,我们使用包管理器 Nuget,切换到 Nuget 任务窗格。

然后搜索 Kestrel 关键字,选择 Microsoft.AspNetCore.Server.Kestrel 并在右侧窗格中加入到项目中引用。

同理依次添加下列第三方库的引用:

Microsoft.AspNetCore.Hosting
Microsoft.AspNetCore.Owin
Microsoft.Extensions.Configuration.Binder
Microsoft.Extensions.Configuration.Json
Nancy

然后,在项目中创建一个名为 Startup 的类,Startup 是一个每个 Owin 程序都会有的一个类,Owin 是一组应用服务器承载规范,理论上 Startup 需要实现接口 IStartup,不过在 .NET Core 中,都没有明确表示必须使用接口实现,而是被反射调用。

 public interface IStartup
 {
     IServiceProvider ConfigureServices(IServiceCollection services);
     void Configure(IApplicationBuilder app);
 }

所以我们创建一个 Startup 类,指定应用程序管道模型将会由 Nancy 进行处理。

namespace StudentManagementSystem
{
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Nancy.Owin;

    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseOwin(x => x.UseNancy());
        }
    }
}

接下来创建一个 Nancy 处理模块,名为 HomeModule,如果对 ASP.NET MVC 或者 Java Servlet 有所了解的朋友应该了解这么个意思,它用来处理对应的 URL 的 HTTP 请求,可以根据不同的 HTTP 谓词返回不同的结果。

所有的 Module 必须继承于它的父类 NancyModule,并在默认构造函数里处理请求,这用起来虽然有点怪,不过没关系,用习惯了就会一发不可收拾。

所有的谓词都是可以通过 NancyModule 中提供的虚方法自行实现,例如 GET 请求,其源代码如下:

 /// <summary>
/// Declares a route for GET requests.
/// </summary>
/// <param name="path">The path that the route will respond to</param>
/// <param name="action">Action that will be invoked when the route it hit</param>
/// <param name="name">Name of the route</param>
/// <param name="condition">A condition to determine if the route can be hit</param>
public virtual void Get(string path, Func<dynamic, object> action, Func<NancyContext, bool> condition = null, string name = null)
{
    this.Get<object>(path, action, condition, name);
}

/// <summary>
/// Declares a route for GET requests.
/// </summary>
/// <typeparam name="T">The return type of the <paramref name="action"/></typeparam>
/// <param name="path">The path that the route will respond to</param>
/// <param name="action">Action that will be invoked when the route it hit</param>
/// <param name="name">Name of the route</param>
/// <param name="condition">A condition to determine if the route can be hit</param>
public virtual void Get<T>(string path, Func<dynamic, T> action, Func<NancyContext, bool> condition = null, string name = null)
{
    this.Get(path, args => Task.FromResult(action((DynamicDictionary)args)), condition, name);
}

/// <summary>
/// Declares a route for GET requests.
/// </summary>
/// <param name="path">The path that the route will respond to</param>
/// <param name="action">Action that will be invoked when the route it hit</param>
/// <param name="name">Name of the route</param>
/// <param name="condition">A condition to determine if the route can be hit</param>
public virtual void Get(string path, Func<dynamic, Task<object>> action, Func<NancyContext, bool> condition = null, string name = null)
{
    this.Get<object>(path, action, condition, name);
}

/// <summary>
/// Declares a route for GET requests.
/// </summary>
/// <typeparam name="T">The return type of the <paramref name="action"/></typeparam>
/// <param name="path">The path that the route will respond to</param>
/// <param name="action">Action that will be invoked when the route it hit</param>
/// <param name="name">Name of the route</param>
/// <param name="condition">A condition to determine if the route can be hit</param>
public virtual void Get<T>(string path, Func<dynamic, Task<T>> action, Func<NancyContext, bool> condition = null, string name = null)
{
    this.Get(path, (args, ct) => action((DynamicDictionary)args), condition, name);
}

/// <summary>
/// Declares a route for GET requests.
/// </summary>
/// <param name="path">The path that the route will respond to</param>
/// <param name="action">Action that will be invoked when the route it hit</param>
/// <param name="name">Name of the route</param>
/// <param name="condition">A condition to determine if the route can be hit</param>
public virtual void Get(string path, Func<dynamic, CancellationToken, Task<object>> action, Func<NancyContext, bool> condition = null, string name = null)
{
    this.Get<object>(path, action, condition, name);
}

/// <summary>
/// Declares a route for GET requests.
/// </summary>
/// <typeparam name="T">The return type of the <paramref name="action"/></typeparam>
/// <param name="path">The path that the route will respond to</param>
/// <param name="action">Action that will be invoked when the route it hit</param>
/// <param name="name">Name of the route</param>
/// <param name="condition">A condition to determine if the route can be hit</param>
public virtual void Get<T>(string path, Func<dynamic, CancellationToken, Task<T>> action, Func<NancyContext, bool> condition = null, string name = null)
{
    this.AddRoute("GET", path, action, condition, name);
}

我们可以实现一个处理 GET 路由请求 /api/hello 的处理方法,如下:

public class HomeModule:NancyModule
{
    public HomeModule()
    {
        Get("/api/hello", _ => { return "hello nancy!"; });
    }
}

现在是时候检验一下我们的这个路由是否生效,并且是否能按照我们预期处理这个 GET 请求,还需要做一件事,就是在 Main 方法里启动 Kestrel,并制定启动管道配置和处理类为 Startup。

var host = new WebHostBuilder()
          .UseContentRoot(Directory.GetCurrentDirectory())
          .UseKestrel()
          .UseStartup<Startup>()
          .Build();
host.Run();

添加完这部分代码之后,编译执行,看看 Run 窗口有没有下面的输出:

如果由,恭喜你你的第一个 .NET Core 接口服务已经成功 Run 起来了,此时可以点击这个超链接 http://localhost:5000 看看浏览器有什么,不出意外,会出现一个 404 页面,这是因为我们没有为根请求进行任何处理,而此时只要输入我们的第一个 GET 请求的 URL http://localhost:5000/api/hello 的时候,就会出现下面的页面,赫然已经看到了我们的代码已经生效,这个 GET 请求已经被成功处理了。

或者,我们严谨一定啊,让这个接口返回 JSON 数据,毕竟 APP 请求接口,还是 JSON 处理的比较方便。

public HomeModule()
{
    Get("/api/hello", _ => 
    { 
        return this.Response.AsJson(new
        {
            result = 0, 
            message = "ok"
        }); 
    });
}

再次执行查看输出,即可看到我们已经成功输出了 JSON 类型的数据了。

OK,从零开始就到这里了,感谢各位看官!

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

推荐阅读更多精彩内容