点击上方蓝字关注“汪宇杰博客”
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/85a723c01fbc0cd629061e6298dd2bbc.png)
导语
Azure Function 是一个事件驱动型无服务器计算平台,可以解决复杂的业务流程问题,更加高效地进行开发。在本地构建和调试,而无需额外的设置,在云中大规模部署和操作,并使用触发器和绑定集成服务。对我来说,Function 能帮助我快速开发一些简单的API,我只需要编写业务代码,而无需构建完整的应用框架等基础代码。本文将会讲解如何使用.NET Core,C#语言,开发一个简单的 http 触发的 Function 应用,并完成 GitHub 的持续部署配置。
需求
我需要一个 API,用于返回客户端 IP 以及 User-Agent 字符串。但是我不想为了做这么一件简单的事,而创建一个完整的 ASP.NET Core Web API 项目。我只关心我要进行的业务逻辑,不想编写和管理配置文件、依赖注入、Controller、验证等基础代码。
准备开发环境
我们需要以下开发条件:
卖血买到的 Azure 订阅
丐版 Visual Studio 2019 Community 或豪版 Enterprise
Azure development workload
免费开源的 .NET Core 2.1 / 2.2 SDK
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/017784b1fc79e712d2ef2745df18082c.png)
创建 Function 应用
在 VS2019 里,选择 Azure 分类下的 Azure Function,新建一个工程。如:Edi.AzureFunctions
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/827fc4d90bdccf638585d6cdd141b870.png)
默认的.NET Core版本为2.1,我们可以手工改成2.2。编辑 Edi.AzureFunctions.csproj文件,将 TargetFramework 改成 netcoreapp2.2
同样,也可以将 Microsoft.NET.Sdk.Functions 升级到最新版。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AzureFunctionsVersion>v2</AzureFunctionsVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.29" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
将默认类改名为 ClientInfoFuncion,完成我们的业务代码:取客户端IP及User-Agent
public static class ClientInfoFuncion
{
[FunctionName("IP")]
public static IActionResult GetClientIp(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("Requesting client IP.");
var ip = req.HttpContext.Connection.RemoteIpAddress.ToString();
return ip != null
? (ActionResult)new OkObjectResult($"{ip}")
: new BadRequestObjectResult("ip is null");
}
[FunctionName("UserAgent")]
public static IActionResult GetClientUserAgent(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("Requesting client User-Agent.");
var ua = req.Headers["User-Agent"].ToString();
return ua != null
? (ActionResult)new OkObjectResult($"{ua}")
: new BadRequestObjectResult("user-agent is null");
}
}
可以发现,Azure Function 的代码非常类似 ASP.NET Core MVC / Web API,返回类型正是 Microsoft.AspNetCore.Mvc.IActionResult,然而我们无需关心如何构建一个完整的ASP.NET Core Web API工程,也无需知道 Controller 的存在,只要编写业务逻辑即可。
FunctionName 可以理解为 ASP.NET Core 中的 Route,也就是最终用户调用 API 的 endpoint 地址。
[FunctionName("IP")] 也就最终对应:
https://你的function地址/api/IP
HttpTrigger 是 Azure Function 的一种触发器,表示通过HTTP请求,触发你写的业务逻辑。此处我允许匿名访问,并限定为 get / post 两种HTTP Verb。
ILogger 接口中的日志最终会输出到 Azure Function 的后台,用于调试。
按 F5 启动本地调试。会看见一个很炫酷的命令行窗口,里面启动的是本地的 Azure Function 模拟器。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/267cbadd54cac4bdd53dc3d4c81c8709.png)
模拟器准备完成后,控制台会输出两个绿色的URL地址,用于本地测试。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/e0b5c4b60cb0d1bb9a7f69a9a093bbc6.png)
复制URL到浏览器,完成测试。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/d7cc13a10cfe2959cb700eda05c56daa.png)
从 VS 创建和部署 Azure Function
在 Edi.AzureFunctions 点击右键,选择 Publish
点击 Start,选择 Azure Functions Consumption Plan,然后选择 Create New。如果你已经在 Azure 上创建过一个 Function 应用,则可选择 Select Existing。关于如何从 Azure portal 创建一个 Function 应用,在稍后讨论。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/cb37f98c7f7da4f70198ec1d35d6a2b7.png)
根据需要填写参数
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/01e5e615424a0cf186408c30f75b8167.png)
最后点击 Publish 完成发布
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/4eadbf336767f786cb69e13ab30f77a9.png)
如果发布成功,你可以在 Azure portal 里找到这个 Function 应用,并能看见其 URL
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/e6171ec0e483ca873992e3119c3f3d5a.png)
尝试在浏览器中访问两个API,分别是:
https://你的function名称.azurewebsites.net/api/ip
https://你的function名称.azurewebsites.net/api/useragent
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/a66d51aa1d55871d635198b23950c88f.png)
你也可以在 Functions 下进入具体的 HttpTrigger,并在 Azure Portal 里完成测试。这会输出更详细的日志信息。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/85b88bfed1e74d1db84042fd1b93a9e4.png)
在 Azure Portal 创建 Function
刚才的例子里,我们通过 VS2019 一条龙开发和部署了一个Azure Function,而在实际应用中,通常不会这样操作。因为 Azure Portal 给我们提供了更细致的参数选项,以便根据自己的需求调整。要从 Azure Portal 创建一个空的Function 应用也十分简单,点击 Function App / Add
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/a085cfb7b21494be85bd8ec933129e6b.png)
输入 App name,它将作为该function默认域名使用。
其他参数根据自己需要设置。我建议大家重用既有的 App Service Plan,这样可以省钱。
Runtime Stack 选择 .NET Core
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/57646d6a08d212f0b782f172e301cd97.png)
从 GitHub 仓库持续部署
我在之前的博客文章中介绍过了 Azure DevOps 的CI/CD流程,非常强大。但是本文给大家介绍一个更简单,但是略为基础的发布方式。
实际上 Function 的本质是对 App Service 的进一步包装,所以包括部署在内的大部分 App Service 的功能这里也能用。在 Platform features 里进入 Deployment Center
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/0091277786137a8690235208a59b1f42.png)
代码来源选择 GitHub
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/601e824b10b48176950f807584da07d5.png)
选择 Kudu 编译
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/d911bd7f6cb3e9f9bdc18cee6bb2b1de.png)
我已将本文的代码上传到 https://github.com/EdiWang/Edi.AzureFunctions
在 Azure 里选择对应的仓库以及分支,并完成配置。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/e0d8019e8973660d8a76ce92f6d05680.png)
完成配置后,会立即触发一次部署,可以看到详细日志。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/1f31e6cb9b3a5ae36f18a283fc9e9800.png)
部署成功后,刷新左边的 Functions 就能看见我们的两个 API 了
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/aa10e248077cea5bad3850c1ff12df5f.png)
如果你是个土豪,有自己的域名和证书,也可以绑定自定义域名。就像我这样:
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/7a8276a44faa754ce35c0e9fc67ff23a.png)
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/dff15acdddbe8cc4fcea01d805f7846a.png)
最后,就能优雅的通过自己的域名访问 Function 了!
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/713786539b585194864b85bdf07f080d.png)
![640?wx_fmt=gif](https://i-blog.csdnimg.cn/blog_migrate/f3fb702a7f93063d3cb2371599bae935.gif)