如何使用 C# 和 .NET 创建无服务器 API

2025-05-25

如何使用 C# 和 .NET 创建无服务器 API

在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris

在本文中,我们将学习如何使用 C# 和 .NET 构建一个无服务器函数。我们将解释无服务器的原理,并学习如何在 VS Code 中构建、运行和调试我们的第一个函数。

如果您对如何在 JavaScript 中编写函数感兴趣,请看下文,否则,请继续阅读。

如果您对 JavaScript 和无服务器感兴趣,请查看本系列

https://dev.to/azure/serverless-how-you-can-learn-serverless-authoring-functions-in-portal-vs-code-write-apis-and-more-1cno

在我们开始构建第一个函数之前,让我们先提一下我们将在本文中介绍的内容:

  • 为何选择无服务器?问问自己为什么要做这件事至关重要。有一些标准使使用无服务器函数成为一个不错的选择。
  • 核心概念:无服务器架构包含一系列概念,你需要了解这些概念才能高效地使用它,并大致了解它的基本原理。你需要了解的最重要的概念是输入/输出绑定。
  • 构建我们的第一个函数,我们将在这里介绍如何通过设置触发器来编写函数,以及如何解析来自路由/查询参数或已发布的 Body 的输入。我们还将了解如何定义输出。

资源

 为什么无服务器

Serverless 是一种云计算执行模型

好吧,听起来有点棘手。我们到底想说什么呢?

很简单,我们编写的函数将在云端执行。这意味着我们无需关心分配虚拟机,甚至无需运行 Web 服务器来运行代码,这些都由云端自行管理。这也意味着资源分配也由云端自行管理。

听起来不错,还有什么?

选择无服务器的主要原因之一是成本或零成本。无服务器很便宜。通常情况下,您只需在函数实际运行时付费。

怎么会便宜呢?

您的函数并非始终存在,它们的实例会在您需要时启动。这意味着您可能会遇到所谓的冷启动。这意味着实例首次启动并开始与其交互可能需要一些时间。

我不确定这听起来是否很好,如果我希望我的客户能够全天候使用我的功能,或者至少避免这种冷启动,那该怎么办?

如果你真的想避免冷启动,有一个高级计划

https://docs.microsoft.com/en-us/azure/azure-functions/functions-premium-plan

但在你开始之前,让我们先讨论一下在那里实际运行什么代码。

好的?

是的,所有类型的代码和业务逻辑都不应该在那里运行。只有那些你很少用到的东西才应该以无服务器的方式运行,比如一些实用函数或某种计算。

好的,我明白了。我想我的代码库里有一些代码可以转换成无服务器模式,不需要那么频繁地运行。这样做可以省钱,对吧?

是的,很少运行的代码可以变成无服务器的,从而可以节省大量的钱。

就这些吗?

嗯,不止于此,无服务器通常是更大图景的一部分,因为它可以与大多数云资源交互。它可以被从数据库到队列到 HTTP 的一切触发,我们也可以轻松地将计算结果传递给云中的另一个服务。

哇,听起来......真的很棒。

 核心概念

好的,我们已经提到了部分核心概念,但让我们更清楚地了解这些概念是什么。

我们得到以下概念:

  • 触发器,用于触发函数启动。触发器可以是 HTTP 调用、触发事件、数据库中的一行数据等等。
  • 输入绑定,输入绑定是与资源的连接,可以是 SignalR、表存储、CosmosDB 等等。关键在于我们无需实例化连接,它已经预先创建好了,随时可以进行交互。
  • 输出绑定,就是你可以写入的内容。例如,你可以使用输入绑定连接到数据库并读取产品列表。相反,你可以使用输出绑定,根据函数的输入数据创建新的产品条目。

这可能听起来有点奇怪,但当我们学会创建一些函数时就会变得更加清晰。

我建议看一下这个链接

https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings

查看所有受支持的输入/输出绑定的完整范围

构建我们的第一个函数

好的,我们来到了最后的部分,编写一些函数。在开始之前,我们需要安装一些扩展程序来提升我们的创作体验。

设置

我们需要以下材料:

  • 因为我们将使用 C#,所以我们需要它的扩展,它应该看起来像这样

让我们开始编码

我们已经准备好编写代码了。我们将执行以下操作:

  1. 生成 Azure 函数应用
  2. 创建由 HTTP 触发的 Azure 函数

生成 Azure Function App
我们的函数需要托管在应用程序中。一个应用程序可以包含多个函数。这很简单,只需在 VS Code 中通过选择View/Command Palette或按下组合键(COMMAND + SHIFT + P如果您使用的是 Mac)打开命令面板即可。打开菜单后,输入:Azure Functions: Create new project

这将要求您提供:

  • 目录,将应用程序存储在哪个目录中,选择当前目录
  • 代码语言,它会继续询问你代码语言,选择C#
  • 触发器,然后它会询问你的第一个函数触发器,选择 HTTP 触发器
  • 函数名称,之后它会要求输入函数名称,输入Hello
  • 命名空间,然后会要求您提供一个命名空间,现在,使用Company.Function,您以后可以随时更改它
  • 访问权限,当被问及访问权限时,请选择Anonymous。安全性很重要,但在本练习中,我们将重点了解各个部分,以后可以加强安全性。

它还会询问您第一个函数的名称、语言和触发器类型。

这将为您生成一个项目。但是,一开始它会用红色字体提示存在未解决的依赖项。您可以通过点击restore项目生成完成后立即出现的弹出窗口来解决这个问题,或者在终端中,在项目根目录下输入以下命令:

点网恢复

这将恢复项目中指出的依赖关系,您很快就会看到代码再次看起来令人满意。

函数的剖析

让我们看一下Hello.cs创建应用程序时生成的文件:

// Hello.cs

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Company.Function
{
    public static class Hello
    {
        [FunctionName("Hello")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

我们需要了解的关于用 C# 编写的 Azure Functions 的大部分内容都在这段代码中。让我们从头开始看一下:

  • FunctionName,这是一个接受字符串参数的装饰器。当我们调试或以其他方式测试应用程序时,该函数将被调用。使用字符串参数是Hello为了确保该函数的最终 URL 是[someUrl]/Hello
  • 运行,这是一个任意函数名,可以叫任何名字
  • HttpTrigger,这是一个装饰器,用于装饰我们的 HttpRequest 参数req。我们从装饰器开始,将 设置AuthorizationLevelAnonymous,这意味着任何外部人员都可以访问该函数。还有其他选项,AuthorizationLevel我们将在以后的文章中探讨。
  • HTTP 动词列表get,接下来是一个由 HTTP 动词组成的参数字符串列表。我们可以看到,到目前为止,我们已经有了值post,这意味着该函数将响应这些 HTTP 动词。我们当然可以扩展列表,"get", "post", "put", "delete"但在 REST 中,每种操作类型通常只支持一个动词。
  • 路由模式,下一个值为Route。如果我们更改此值,则可以开始支持路由参数,稍后我们将展示这一点。

这就是 HttpRequest 参数的全部内容。让我们看看log类型ILogger。通过调用此参数,我们可以写入日志。日志在调试时会在本地可见,但在部署应用后也会在门户中可见。

好的,那么我们来看看生成的函数体:

string name = req.Query["name"];

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;

return name != null
    ? (ActionResult)new OkObjectResult($"Hello, {name}")
    : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
Enter fullscreen mode Exit fullscreen mode

第一行告诉我们如何解析查询参数。我们使用Query带有字符串参数的 来实现。例如,如果您的 URL 如下所示,则url/Hello?name='some value'变量name将保存值some value

接下来,我们使用 StreamReader 读取 Body,注意关键字 的使用await。注意,函数头开头的 this 是如何与async关键字匹配的。接下来的一行是调用JsonConvert.DeserializeObject并将其赋值给dynamic。在下一行,我们尝试读取nameBody,前提是 Body 非空。最后,我们使用或 来返回200响应OkObjectResult401BadRequestObjectResult

运行函数

接下来,我们将使用 VS Code 调试运行该函数,并尝试 GET 和 POST 操作。转到菜单并选择Debug /Start Debugging“这将编译您的应用程序并启动运行时”。完成后,您应该会看到以下内容:

转到浏览器并输入指示的 URL http://localhost:7071/api/hello

这段代码显然不尽如人意,因为我们没有提供查询参数,也没有提交数据主体。我们试试添加查询参数?name=chrishttp://localhost:7071/api/hello?name=chris输入以下内容:

一切都好!

让我们设置一个断点,然后重新加载浏览器:

正如您在上面看到的,断点被击中,我们能够像往常一样检查和调试我们的代码,很好:)

我们已经成功实现了 GET 调用,那么 POST 调用呢?打开你常用的 REST 客户端,cURL 还是 Advanced Rest Client,你自己决定。然后创建一个如下请求:

然后查看断点是如何被触发的,如下所示:

构建 REST API

我们想做的不仅仅是一个简单的函数,也就是生成一个 REST API。我们不会使用数据库,而是使用一个保存状态的静态类。

为此创建一个全新的目录,并创建一个新的 Azure Function App。当系统询问方法名称时,请ProductsGet选择HttpTrigger

然后使用命令面板创建以下方法:

  • 产品创建
  • 产品获取

为了生成这些方法,我们打开命令面板,这次我们选择Azure Functions: Create Function。选择HttpTrigger并命名。此时,您的项目应该如下所示:

现在怎么办?

我们将按顺序介绍每个生成的类,但首先让我们创建保存数据库的类Db.cs

创建数据库

这只是一个保存我们数据的简单类,但在以后的文章中,我们将用实际的数据库取代它。

// Db.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace Company {
  public class Product
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }
  public static class Db {

    private static List<Product> products = new List<Product>(){
        new Product(){ Id = 1, Name= "Avengers End Game" },
        new Product(){ Id = 2, Name= "Wonder Woman" }
    };

    public static IEnumerable<Product> GetProducts() 
    {
      return products.AsEnumerable();
    }

    public static Product GetProductById(int id)
    {
      return products.Find(p => p.Id == id);
    }

    public static Product CreateProduct(string name)
    {
      var newProduct = new Product(){ Id = products.Count + 1, Name = name}; 
      products.Add(newProduct);
      return newProduct;
    }
  } 
}
Enter fullscreen mode Exit fullscreen mode

正如您在上面看到的,我们创建了类型Product和方法GetProductsGetProductById()并且CreateProduct()

产品列表

这将返回产品列表并且看起来非常简单:

// ProductsGet.cs

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Company.Function
{    
    public static class ProductsGet
    {
        [FunctionName("ProductsGet")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var json = JsonConvert.SerializeObject(new{
                products = Db.GetProducts()
            });

            return (ActionResult)new OkObjectResult(json);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

值得评论一下我们如何创建响应:

var json = JsonConvert.SerializeObject(new{
    products = Db.GetProducts()
});
Enter fullscreen mode Exit fullscreen mode

我们在这里要做的是创建一个如下所示的 JSON 响应:

{
  "products": [/* list of products*/]
}
Enter fullscreen mode Exit fullscreen mode

还值得注意的是我们如何将允许的 HTTP 动词限制为仅有get

获取特定产品

现在,这是关于查询特定产品的。我们将通过创建类似这样的路由来实现url/ProductGet/{productid}。我们的想法是,通过输入例如来查找特定产品url/ProductGet/1。让我们看一下实现:

// ProductGet.cs

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Company.Function
{
    public static class ProductGet
    {
        [FunctionName("ProductGet")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ProductsGet/{id:int}")] HttpRequest req,
            int id,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var product = Db.GetProductById(id);
            var json = JsonConvert.SerializeObject(new {
                product = product
            });

            return (ActionResult) new OkObjectResult(json);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

特别注意我们在HttpTrigger装饰器中如何将 的值设置RouteProductsGet/{id:int}。这是一种模式,意味着我们将响应类似 的请求。我们只需输入 ,就能从路由中url/ProductsGet/1解析出其他内容。然后,我们只需通过以下代码筛选出匹配的产品,并创建 JSON 响应:idint id

var product = Db.GetProductById(id);
var json = JsonConvert.SerializeObject(new {
    product = product
});
Enter fullscreen mode Exit fullscreen mode

创建产品

在本例中,我们将 HTTP 动词限制为post。我们还将从正文中读取并解析出name。代码如下所示:

// ProductCreate.cs

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Company.Function
{
    public static class ProductCreate
    {
        [FunctionName("ProductCreate")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = null;

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            log.LogInformation("request body", requestBody);
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            if(name != null) 
            {
                log.LogInformation("name", name);
                var product = Db.CreateProduct(name);
                var json = JsonConvert.SerializeObject(new{
                    product = product
                });
                return (ActionResult)new OkObjectResult(json);
            } 
            else {
                return new BadRequestObjectResult("Missing name in posted Body");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

这里有趣的部分是我们如何使用以下代码解析出 Body 信息:

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
log.LogInformation("request body", requestBody);
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
Enter fullscreen mode Exit fullscreen mode

除此之外,代码非常简单,并检查nameBody 中是否提供了,如果是,则继续创建它,最后创建 JSON 响应。

概括

好的,我们取得了一些进展。我们理解了选择无服务器的原因,并能够创建两个不同的 Azure Function 应用。一个比较简单,展示了处理查询参数和主体等概念;另一个示例展示了一个接近生产代码的 REST API。

这是第一篇关于无服务器和 C# 的文章,希望您现在已经掌握了基础知识。我们只是触及了皮毛。真正的价值在于无服务器框架能够设置不同的触发器、输入和输出绑定,并与 Azure 上的许多其他服务进行交互。

文章来源:https://dev.to/azure/how-you-can-create-a-serverless-api-in-c-and-net-1ie
PREV
Kubernetes 初学者术语概述
NEXT
如何快速构建一个带有绑定和数据库的无服务器 C# .Net Core API