Skip to content

Latest commit

 

History

History
561 lines (412 loc) · 24.7 KB

functions-reference-csharp.md

File metadata and controls

561 lines (412 loc) · 24.7 KB
title description ms.topic ms.custom ms.date
Azure Functions C# script developer reference
Understand how to develop Azure Functions using C# script.
conceptual
devx-track-csharp, ignite-2022
09/15/2022

Azure Functions C# script (.csx) developer reference

This article is an introduction to developing Azure Functions by using C# script (.csx).

Azure Functions lets you develop functions using C# in one of the following ways:

Type Execution process Code extension Development environment Reference
C# script in-process .csx Portal
Core Tools
This article
C# class library in-process .cs Visual Studio
Visual Studio Code
Core Tools
In-process C# class library functions
C# class library (isolated worker process) in an isolated worker process .cs Visual Studio
Visual Studio Code
Core Tools
.NET isolated worker process functions

This article assumes that you've already read the Azure Functions developers guide.

How .csx works

Data flows into your C# function via method arguments. Argument names are specified in a function.json file, and there are predefined names for accessing things like the function logger and cancellation tokens.

The .csx format allows you to write less "boilerplate" and focus on writing just a C# function. Instead of wrapping everything in a namespace and class, just define a Run method. Include any assembly references and namespaces at the beginning of the file as usual.

A function app's .csx files are compiled when an instance is initialized. This compilation step means things like cold start may take longer for C# script functions compared to C# class libraries. This compilation step is also why C# script functions are editable in the Azure portal, while C# class libraries aren't.

Folder structure

The folder structure for a C# script project looks like the following example:

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

There's a shared host.json file that can be used to configure the function app. Each function has its own code file (.csx) and binding configuration file (function.json).

The binding extensions required in version 2.x and later versions of the Functions runtime are defined in the extensions.csproj file, with the actual library files in the bin folder. When developing locally, you must register binding extensions. When you develop functions in the Azure portal, this registration is done for you.

Binding to arguments

Input or output data is bound to a C# script function parameter via the name property in the function.json configuration file. The following example shows a function.json file and run.csx file for a queue-triggered function. The parameter that receives data from the queue message is named myQueueItem because that's the value of the name property.

{
    "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}");
}

The #r statement is explained later in this article.

Supported types for bindings

Each binding has its own supported types; for instance, a blob trigger can be used with a string parameter, a POCO parameter, a CloudBlockBlob parameter, or any of several other supported types. The binding reference article for blob bindings lists all supported parameter types for blob triggers. For more information, see Triggers and bindings and the binding reference docs for each binding type.

[!INCLUDE HTTP client best practices]

Referencing custom classes

If you need to use a custom Plain Old CLR Object (POCO) class, you can include the class definition inside the same file or put it in a separate file.

The following example shows a run.csx example that includes a POCO class definition.

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; }
}

A POCO class must have a getter and setter defined for each property.

Reusing .csx code

You can use classes and methods defined in other .csx files in your run.csx file. To do that, use #load directives in your run.csx file. In the following example, a logging routine named MyLogger is shared in myLogger.csx and loaded into run.csx using the #load directive:

Example 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}");
}

Example mylogger.csx:

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

Using a shared .csx file is a common pattern when you want to strongly type the data passed between functions by using a POCO object. In the following simplified example, an HTTP trigger and queue trigger share a POCO object named Order to strongly type the order data:

Example run.csx for HTTP trigger:

#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);
    }
}

Example run.csx for queue trigger:

#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;
}

Example 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}";
    }
}

You can use a relative path with the #load directive:

  • #load "mylogger.csx" loads a file located in the function folder.
  • #load "loadedfiles\mylogger.csx" loads a file located in a folder in the function folder.
  • #load "..\shared\mylogger.csx" loads a file located in a folder at the same level as the function folder, that is, directly under wwwroot.

The #load directive works only with .csx files, not with .cs files.

Binding to method return value

You can use a method return value for an output binding, by using the name $return in function.json. For examples, see Triggers and bindings.

Use the return value only if a successful function execution always results in a return value to pass to the output binding. Otherwise, use ICollector or IAsyncCollector, as shown in the following section.

Writing multiple output values

To write multiple values to an output binding, or if a successful function invocation might not result in anything to pass to the output binding, use the ICollector or IAsyncCollector types. These types are write-only collections that are written to the output binding when the method completes.

This example writes multiple queue messages into the same queue using ICollector:

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

Logging

To log output to your streaming logs in C#, include an argument of type ILogger. We recommend that you name it log. Avoid using Console.Write in Azure Functions.

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

Note

For information about a newer logging framework that you can use instead of TraceWriter, see the ILogger documentation in the .NET class library developer guide.

Custom metrics logging

You can use the LogMetric extension method on ILogger to create custom metrics in Application Insights. Here's a sample method call:

logger.LogMetric("TestMetric", 1234);

This code is an alternative to calling TrackMetric by using the Application Insights API for .NET.

Async

To make a function asynchronous, use the async keyword and return a Task object.

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

You can't use out parameters in async functions. For output bindings, use the function return value or a collector object instead.

Cancellation tokens

A function can accept a CancellationToken parameter, which enables the operating system to notify your code when the function is about to be terminated. You can use this notification to make sure the function doesn't terminate unexpectedly in a way that leaves data in an inconsistent state.

The following example shows how to check for impending function termination.

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);
    }
}

Importing namespaces

If you need to import namespaces, you can do so as usual, with the using clause.

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

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

The following namespaces are automatically imported and are therefore optional:

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

Referencing external assemblies

For framework assemblies, add references by using the #r "AssemblyName" directive.

#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)

The following assemblies are automatically added by the Azure Functions hosting environment:

  • 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

The following assemblies may be referenced by simple-name, by runtime version:

  • Newtonsoft.Json
  • Microsoft.WindowsAzure.Storage*

*Removed in version 4.x of the runtime.

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

In code, assemblies are referenced like the following example:

#r "AssemblyName"

Referencing custom assemblies

To reference a custom assembly, you can use either a shared assembly or a private assembly:

  • Shared assemblies are shared across all functions within a function app. To reference a custom assembly, upload the assembly to a folder named bin in your function app root folder (wwwroot).

  • Private assemblies are part of a given function's context, and support side-loading of different versions. Private assemblies should be uploaded in a bin folder in the function directory. Reference the assemblies using the file name, such as #r "MyAssembly.dll".

For information on how to upload files to your function folder, see the section on package management.

Watched directories

The directory that contains the function script file is automatically watched for changes to assemblies. To watch for assembly changes in other directories, add them to the watchDirectories list in host.json.

Using NuGet packages

The way that both binding extension packages and other NuGet packages are added to your function app depends on the targeted version of the Functions runtime.

By default, the supported set of Functions extension NuGet packages are made available to your C# script function app by using extension bundles. To learn more, see Extension bundles.

If for some reason you can't use extension bundles in your project, you can also use the Azure Functions Core Tools to install extensions based on bindings defined in the function.json files in your app. When using Core Tools to register extensions, make sure to use the --csx option. To learn more, see Install extensions.

By default, Core Tools reads the function.json files and adds the required packages to an extensions.csproj C# class library project file in the root of the function app's file system (wwwroot). Because Core Tools uses dotnet.exe, you can use it to add any NuGet package reference to this extensions file. During installation, Core Tools builds the extensions.csproj to install the required libraries. Here's an example extensions.csproj file that adds a reference to Microsoft.ProjectOxford.Face version 1.1.0:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.ProjectOxford.Face" Version="1.1.0" />
    </ItemGroup>
</Project>

Note

For C# script (.csx), you must set TargetFramework to a value of netstandard2.0. Other target frameworks, such as net6.0, aren't supported.

Version 1.x of the Functions runtime uses a project.json file to define dependencies. Here's an example project.json file:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.ProjectOxford.Face": "1.1.0"
      }
    }
   }
}

Extension bundles aren't supported by version 1.x.


To use a custom NuGet feed, specify the feed in a Nuget.Config file in the function app root folder. For more information, see Configuring NuGet behavior.

If you're working on your project only in the portal, you'll need to manually create the extensions.csproj file or a Nuget.Config file directly in the site. To learn more, see Manually install extensions.

Environment variables

To get an environment variable or an app setting value, use System.Environment.GetEnvironmentVariable, as shown in the following code example:

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);
}

Binding at runtime

In C# and other .NET languages, you can use an imperative binding pattern, as opposed to the declarative bindings in function.json. Imperative binding is useful when binding parameters need to be computed at runtime rather than design time. With this pattern, you can bind to supported input and output bindings on-the-fly in your function code.

Define an imperative binding as follows:

  • Do not include an entry in function.json for your desired imperative bindings.
  • Pass in an input parameter Binder binder or IBinder binder.
  • Use the following C# pattern to perform the data binding.
using (var output = await binder.BindAsync<T>(new BindingTypeAttribute(...)))
{
    ...
}

BindingTypeAttribute is the .NET attribute that defines your binding and T is an input or output type that's supported by that binding type. T can't be an out parameter type (such as out JObject). For example, the Mobile Apps table output binding supports six output types, but you can only use ICollector<T> or IAsyncCollector<T> for T.

Single attribute example

The following example code creates a Storage blob output binding with blob path that's defined at run time, then writes a string to the 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 defines the Storage blob input or output binding, and TextWriter is a supported output binding type.

Multiple attributes example

The preceding example gets the app setting for the function app's main Storage account connection string (which is AzureWebJobsStorage). You can specify a custom app setting to use for the Storage account by adding the StorageAccountAttribute and passing the attribute array into BindAsync<T>(). Use a Binder parameter, not IBinder. For example:

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!");
    }
}

The following table lists the .NET attributes for each binding type and the packages in which they're defined.

[!div class="mx-codeBreakAll"]

Binding Attribute Add reference
Azure Cosmos DB Microsoft.Azure.WebJobs.DocumentDBAttribute #r "Microsoft.Azure.WebJobs.Extensions.CosmosDB"
Event Hubs 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"
Notification Hubs Microsoft.Azure.WebJobs.NotificationHubAttribute #r "Microsoft.Azure.WebJobs.Extensions.NotificationHubs"
Service Bus Microsoft.Azure.WebJobs.ServiceBusAttribute, Microsoft.Azure.WebJobs.ServiceBusAccountAttribute #r "Microsoft.Azure.WebJobs.ServiceBus"
Storage queue Microsoft.Azure.WebJobs.QueueAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
Storage blob Microsoft.Azure.WebJobs.BlobAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
Storage table Microsoft.Azure.WebJobs.TableAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
Twilio Microsoft.Azure.WebJobs.TwilioSmsAttribute #r "Microsoft.Azure.WebJobs.Extensions.Twilio"

Next steps

[!div class="nextstepaction"] Learn more about triggers and bindings

[!div class="nextstepaction"] Learn more about best practices for Azure Functions