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. 五、Zookeeper的Shell操作

    前文 一.CentOS7 hadoop3.3.1安装(单机分布式.伪分布式.分布式 二.JAVA API实现HDFS 三.MapReduce编程实例 四.Zookeeper3.7安装 Zookeepe ...

  2. WebRTC打开本地摄像头

    本文使用WebRTC的功能,打开电脑上的摄像头,并且把摄像头预览到的图像显示出来. 纯网页实现,能支持除IE外的多数浏览器.手机浏览器也可用. 引入依赖 我们需要引入adapter-latest.js ...

  3. Java安全之Axis漏洞分析

    Java安全之Axis漏洞分析 0x00 前言 看到个别代码常出现里面有一些Axis组件,没去仔细研究过该漏洞.研究记录一下. 0x01 漏洞复现 漏洞版本:axis=<1.4 Axis1.4 ...

  4. AutoHotkey

    ;注释 : #==win !==Alt  ^==Ctr  +==shift 需要注意的是不要和现有的快捷键冲突,他会代替掉原来的快捷键操作很难受的. 热指令: 比如 ::yx1::1359720840 ...

  5. 如何删除一个win10的服务

    使用场景: 之前电脑玩腾讯qq微端游戏,后来卸载残留服务一直在后台占用系统资源.那么如何关闭这个服务呢. 1.首先 管理员运行--cmd.exe 2.打开任务管理器,找到服务名称,如果服务开启可以关闭 ...

  6. 贪心/构造/DP 杂题选做

    本博客将会收录一些贪心/构造的我认为较有价值的题目,这样可以有效的避免日后碰到 P7115 或者 P7915 这样的题就束手无策进而垫底的情况/dk 某些题目虽然跟贪心关系不大,但是在 CF 上有个 ...

  7. Codeforces 710F - String Set Queries(AC 自动机)

    题面传送门 题意:强制在线的 AC 自动机. \(n,\sum|s|\leq 3\times 10^5\) 如果不是强制在线那此题就是道 sb 题,加了强制在线就不那么 sb 了. 这里介绍两种做法: ...

  8. js ajax 请求

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. windows系统 svn自动更新

    如果对svn不熟悉,当svn上面有更新时,想看到实时效果,就得去web目录手动更新,比较麻烦 其它svn有一个自动更新的功能 利用 hook   在svn 仓库目录下面有一个hook目录 在post- ...

  10. 11 — springboot集成swagger — 更新完毕

    1.前言 理论知识滤过,自行百度百科swagger是什么 2.导入依赖 <!-- swagger所需要的依赖--> <dependency> <groupId>io ...