点击上方蓝字关注“汪宇杰博客”
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/c62f9c48e535a99fcced696618b19e67.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://img-blog.csdnimg.cn/img_convert/2f0342e8790641205f96dc92b2c65682.png)
创建 Function 应用
在 VS2019 里,选择 Azure 分类下的 Azure Function,新建一个工程。如:Edi.AzureFunctions
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/1cb8a6b71c233b742d6a6890db94bc1b.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://img-blog.csdnimg.cn/img_convert/ab175366a6822caa87d96f38345e95d4.png)
模拟器准备完成后,控制台会输出两个绿色的URL地址,用于本地测试。
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/6841863913236044e4fc9354bc6e7912.png)
复制URL到浏览器,完成测试。
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/1534aeffbc9712bf05de886c60744de7.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://img-blog.csdnimg.cn/img_convert/19d2105000249bed8c91bdbaeae7b3de.png)
根据需要填写参数
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/8de89065a895b3f4fd213af18a3ef6f4.png)
最后点击 Publish 完成发布
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/2fddcf1196b51988824bd96279f52103.png)
如果发布成功,你可以在 Azure portal 里找到这个 Function 应用,并能看见其 URL
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/4157aab53b55d2e3a8448275555e8d31.png)
尝试在浏览器中访问两个API,分别是:
https://你的function名称.azurewebsites.net/api/ip
https://你的function名称.azurewebsites.net/api/useragent
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/7ff7c8991cec32a649b80bf53868ff52.png)
你也可以在 Functions 下进入具体的 HttpTrigger,并在 Azure Portal 里完成测试。这会输出更详细的日志信息。
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/1b62e5619959f1197d7bca11ea5d20e4.png)
在 Azure Portal 创建 Function
刚才的例子里,我们通过 VS2019 一条龙开发和部署了一个Azure Function,而在实际应用中,通常不会这样操作。因为 Azure Portal 给我们提供了更细致的参数选项,以便根据自己的需求调整。要从 Azure Portal 创建一个空的Function 应用也十分简单,点击 Function App / Add
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/5e5a2e5a416199b6583c627d24b3e62f.png)
输入 App name,它将作为该function默认域名使用。
其他参数根据自己需要设置。我建议大家重用既有的 App Service Plan,这样可以省钱。
Runtime Stack 选择 .NET Core
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/3856af25bbb986afee8c3bf7285a76b9.png)
从 GitHub 仓库持续部署
我在之前的博客文章中介绍过了 Azure DevOps 的CI/CD流程,非常强大。但是本文给大家介绍一个更简单,但是略为基础的发布方式。
实际上 Function 的本质是对 App Service 的进一步包装,所以包括部署在内的大部分 App Service 的功能这里也能用。在 Platform features 里进入 Deployment Center
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/eb0adf135b7c3724e89ba5c718c29d25.png)
代码来源选择 GitHub
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/e82a036982807cbe102e66572122e5ce.png)
选择 Kudu 编译
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/6329951dadc2911e57cf0ce108edf3cf.png)
我已将本文的代码上传到 https://github.com/EdiWang/Edi.AzureFunctions
在 Azure 里选择对应的仓库以及分支,并完成配置。
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/eb7b2e43ed6b08795f73a63bea21acde.png)
完成配置后,会立即触发一次部署,可以看到详细日志。
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/704c9f230950f68b86387403171abef5.png)
部署成功后,刷新左边的 Functions 就能看见我们的两个 API 了
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/5589bd568289a656923cf64f463959aa.png)
如果你是个土豪,有自己的域名和证书,也可以绑定自定义域名。就像我这样:
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/2ba80ea5df1ec218ce702b5e6d6451de.png)
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/bb47d7434bf1f16d77afcdad395e568a.png)
最后,就能优雅的通过自己的域名访问 Function 了!
![640?wx_fmt=png](https://img-blog.csdnimg.cn/img_convert/1557e78ef8a904c999ef9530a18de5a2.png)
![640?wx_fmt=gif](https://img-blog.csdnimg.cn/img_convert/091f483ba31db460754bd37597e4505b.gif)