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. [loj3274]变色龙之恋

    首先有一个暴力的做法,将任意两个点判断,可以得到与之相关的1或3只变色龙:1只是两只变色龙相互喜欢,那么剩下那只就是颜色相同:3只从3只选2只并和自己判断一次,结果为1的那次剩下的那个就是他喜欢的,然 ...

  2. spring-boot spring-MVC自动配置

    Spring MVC auto-configuration Spring Boot 自动配置好了SpringMVC 以下是SpringBoot对SpringMVC的默认配置:==(WebMvcAuto ...

  3. R包customLayout比例拼图

    一个简单的需求: 拼接两个图,一行两列,但不要一样大,让主图占的比例大些(如2/3),另一个图小一些(如1/3) 如上,我想突出曼哈顿图. R相关的拼图函数及包: 基础函数如par(mar =c(3, ...

  4. R数据科学-2

    R数据科学(R for Data Science) Part 2:数据处理 导入-->整理-->转换 ------------------第7章 使用tibble实现简单数据框------ ...

  5. mysql—将字符型数字转成数值型数字

    今天写sql语句时,相对字符串类型的数字进行排序,怎么做呢? 需要先转换成数字再进行排序 1.直接用加法 字符串+0 eg: select * from orders order by (mark+0 ...

  6. Java 读取TXT文件的多种方式

    1).按行读取TXT文件package zc;import java.io.BufferedReader;import java.io.File;import java.io.FileNotFound ...

  7. 阿里云NAS性能测试

    测试方法:根据阿里云NAS官方文档进行测试 测试对象:性能型NAS,总容量1PB,已使用27.49GB(计算吞吐量时按30GB计算) 随机读IOPS测试 测试命令 fio -numjobs=1 -io ...

  8. Label -- 跳出循环的思路

    let num = 0 ; outPoint: //label for (let i = 0; i < 10; i++) { for ( let j = 0; j < 10; j++) { ...

  9. javaSE中级篇2 — 工具类篇 — 更新完毕

    1.工具类(也叫常用类)-- 指的是别人已经写好了的,我们只需要拿来用就行了 官网网址:Overview (Java Platform SE 8 ) (oracle.com) ---- 但是这个是英文 ...

  10. day27 网络编程

    1.OSI七层协议 1.七层划分为:应用层,表示层.会话层.传输层.网络层.数据链路层.物理层 2.五层划分:应用层.传输层.网络层.数据链路层.物理层 应用层: 表示层: 会话层: 传输层:四层交换 ...