承接上一篇文章,接下来会说些非常简单的例子,也比较符合 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 和任何静态页面服务。
让我们先从一个学生管理系统说起,这也是大多数例子常用的场景,首先创建一个 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,从零开始就到这里了,感谢各位看官!