ASP.NET Core 体验 Mini Web API

按照计划,老周还想给大伙伴们演示一下使用 Web API 来封装对 MPD 控制。思路很 Easy,树莓派上使用本地 Socket 来封装一下,然后以 Web API 的方式对客户端公开。
 
这样有一个好处:之后不管你打算把客户端做成桌面窗口,还是 Web 页面,或是做成手机 App,你都可以直接调用这套 Web API。这样一来,很多代码就不必重复写了,省时省力,减少脑细胞的大量死亡。
 
如果大家比较关心时事的话,应该知道 .NET 6 的 ASP.NET Core 有一个新特性—— Mini API或 Mini Web API。有一个不错的翻译叫做“极简 API”。其实,极简的不只是 Web API,整个 ASP.NET Core 应用项目的结构都精简了不少。.
 
最让老周高兴的就是没有了 Startup 类(其实早期版本中也可以不使用 Startup,如果你以前看过老周的误人子弟教程的话,你应该有印象),也不用费心地搞个什么 ConfigureService 又要弄个 Configure 方法约定了。再配合 C# 9 的新功能,连 Main 方法都省了。所以初始化配置工作都可以在一个代码文件中搞定。
 
使用这个简化版的 Web API 来做 MPD 的封装还真的不错。不过,本文老周先不弄这个,先让大伙伴们了解一下 Mini API 怎么玩——权且当作预备知识。
 
.NET 6 还有个新功能也不错,就是全局的 using 指令。以前在 C 语言中,如果你写一个 abc.h 头文件,里面放上这些代码:
#ifndef _ABC_H_
#define _ABC_H_

#include "nc.h"
#include "nt.h"
#include "zz.h"
#include "xb.h"
#include "sb.h"

#endif

然后在代码文件中 include 一个这个 abc.h 就可以间接引用这些头文件,但 C# 中没有这种玩法,每个代码文件要用到哪些命名空间,都要写一遍 using 指令。

在随同 .NET 6 一同发布的 C# 10 中终于有全局 using 了。只要你在其中一个代码文件中写上全局 using 指令,然后其他代码文件中就不必再 using 了。

方法是在 using 前加上 global 就行了。C# 虽然早有 global 关键字,但在过去这个并不是全局 using 用的,而是专用来标识 .NET 框架中的命名空间的,主要是防止命名空间重名的。

Mini API 使用

此处假设你已经安装好 VS 2022 和 .NET 6。在创建新项目时选择空白 ASP.NET Core 应用程序。用空白项目方便稍后写自己的 Code。

ASP.NET Core 体验 Mini Web API

然后输入项目名称以及存放路径。

ASP.NET Core 体验 Mini Web API

选择.NET 版本号。

ASP.NET Core 体验 Mini Web API

最后,确定新创建项目。

项目创建后你会发现,真简洁了不少,Program.cs 文件中只有这么几行。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

第一行:调用 CreateBuilder 方法创建一个builder ,这个 builder 随后用于构建 Web 应用程序。

第二行:直接就用预设的参数构建了一个 app 对象。

第三行:配置 HTTP 管道——怎么处理HTTP请求。此处配置表明只向客户端返回“Hello World!”。

第四行:运行 app。

你,不用再写 Startup 类了,也不用遵守方法签约定去写 ConfigureServices 等方法了。

要配置服务咋办?直接 Services Add。例如

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddRazorPages();
builder.Services.AddAntiforgery()
                .AddWebEncoders()
                .AddSingleton<XXX, YYY>();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");

app.Run();

看看是不是很整齐?注意这个过程是在 build 方法调用之前完成的。原因和以前 Startup 类中写方法一样,Add 服务是声明咱们的应用程序中要使用哪些功能,哪些对象被用于依赖注入,一旦 build 了,程序的功能结构就确定下来了,所以应用程序构建后就不再修改其功能了。

以前在写 Startup 时,我们知道,还有一个 Configure 方法用来配置中间件的,也就是刚刚说的配置HTTP管道。build 方法构建 app 后,就可以直接通过这个 app 实例来配置你要 Use 的东西。例如

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// 中间件配置
app.MapGet("/", () => "Hello World!");
app.UseRouting();
app.UseCookiePolicy();
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("Server", "Big Bomb");
    await next();
});

app.MapControllers(); 

app.MapBlazorHub(); 

app.MapRazorPages();

app.Run();

如果我们要编写 Mini API,不需要 Add 什么 Service,也不需要 Use 什么组件,在 build 和 app.run 之间直接 MapXXX 就行了。XXX 指 HTTP 请求方法,比如 GET、POST、PUT 等。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//======================
// Mini API 写在这里
//=======================
app.Run();

相当有意思的是:这些 MapXXX 扩展方法都有一个 handler 参数,类型是 Delegate。也就是说你在调用时可以传递任何类型的委托对象。于是,就可以这样搞:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseHttpsRedirection();
//============================
// Mini API 写在这里
app.MapGet("/greet", () =>
{
    return "What the fuck?";
});
//===========================
app.Run();

一个 Web API 就完成了。真的,可以用了,不信运行下看看。拿出秘密武器——Postman,测试一下。

ASP.NET Core 体验 Mini Web API

用 Postman 还是有些不够爽,而且这货现在越做越复杂,还整天叫你注册帐号,实属无趣。我们改为用 swagger 来测试。打开 Nuget 包管理器,搜索 swagger。

ASP.NET Core 体验 Mini Web API

安装这个包包。

把代码改一下。

var builder = WebApplication.CreateBuilder(args);
// 不要忘了加这两个服务
builder.Services.AddSwaggerGen();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
//======================
// Mini API 写在这里
app.MapGet("/greet", () =>
{
    return "What the fuck?";
});
//========================
app.Run();

这样一来,咱们在开发测试阶段就可以直接用这个组件在 Web 页上测试 API 的调用了,不再启动其他工具软件了。

运行该项目,然后浏览器定位到 http(s)://root_url:port/swagger。

比如,我运行后得到的URL是 https://localhost:7189,那么在浏览器中打开 https://localhost:7189/swagger。

ASP.NET Core 体验 Mini Web API

点击右边的“try it out”,然后点 “Execute” 按钮,就能看到执行结果了。

ASP.NET Core 体验 Mini Web API

怎么样,好用吧?咱们再添加一个API,这次要带参数的 POST 请求。

app.MapPost("/submit", (string name, int age) =>
{
    return $"你提交的内容:{name} - {age}";
});

这个 API 带两个参数,分别取名为 name,age。在调用时,是通过请求的 body 来提供的,格式为 JSON。

前面老周说过,这些MapXXX方法的委托参数是 Delegate 类型,所以你可以用任意类型的委托,有参数的没参数的,有返回值的没返回值的。

每次调试时都要在浏览器地址输入 http(s)://root/swagger 太TM不方便了,咱们可以配置一下,让其自动定位到 swagger 下。打开项目属性窗口,转到“调试”标签。

ASP.NET Core 体验 Mini Web API

点击页面上的“打开调试启动配置文件 UI”链接。

ASP.NET Core 体验 Mini Web API

把滚动条往地狱方向拉,一直拉到看到“URL”标题,在文本中填上 swagger。搞定。

现在,你直接运行项目,就自动打开 API 列表了。点击展开 submit,再点“try it out”。

ASP.NET Core 体验 Mini Web API

为 name 和 age 参数填上值,执行。

ASP.NET Core 体验 Mini Web API

下面再举一例,请求方式为 GET,参数来自 URL 查询(即带 ? 的URL,如 /abc?t=3000)。

app.MapGet("/md5", ([FromQuery(Name = "msg")] string data) =>
{
    byte[] buffer = Encoding.UTF8.GetBytes(data);
    using MD5 md5ec = MD5.Create();
    byte[] comres = md5ec.ComputeHash(buffer);
    return $"加密结果:{Convert.ToHexString(comres).ToLower()}";
});

这个 PI 的功能:接收一个字符串类型的对象,对其作 MD5 运算,然后返回结果。这个请求是从查询字符串中得到参数 data 的值的,所以要加上 FromQuery 特性,而且,实际传值时查询参数的名称与API的参数名称不同,故要用 Name = .... 明确指定,要从 msg 查询参数中提取值。

综上,此API的调用方式为 GET /md5?msg=呵呵哈哈呵呵哈。

ASP.NET Core 体验 Mini Web API

咱们玩这一步了,你心中一定有个高大上的疑问:这货支持依赖注入乎?

很好,老周也有此疑问,要不,咱们搞搞看。

public interface IComputer
{
    int RunIt(int x, int y, int z);
}

internal class ComputerService : IComputer
{
    public int RunIt(int x, int y, int z)
    {
        return x - y - z;
    }
}

我定义了一个服务接口,里面有个 RunIt 方法;然后俺实现之。逻辑很简单,x、y、z 三数相减。

接下来改代码,在 build 方法调用之前注册服务,咱们就注册个单实例模式吧,全局共享一个实例。

var builder = WebApplication.CreateBuilder(args);
……
builder.Services.AddSingleton<IComputer, ComputerService>();

然后,我们完成 API。

app.MapPost("/comp", (int n1, int n2, int n3, IComputer compsv) =>
{
    int r = compsv.RunIt(n1, n2, n3);
    return $"计算结果:{r}";
});

不要犹豫,运行它!

ASP.NET Core 体验 Mini Web API

得到结果:

ASP.NET Core 体验 Mini Web API

在这个API中,n1,n2,n3 三个参数是从客户端 POST 过来的,而最后一个参数是通过依赖注入得到引用对象的。那么,把参数的位置调换一下,是否也可行呢?

app.MapPost("/comp", (IComputer compsv, int n1, int n2, int n3) =>
{
    int r = compsv.RunIt(n1, n2, n3);
    return $"计算结果:{r}";
});

然后再测,结果表明,是可行滴。

ASP.NET Core 体验 Mini Web API

好了,话题就聊到这儿了,下次咱们就用这个 Mini API 来封装 MPD。