【ASP.NET Core】体验一下 Mini Web API

时间:2024-04-16 10:19:31

在上一篇水文中,老周给大伙伴们简单演示了通过 Socket 编程的方式控制 MPD (在树莓派上)。按照计划,老周还想给大伙伴们演示一下使用 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。

 

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

 

选择.net 版本号。

 

  最后,确定新创建项目。

---------------------------------------------------------------------------------

项目创建后你会发现,真TM简洁了不少,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,测试一下。

 

 

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

 

 安装这个包包。

 

把代码改一下。

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。

 

 

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

 

 

怎么样,好用吧?咱们再添加一个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 下。打开项目属性窗口,转到“调试”标签。

 

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

 

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

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

 

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

 

 

下面再举一例,请求方式为 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()}";
});

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

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

 

 

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

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

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}";
});

不要犹豫,运行它!

 

 得到结果:

 

 

在这个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}";
});

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

 

 

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