Azure Functions C# 脚本 (.csx) 开发人员参考

本文介绍了如何使用 C# 脚本 (.csx) 开发 Azure Functions。

Azure Functions 支持 C# 和 C# 脚本编程语言。 如需在 Visual Studio 类库项目中使用 C# 方面的指南,请参阅 C# 开发人员参考

本文假定你已阅读 Azure Functions 开发人员指南

.csx 的工作原理

Azure Functions 的 C# 脚本体验基于 Azure WebJobs SDK。 数据通过方法参数流入 C # 函数。 参数名称在 function.json 文件中指定,而预定义的名称则用于访问函数记录器和取消令牌等内容。

.csx 格式允许编写更少的“样本”,因此你可以集中编写 C# 函数。 只需定义 Run 方法即可,无需将所有内容都包装在命名空间和类中。 像往常一样,在文件开头包含任何程序集引用和命名空间。

初始化实例时,会编译函数应用的 .csx 文件。 此编译步骤意味着,与 C# 类库相比,冷启动之类的操作对于 C# 脚本函数来说可能需要更长的时间。 此编译步骤也说明了为何 C# 脚本函数在 Azure 门户中可以编辑,而 C# 类库则不可以编辑。

文件夹结构

C# 脚本项目的文件夹结构如下所示:

FunctionsProject
 | - MyFirstFunction
 | | - run.csx
 | | - function.json
 | | - function.proj
 | - MySecondFunction
 | | - run.csx
 | | - function.json
 | | - function.proj
 | - host.json
 | - extensions.csproj
 | - bin

有一个共享的 host.json 文件,可用于配置函数应用。 每个函数都具有自己的代码文件 (.csx) 和绑定配置文件 (function.json)。

2.x 版 Functions 运行时中所需的绑定扩展在 extensions.csproj 文件中定义,实际库文件位于 bin 文件夹中。 本地开发时,必须注册绑定扩展。 在 Azure 门户中开发函数时,系统将为你完成此注册。

绑定到参数

输入或输出数据通过 function.json 配置文件中的 name 属性绑定到 C# 脚本函数参数。 以下示例显示了一个 function.json 文件以及适用于队列触发函数的 run.csx 文件。 从队列消息接收数据的参数名为 myQueueItem,因为这是 name 属性的值。

{
    "disabled": false,
    "bindings": [
        {
            "type": "queueTrigger",
            "direction": "in",
            "name": "myQueueItem",
            "queueName": "myqueue-items",
            "connection":"MyStorageConnectionAppSetting"
        }
    ]
}
#r "Microsoft.WindowsAzure.Storage"

using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Queue;
using System;

public static void Run(CloudQueueMessage myQueueItem, ILogger log)
{
    log.LogInformation($"C# Queue trigger function processed: {myQueueItem.AsString}");
}

本文后面#r 语句做了解释。

绑定支持的类型

每个绑定都具有其自己支持的类型;例如,Blob 触发器可以与字符串参数、POCO 参数、CloudBlockBlob 参数或任何其他几种受支持类型之一配合使用。 适用于 Blob 绑定的绑定参考文章列出了适用于 Blob 触发器的所有受支持参数类型。 有关详细信息,请参阅触发器和绑定每个绑定类型的绑定参考文档

Tip

如果计划使用 HTTP 或 WebHook 绑定,请制定计划来避免因实例化 HttpClient 不当导致的端口耗尽现象。 有关详细信息,请参阅如何在 Azure Functions 中管理连接

引用自定义类

如需使用自定义的普通旧 CLR 对象 (POCO) 类,可以将类定义包括在同一文件中,也可以将其置于单独的文件中。

以下示例显示了一个 run.csx 示例,后者包括一个 POCO 类定义。

public static void Run(string myBlob, out MyClass myQueueItem)
{
    log.Verbose($"C# Blob trigger function processed: {myBlob}");
    myQueueItem = new MyClass() { Id = "myid" };
}

public class MyClass
{
    public string Id { get; set; }
}

POCO 类必须为每个属性定义 getter 和 setter。

重用.csx 代码

可以在 run.csx 文件中使用其他 run.csx 文件中定义的类和方法。 为此,需使用 run.csx 文件中的 #load 指令。 在下面的实例中,在 myLogger.csx 中共享了名为 MyLogger 的日志记录例程,并使用 #load 指令将其加载到 run.csx:

示例 run.csx

#load "mylogger.csx"

using Microsoft.Extensions.Logging;

public static void Run(TimerInfo myTimer, ILogger log)
{
    log.LogInformation($"Log by run.csx: {DateTime.Now}");
    MyLogger(log, $"Log by MyLogger: {DateTime.Now}");
}

示例 mylogger.csx

public static void MyLogger(ILogger log, string logtext)
{
    log.LogInformation(logtext);
}

若要使用 POCO 对象强类型化在函数间传递的数据,常见模式是使用共享的 .csx 文件。 在下面的简化示例中,一个 HTTP 触发器和队列触发器共享名为 Order 的 POCO 对象以强类型化顺序数据:

HTTP 触发器的示例 run.csx:

#load "..\shared\order.csx"

using System.Net;
using Microsoft.Extensions.Logging;

public static async Task<HttpResponseMessage> Run(Order req, IAsyncCollector<Order> outputQueueItem, ILogger log)
{
    log.LogInformation("C# HTTP trigger function received an order.");
    log.LogInformation(req.ToString());
    log.LogInformation("Submitting to processing queue.");

    if (req.orderId == null)
    {
        return new HttpResponseMessage(HttpStatusCode.BadRequest);
    }
    else
    {
        await outputQueueItem.AddAsync(req);
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
}

队列触发器的示例 run.csx:

#load "..\shared\order.csx"

using System;
using Microsoft.Extensions.Logging;

public static void Run(Order myQueueItem, out Order outputQueueItem, ILogger log)
{
    log.LogInformation($"C# Queue trigger function processed order...");
    log.LogInformation(myQueueItem.ToString());

    outputQueueItem = myQueueItem;
}

示例 order.csx:

public class Order
{
    public string orderId {get; set; }
    public string custName {get; set;}
    public string custAddress {get; set;}
    public string custEmail {get; set;}
    public string cartId {get; set; }

    public override String ToString()
    {
        return "\n{\n\torderId : " + orderId +
                  "\n\tcustName : " + custName +             
                  "\n\tcustAddress : " + custAddress +             
                  "\n\tcustEmail : " + custEmail +             
                  "\n\tcartId : " + cartId + "\n}";             
    }
}

可以使用相对路径与 #load 指令:

  • #load "mylogger.csx" 加载函数文件夹中的文件。
  • #load "loadedfiles\mylogger.csx" 加载文件函数文件夹中的文件夹。
  • #load "..\shared\mylogger.csx" 在同一级别(即 wwwroot 的正下方)加载文件夹中的文件,使其成为函数文件夹。

#load 指令仅适用于 .csx 文件,不适用于 .cs 文件。

绑定到方法返回值

可通过在 function.json 中使用名称 $return 将方法返回值用于输出绑定。 有关示例,请参阅触发器和绑定

仅当成功的函数执行始终将返回值传递给输出绑定时,才使用返回值。 否则,请使用 ICollectorIAsyncCollector,如以下部分所示。

写入多个输出值

若要将多个值写入输出绑定,或者如果成功的函数调用可能无法将任何内容传递给输出绑定,请使用 ICollectorIAsyncCollector 类型。 这些类型是只写集合,当方法完成时写入输出绑定。

此示例使用 ICollector 将多个队列消息写入到同一队列:

public static void Run(ICollector<string> myQueue, ILogger log)
{
    myQueue.Add("Hello");
    myQueue.Add("World!");
}

日志记录

若要使用 C# 将输出记录到流式处理日志中,请包括 ILogger 类型的参数。 建议将其命名为 log。 避免在 Azure Functions 中使用 Console.Write

public static void Run(string myBlob, ILogger log)
{
    log.LogInformation($"C# Blob trigger function processed: {myBlob}");
}

异步

要使函数异步,请使用 async 关键字并返回 Task 对象。

public async static Task ProcessQueueMessageAsync(
        string blobName,
        Stream blobInput,
        Stream blobOutput)
{
    await blobInput.CopyToAsync(blobOutput, 4096);
}

不能在异步函数中使用 out 参数。 对于输出绑定,请改用函数返回值收集器对象

取消令牌

函数可以接受 CancellationToken 参数,以使操作系统能够在函数即将终止时通知代码。 可以使用此通知来确保该函数不会意外终止,导致数据处于不一致状态。

下面的示例演示了如何检查即将发生的函数终止。

using System;
using System.IO;
using System.Threading;

public static void Run(
    string inputText,
    TextWriter logger,
    CancellationToken token)
{
    for (int i = 0; i < 100; i++)
    {
        if (token.IsCancellationRequested)
        {
            logger.WriteLine("Function was cancelled at iteration {0}", i);
            break;
        }
        Thread.Sleep(5000);
        logger.WriteLine("Normal processing for queue message={0}", inputText);
    }
}

导入命名空间

如果需要导入命名空间,则可使用 using 子句,按正常情况处理。

using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

public static Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)

会自动导入以下命名空间,而且是可选的:

  • System
  • System.Collections.Generic
  • System.IO
  • System.Linq
  • System.Net.Http
  • System.Threading.Tasks
  • Microsoft.Azure.WebJobs
  • Microsoft.Azure.WebJobs.Host

引用外部程序集

对于框架程序集,通过使用 #r "AssemblyName" 指令添加引用。

#r "System.Web.Http"

using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

public static Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)

由 Azure 函数主机环境自动添加以下程序集:

  • mscorlib
  • System
  • System.Core
  • System.Xml
  • System.Net.Http
  • Microsoft.Azure.WebJobs
  • Microsoft.Azure.WebJobs.Host
  • Microsoft.Azure.WebJobs.Extensions
  • System.Web.Http
  • System.Net.Http.Formatting

可通过简单名称(例如 #r "AssemblyName")引用以下程序集:

  • Newtonsoft.Json
  • Microsoft.WindowsAzure.Storage
  • Microsoft.ServiceBus
  • Microsoft.AspNet.WebHooks.Receivers
  • Microsoft.AspNet.WebHooks.Common
  • Microsoft.Azure.NotificationHubs

引用自定义程序集

若要引用自定义程序集,可使用共享程序集或私有程序集:

  • 共享程序集在函数应用内的所有函数中共享。 若要引用自定义程序集,请将程序集上传到函数应用根文件夹bin (wwwroot) 中名为 的文件夹。

  • 私有程序集是给定函数上下文的一部分,支持不同版本的旁加载。 私有程序集应上传到函数目录中的 bin 文件夹。 使用文件名(例如 #r "MyAssembly.dll")引用程序集。

有关如何将文件上传到函数文件夹的信息,请参阅有关程序包管理的部分。

监视的目录

自动监视包含函数脚本文件的目录的程序集更改。 若要监视其他目录中的程序集更改,请将其添加到 host.json 中的 watchDirectories 列表中。

使用 NuGet 包

要在 C# 函数中使用 NuGet 包,可将 extensions.csproj 文件上传到函数应用的文件系统中的函数文件夹。 下面是示例 extensions.csproj 文件,它添加了对 Microsoft.ProjectOxford.Face 1.1.0 版的引用:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net46</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.ProjectOxford.Face" Version="1.1.0" />
    </ItemGroup>
</Project>

若要使用自定义 NuGet 源,请在 Function App 根中指定“Nuget.Config”文件中的源。 有关详细信息,请参阅配置 NuGet 行为

使用 extensions.csproj 文件

  1. 在 Azure 门户中打开函数。 日志选项卡显示包安装输出。
  2. 若要上传 extensions.csproj 文件,请使用 Azure Functions 开发人员参考主题的如何更新函数应用文件中所述的方法之一。
  3. 上传完 extensions.csproj 文件后,将在函数的流日志中看到类似以下示例的输出:
2016-04-04T19:02:48.745 Restoring packages.
2016-04-04T19:02:48.745 Starting NuGet restore
2016-04-04T19:02:50.183 MSBuild auto-detection: using msbuild version '14.0' from 'D:\Program Files (x86)\MSBuild\14.0\bin'.
2016-04-04T19:02:50.261 Feeds used:
2016-04-04T19:02:50.261 C:\DWASFiles\Sites\facavalfunctest\LocalAppData\NuGet\Cache
2016-04-04T19:02:50.261 https://api.nuget.org/v3/index.json
2016-04-04T19:02:50.261
2016-04-04T19:02:50.511 Restoring packages for D:\home\site\wwwroot\HttpTriggerCSharp1\extensions.csproj...
2016-04-04T19:02:52.800 Installing Newtonsoft.Json 6.0.8.
2016-04-04T19:02:52.800 Installing Microsoft.ProjectOxford.Face 1.1.0.
2016-04-04T19:02:57.095 All packages are compatible with .NETFramework,Version=v4.6.
2016-04-04T19:02:57.189
2016-04-04T19:02:57.189
2016-04-04T19:02:57.455 Packages restored.

环境变量

若要获取环境变量或应用设置值,请使用 System.Environment.GetEnvironmentVariable,如以下代码示例所示:

public static void Run(TimerInfo myTimer, ILogger log)
{
    log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
    log.LogInformation(GetEnvironmentVariable("AzureWebJobsStorage"));
    log.LogInformation(GetEnvironmentVariable("WEBSITE_SITE_NAME"));
}

public static string GetEnvironmentVariable(string name)
{
    return name + ": " +
        System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
}

在运行时绑定

在 C# 和其他 .NET 语言中,可以使用命令性绑定模式,而不是 function.json 中的声明式绑定。 当绑定参数需要在运行时(而非在设计时)计算时,命令性绑定很有用。 通过此模式,可以在函数代码中动态绑定到受支持的输入和输出绑定。

如下所示定义命令性绑定:

  • 对于所需的命令性绑定,不要包含 function.json 中的条目。
  • 传递输入参数 Binder binderIBinder binder
  • 使用下面的 C# 模式执行数据绑定。
using (var output = await binder.BindAsync<T>(new BindingTypeAttribute(...)))
{
    ...
}

BindingTypeAttribute 是定义了绑定的 .NET 属性,T 是该绑定类型所支持的输入或输出类型。 T 不能是 out 参数类型(例如 out JObject)。 例如,移动应用表输出绑定支持 6 种输出类型,但对于 T,可仅使用 ICollectorIAsyncCollector

单属性示例

下面的示例代码使用在运行时定义的 blob 路径创建存储 blob 输出绑定,然后将字符串写入此 blob。

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host.Bindings.Runtime;

public static async Task Run(string input, Binder binder)
{
    using (var writer = await binder.BindAsync<TextWriter>(new BlobAttribute("samples-output/path")))
    {
        writer.Write("Hello World!!");
    }
}

BlobAttribute 定义存储 Blob 输入或输出绑定,TextWriter 是支持的输出绑定类型。

多属性示例

上一个示例获取函数应用的主存储帐户连接字符串(即 AzureWebJobsStorage)的应用设置。 通过添加 StorageAccountAttribute 和将属性数组传入 BindAsync<T>(),可指定要用于存储帐户的自定义应用设置。 使用一个 Binder 参数而非 IBinder。 例如:

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host.Bindings.Runtime;

public static async Task Run(string input, Binder binder)
{
    var attributes = new Attribute[]
    {    
        new BlobAttribute("samples-output/path"),
        new StorageAccountAttribute("MyStorageAccount")
    };

    using (var writer = await binder.BindAsync<TextWriter>(attributes))
    {
        writer.Write("Hello World!");
    }
}

下表列出了每种绑定类型的 .NET 属性及其定义所在的包。

绑定 属性 添加引用
Cosmos DB Microsoft.Azure.WebJobs.DocumentDBAttribute #r "Microsoft.Azure.WebJobs.Extensions.CosmosDB"
事件中心 Microsoft.Azure.WebJobs.ServiceBus.EventHubAttribute, Microsoft.Azure.WebJobs.ServiceBusAccountAttribute #r "Microsoft.Azure.Jobs.ServiceBus"
Mobile Apps Microsoft.Azure.WebJobs.MobileTableAttribute #r "Microsoft.Azure.WebJobs.Extensions.MobileApps"
通知中心 Microsoft.Azure.WebJobs.NotificationHubAttribute #r "Microsoft.Azure.WebJobs.Extensions.NotificationHubs"
服务总线 Microsoft.Azure.WebJobs.ServiceBusAttribute, Microsoft.Azure.WebJobs.ServiceBusAccountAttribute #r "Microsoft.Azure.WebJobs.ServiceBus"
存储队列 Microsoft.Azure.WebJobs.QueueAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
存储 blob Microsoft.Azure.WebJobs.BlobAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
存储表 Microsoft.Azure.WebJobs.TableAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute

后续步骤