Code Repo:

https://github.com/Asinta/ServerlessApp_NetconfChina2020

Prerequisites

What are we going to build?

We are going to build an azure functions app from zero. In this app, we will add two functions. One is used for fetching feeds from .NET Blog and send results into a queue. The second one is used to receive those feeds from the queue and send an email to inform myself to the new feeds posted.

During this demo, we are going to explore several techniques such as:

  • how to create an Azure Functions App using azure cli;
  • how to create functions to respond to certain triggers and binding to certain output using .net core;
  • how to configure app settings to keep code clean;
  • how to retrieve secrets from Azure KeyVault service;
  • how to publish local project to Azure;
  • how to monitor your app running.

So let's get started.

Overview

We are going to call this Azure Functions App AggregateDemo, and in order to show you the most important part of this workflow, I will keep business logic as simple as possible.

Basically, we are going to work through following steps:

  1. using azure cli to create basic resources to hold our app;
  2. using azure function core tools to init and create local functions;
  3. coding for first function: triggered by time, output to queue;
  4. local debugging;
  5. coding for second function: triggered by queue, send email to myself;
  6. refactor our code using app settings;
  7. get access to Azure KeyVault;
  8. publish our app;
  9. monitor app logstream.

1. Create Azure Functions App

We are going to need a resource group to hold our resources during this workshop, as well as a storage account to hold our code and data, at last, we are going to create the Azure Functions App.

# NOTE: Prerequest
# func --version > 3.x
# az --version > 2.4
# dotnet --list-sdks = 3.1.x # NOTE: make sure you have already login to azure using `az login`
# az login # 1. Create resource group
az group create --name xanetcommunity-rg --location eastasia # 2. Create storage account
az storage account create --name xanetcommunitysa --resource-group xanetcommunity-rg # 3. Create Azure Functions App
az functionapp create \
--name AggregateDemo \
--resource-group xanetcommunity-rg \
--storage-account xanetcommunitysa \
--consumption-plan-location eastasia \
--runtime dotnet \
--functions-version 3

Ok, now it's time to check these resources on Azure Portal.

2. Create local project

We are going to use azure function core tools to create local project.

# func init will automatically create project folder for us.
func init AggregatorLocal --dotnet

Let's go through the created files:

  • *.csproj of course is the project file.
  • hots.json is the configuration for our project.
  • local.settings.json is the configure file for local development

If you are using Mac to develop, you should change the value of AzureWebJobsStorage in local.settings.json from origin data to the real storage account connection string, which you can find it using the following command:

az storage account show-connection-string --name xanetcommunitysa --query "connectionString"

3. Add first function: Fetch feeds from .NET Blog

In the first function, we will complete a TimeTrigger function, which will fetch the rss feeds and parse it into our custom data type.

Add function

Firstly, we are going to add a new function called NetBlogsFetcher:

# And choose TimeTrigger for this function.
func new --name NetBlogsFetcher

If we look at the codes in NetBlogsFetcher.cs file, we can see function uses attributes to point out the FunctionName, TriggerType, since we want to send data into a queue, we need a output binding to a queue.

Add queue output binding

Secondly, let's add output queue binding support for our function:

# the version here is to match the version of `Microsoft.NET.Sdk.Functions` package.
dotnet add package Microsoft.Azure.WebJobs.Extensions.Storage --version 3.0.7

then modify function signature to:

// 1. give the queue a name, it will automatically create this queue for us.
// 2. tell which storage account this function will use, default is the value of `AzureWebJobsStorage`.
// 3. use ICollector<T> type instance to operate with, type T is because we don't know the type of data we are going to send into the queue, will fix this later.
public static void Run(
[TimerTrigger("0 */5 * * * *")]TimerInfo myTimer,
[Queue("netblogs"), StorageAccount("AzureWebJobsStorage")] ICollector<T> queue,
ILogger log)

Implement fetching data from rss

Now let's create a folder called NetBlogsClient and create two new files in it: NetBlog.cs and NetBlogsClient.cs:

In NetBlog.cs file, since we don't know the data structure of the feed, just define an empty class here:

public class NetBlog
{ }

In NetBlogsClient.cs file, we create a class to do the fetching work:

using System.Collections.Generic;
using System.Xml; public class NetBlogsClient
{
public ICollection<NetBlog> Blogs { get; set; }
public string RssUrl { get; set; } public NetBlogClient(string rssUrl)
{
RssUrl = rssUrl;
Blogs = new HashSet<NetBlog>();
} public void FetchFeedsFromNetBlog()
{
using var xmlReader = XmlReader.Create(RssUrl);
// How to load rss feeds?
}
}

For loading feeds, we will use the following package:

dotnet add package System.ServiceModel.Syndication --version 5.0.0

Now we can implement FetchFeedsFromNetBlog as following:

public void FetchFeedsFromNetBlog()
{
using var xmlReader = XmlReader.Create(RssUrl);
var feeds = SyndicationFeed.Load(xmlReader); // what data structure is feeds?
}

We will start our local debugging to check the content of feeds to implement our data model:

public class NetBlog
{
public string Title { get; set; }
public string Summary { get; set; }
public DateTimeOffset PubDate { get; set; }
public Uri Uri { get; set; }
}

The final FetchFeedsFromNetBlog is like this:

public void FetchFeedsFromNetBlog()
{
using var xmlReader = XmlReader.Create(RssUrl);
var feeds = SyndicationFeed.Load(xmlReader); foreach (var feed in feeds.Items)
{
Blogs.Add(new NetBlog
{
Title = feed.Title.Text,
Summary = feed.Summary.Text,
PubDate = feed.PublishDate,
Uri = feed.Links[0].Uri
});
}
}

Send data into queue

In order to send data into queue, we simply use queue.Add(T instance) method is enough. The Add method will do the serialization work for us, so please make sure what you are going to put in the queue is json serializable. So we will create a new data type to do it since ICollection type is not json serializable:

public class NetBlogsContainer
{
public int Count { get; set; }
public ICollection<NetBlog> Blogs { get; set; } public NetBlogsContainer(int count, ICollection<NetBlog> blogs)
{
Count = count;
Blogs = blogs;
}
}

Following is our final Run method:

[FunctionName("NetBlogFetcher")]
public static void Run(
[TimerTrigger("0 */5 * * * *")]TimerInfo myTimer,
[Queue("netblogs"), StorageAccount("AzureWebJobsStorage")] ICollector<NetBlogsContainer> queue,
ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); var rssUrl = "https://devblogs.microsoft.com/dotnet/feed/"; var blogClient = new NetBlogClient(rssUrl);
blogClient.FetchFeedsFromNetBlog(); var blogsContainer = new NetBlogsContainer(blogClient.Blogs.Count, blogClient.Blogs);
queue.Add(blogsContainer); log.LogInformation($"fetching from rss execution end.");
}

Let's hit F5 to see if data has been successfully sent into the queue called netblogs. Great!

4. Add second function: receiving data from the queue and send email to myself

Add function

This time will create a function which has QueueTrigger:

# And choose QueueTrigger for this function.
func new --name EmailSender

Then modify the function signature:

// use your queue name and custom data type.
public static void Run(
[QueueTrigger("netblogs", Connection = "AzureWebJobsStorage")] NetBlogsContainer blogsContainer,
ILogger log)
{
log.LogInformation($"C# Queue trigger function processed with {blogsContainer.Count} feeds!");
var blogs = blogsContainer; // how to send emails?
}

Send email

We have some options here: either we can use System.Net.Mail package, or we can use SendGrid service, which is also an optional output binding for function. But I've got some issues with my account, so in this demo, we simply use the first choice with creating a file EmailClient.cs in a new foler EmailClient.

using System.Net;
using System.Net.Mail;
using Microsoft.Extensions.Logging; public class EmailClient
{
private readonly ILogger _logger; public string From { get; set; }
public string To { get; set; }
public string SmtpServer { get; set; }
public int SmtpPort { get; set; }
public string Username { get; set; }
public string Password { get; set; } public EmailClient(
string from,
string to,
string smtpServer,
int smtpPort,
string username,
string password,
ILogger logger)
{
From = from;
To = to;
SmtpServer = smtpServer;
SmtpPort = smtpPort;
Username = username;
Password = password;
_logger = logger;
} public void SendEmail(string subject, string body)
{
using var mailMessage = new MailMessage(From, To)
{
Subject = subject,
Body = body,
IsBodyHtml = true,
Priority = MailPriority.High
}; var mailSender = new SmtpClient(SmtpServer, SmtpPort)
{
EnableSsl = true,
Credentials = new NetworkCredential(Username, Password)
}; try
{
mailSender.Send(mailMessage);
}
catch (System.Exception ex)
{
_logger.LogError($"Send mail with error: {ex.Message}");
}
}
}

Now let's figure out what's in our Run method:

[FunctionName("EmailClient")]
public static void Run(
[QueueTrigger("netblogs", Connection = "AzureWebJobsStorage")] NetBlogsContainer blogsContainer,
ILogger log)
{
log.LogInformation($"C# Queue trigger function processed with {blogsContainer.Count} feeds!"); var blogs = blogsContainer; var fromEmail = "YOUR_SENDER_EMAIL_ADDRESS";
var toEmail = "YOUR_RECEIVE_EMAIL_ADDRESS"; // since I'm using hotmail, so find the smtp server/port from outlook website.
var smtpServer = "smtp-mail.outlook.com";
int smtpPort = 587; var username = fromEmail;
var password = "YOUR_SENDER_EMAIL_LOGIN_PASSWORD"; var emailClient = new EmailSender(fromEmail, toEmail, smtpServer, smtpPort, username, password, log);
emailClient.SendEmail("New feeds coming!", blogs.ToString()); log.LogInformation("Email Sent!");
}

We also need to do some modification to our data model to let blogs.ToString() work:

// in `NetBlog` class
public override string ToString()
{
return $"<h2>{Title}</h2><br><a href={Uri}>{Uri}</a><br><p>{Summary}</p><br><p>{PubDate}</p><br/>";
} // in `NetBlogsContainer` class
public override string ToString()
{
var content = new StringBuilder();
foreach (var item in Blogs)
{
content.AppendLine(item.ToString());
content
}
return $"<h2>{Count} feeds created!</h2><br>" + content.ToString();
}

If you test this project locally, you should be able to receive an email contains the new feeds. Whoo! But wait, there are some unconfortable codes here.

First is we don't want our password to be written like this, secondly we have a lot of configuration items hard-coding here, I want to get rid of them.

5. Refactor

Separate app settings from hard-coding

We will use Azure Functions App Settings to hold our configuration items values.

az functionapp config appsettings set \
--name AggregateDemo \
--resource-group xanetcommunity-rg \
--settings \
"NET_BLOG_RSS_URL=https://devblogs.microsoft.com/dotnet/feed/" \
"EMAIL_FROM=YOUR_SENDER_EMAIL_ADDRESS" \
"EMAIL_TO=YOUR_RECEIVE_EMAIL_ADDRESS" \
"HOTMAIL_SMTP_SERVER=YOUR_SMTP_SERVER_ADDRESS" \
"HOTMAIL_SMTP_PORT=YOUR_SMTP_SERVER_PORT"

Check on Azure portal we can see these settings have been successfully created. So how can we get them in code:

// in `NetBlogsFetcher.cs`
var rssUrl = System.Environment.GetEnvironmentVariable("NET_BLOG_RSS_URL", EnvironmentVariableTarget.Process); // in `EmailSender.cs`
var from = System.Environment.GetEnvironmentVariable("EMAIL_FROM", EnvironmentVariableTarget.Process);
var to = System.Environment.GetEnvironmentVariable("EMAIL_TO", EnvironmentVariableTarget.Process);
var smtpServer = System.Environment.GetEnvironmentVariable("HOTMAIL_SMTP_SERVER", EnvironmentVariableTarget.Process);
var smtpPort = System.int.Parse(Environment.GetEnvironmentVariable("HOTMAIL_SMTP_PORT", EnvironmentVariableTarget.Process));
var username = from

Now let's deal with our password.

Using Azure KeyVault to store your sensitive data and use in Azure Functions App

Firstly let's create a KeyVault in our current resource group:

az keyvault create --name "xanetcommunity-kv" --resource-group "xanetcommunity-rg"

Secondly add our senstive data as secrets:

az keyvault secret set --vault-name "xanetcommunity-kv" --name "hotmailPassword" --value <YOUR_PASSWORD>

Thirdly we need to turn on managed identities for our Functions App:

az webapp identity assign --resource-group xanetcommunity-rg --name AggregateDemo

Then we will add access policy for our app:

# get appId first
az ad sp list --display-name "AggregateDemo" --query "[0].appId" # authorize actions to the secrets form app
az keyvault set-policy \
--name "xanetcommunity-kv" \
--spn <YOUR_APP_ID> \
--secret-permissions get list

Then configure secrets as app settings for our app:

# get secret's uri
az keyvault secret show --vault-name "xanetcommunity-kv" --name hotmailPassword --query "id" # configure secret as app settings
az functionapp config appsettings set \
--name AggregateDemo \
--resource-group xanetcommunity-rg \
--settings HOTMAIL_PASSWORD="@Microsoft.KeyVault(SecretUri=YOUR_SECRET_URI)"

Finally you can use this value the same way as app settings:

var password = System.Environment.GetEnvironmentVariable("HOTMAIL_PASSWORD", EnvironmentVariableTarget.Process);

5. Publish and demo time

Publish local project to Azure is easy with Azure Function Core Tools cli, but before you publish your local project, please consider whether you want to change your cron job frequency.

func azure functionapp publish AggregateDemo

6. Monitor

You can check logstream either in console or in browser using:

func azure functionapp logstream AggregateDemo [--browser]

7. Clean up

Since our resources all resides in xanetcommunity-rg group, simply delete it:

az group delete --name xanetcommunity-rg

Conclusion

This is the most simple way to use Azure Functions App, you can explore more exciting and interesting things with it! Check out on https://docs.microsoft.com/en-us/azure/azure-functions/ and build your very first functions app!

WARNING: DO NOT BREAK ANY LAW!

使用Azure Functions & .NET Core快速构建Serverless应用的更多相关文章

  1. [外包]!采用asp.net core 快速构建小型创业公司后台管理系统(六.结语)

    到这里就结束了,真的结束了,源码会在文末分享! 另外录了两个视频,对这个系统进行了演示! 做有意义的事情,原此生无悔! 视频地址:使用asp.net core 快速构建权限管理模块1 使用asp.ne ...

  2. Mysql EF Core 快速构建 Web Api

    (1)首先创建一个.net core web api web项目; (2)因为我们使用的是ef连接mysql数据库,通过NuGet安装MySql.Data.EntityFrameworkCore,以来 ...

  3. 利用Azure Functions和k8s构建Serverless计算平台

    题记:昨晚在一个技术社区直播分享了"利用Azure Functions和k8s构建Serverless计算平台"这一话题.整个分享分为4个部分:Serverless概念的介绍.Az ...

  4. Azure Functions(一)什么是 ServerLess

    一,引言 自去年4月份分享过3篇关于 Azure Functions 的文章之后,就一直没有再将 Azure Functions 相关的内容了.今天再次开始将 Azure Functions 相关的课 ...

  5. 利用京东云Serverless服务快速构建5G时代的IoT应用

    10月31日,在2019年中国国际信息通信展览会上,工信部宣布:5G商用正式启动.5G商用时代来了! 5G的商用,使得数据传输速度.响应速度.连接数据.数据传输量.传输可靠性等方面都有了显著的提升,这 ...

  6. 从零入门 Serverless | 教你 7 步快速构建 GitLab 持续集成环境

    作者 | 存诚 阿里云弹性计算团队 本文整理自<Serverless 技术公开课>,"Serverless"公众号后台回复"入门",即可获取系列文章 ...

  7. 如何在ASP.NET Core 中快速构建PDF文档

    比如我们需要ASP.NET Core 中需要通过PDF来进行某些简单的报表开发,随着这并不难,但还是会手忙脚乱的去搜索一些资料,那么恭喜您,这篇帖子会帮助到您,我们就不会再去浪费一些宝贵的时间. 在本 ...

  8. Azure Functions + Azure Batch实现MP3音频转码方案

    客户需求 客户的环境是一个网络音乐播放系统,根据网络情况提供给手机用户收听各种码率的MP3歌曲,在客户没购买歌曲的情况下提供一个三十秒内的试听版本.这样一个系统非常明确地一个需求就是会定期需要将一批从 ...

  9. 春节前“摸鱼”指南——SCA命令行工具助你快速构建FaaS服务

    春节将至,身在公司的你是不是已经完全丧失了工作的斗志? 但俗话说得好:"只要心中有沙,办公室也能是马尔代夫." 职场人如何才能做到最大效能地带薪"摸鱼",成为了 ...

随机推荐

  1. [atAGC051D]C4

    考虑将两次移动作为一个整体,两次移动的效果分为:$s-u$.$u-s$和原地不动 对于从$s$回到$s$路径,必然有前两种效果使用次数相同,假设都为$i$(枚举),那么原地不动的次数$j=\frac{ ...

  2. WebRTC从摄像头获取图片传入canvas

    WebRTC从摄像头获取图片传入canvas 前面我们已经能够利用WebRTC的功能,通过浏览器打开摄像头,并把预览的图像显示在video元素中. 接下来我们尝试从视频中截取某一帧,显示在界面上. h ...

  3. nginx反向代理出错:proxy_pass

    问题描述: 一台服务器代理访问另一台服务器,代码如下图所示: 重新加载nginx后不会跳到该域名,而是出现error的页面. 查看error.log日志为以下报错: 2021/03/09 23:07: ...

  4. 常见HTTP请求错误码

    一些常见的状态码为: 200 - 服务器成功返回网页404 - 请求的网页不存在503 - 服务不可用详细分解: 1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态代码. 代码  说明100 ...

  5. Linux-root管理员创建新用户

    Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统.用户的账号一方面可以帮助系统管理员对使用系统的用户进行 ...

  6. 非线性回归支持向量机——MATLAB源码

    支持向量机和神经网络都可以用来做非线性回归拟合,但它们的原理是不相同的,支持向量机基于结构风险最小化理论,普遍认为其泛化能力要比神经网络的强.大量仿真证实,支持向量机的泛化能力强于神经网络,而且能避免 ...

  7. 『学了就忘』Linux文件系统管理 — 62、手动分配swap分区

    目录 1.查看swap分区情况 2.手动修改swap分区 3.格式化swap分区 4.使用swap分区 5.配置swap分区开机之后自动挂载 1.查看swap分区情况 swap分区就相当于是内存的一个 ...

  8. C语言中的重要位运算

    1. 常用的等式 :-n = ~(n-1) = ~n + 1. 2. 获取整数n的人进制形式中的最后1个,也就是只保留最后一个1,其余的全部置位0,如1000 0011 --->  0000 0 ...

  9. C语言中的字节对齐

    下面这个篇博客讲解很好 http://blog.csdn.net/meegomeego/article/details/9393783 总的来看分三类: 1. 不加 #pragma pack(n)伪指 ...

  10. 日常Java 2021/11/3

    java网络编程 网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来.java.net包中J2SE的APl包含有类和接口,它们提供低层次的通信细节.你可以直接使用这些类和接口, ...