Google Cloud Functions in C#

1. Introduction

Google Cloud Run functions is an event-driven serverless compute platform. Cloud Run functions allows you to write your code without worrying about provisioning resources or scaling to handle changing requirements.

There are two types of Cloud Run functions:

  • HTTP functions respond to HTTP requests.
  • Event functions are triggered by events, like a message being published to Cloud Pub/Sub or a file being uploaded to Cloud Storage.

efb3268e3b74ed4f.png

This codelab will walk you through creating your own Cloud Run functions in C#. More specifically, you will deploy C# functions responding to HTTP and CloudEvents from various Google Cloud sources.

What you'll learn

  • Functions Framework for .NET.
  • How to write an HTTP Function.
  • How to write an Event Triggered function responding to Cloud Storage events.
  • How to write a Event Triggered function responding to Cloud Pub/Sub events.
  • How to write a Event Triggered function responding to any type of event.

2. Setup and Requirements

Self-paced environment setup

  1. Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • The Project name is the display name for this project's participants. It is a character string not used by Google APIs. You can update it at any time.
  • The Project ID must be unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference the Project ID (it is typically identified as PROJECT_ID). If you don't like the generated ID, you may generate another random one. Alternatively, you can try your own and see if it's available. It cannot be changed after this step and will remain for the duration of the project.
  • For your information, there is a third value, a Project Number which some APIs use. Learn more about all three of these values in the documentation.
  1. Next, you'll need to enable billing in the Cloud Console to use Cloud resources/APIs. Running through this codelab shouldn't cost much, if anything at all. To shut down resources so you don't incur billing beyond this tutorial, you can delete the resources you created or delete the whole project. New users of Google Cloud are eligible for the $300 USD Free Trial program.

Start Cloud Shell

While Google Cloud can be operated remotely from your laptop, in this codelab you will be using Google Cloud Shell, a command line environment running in the Cloud.

From the Google Cloud Console, click the Cloud Shell icon on the top right toolbar:

55efc1aaa7a4d3ad.png

It should only take a few moments to provision and connect to the environment. When it is finished, you should see something like this:

7ffe5cbb04455448.png

This virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory, and runs on Google Cloud, greatly enhancing network performance and authentication. All of your work in this codelab can be done within a browser. You do not need to install anything.

3. Before you begin

Inside Cloud Shell, run the following command to enable required services:

gcloud services enable \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  cloudfunctions.googleapis.com \
  eventarc.googleapis.com \
  run.googleapis.com

Next, set your region.

REGION=<YOUR_REGION>

For this codelab, you'll create a service account with required EventArc permissions and Cloud Run invoker role to receive an event from Cloud Storage and invoke the Cloud Run function.

First, create the service account.

PROJECT_ID=$(gcloud config get-value core/project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

SERVICE_ACCOUNT="cloud-run-functions"
SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com

gcloud iam service-accounts create $SERVICE_ACCOUNT \
  --display-name="Cloud Run functions Eventarc service account"

Next, grant the Eventarc Event Receiver role (roles/eventarc.eventReceiver) on the project to the service account associated with your Eventarc trigger so that the trigger can receive events from event providers.

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
  --role=roles/eventarc.eventReceiver

Then, grant the service account the Cloud Run invoker role so that it can invoke the function.

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
  --role=roles/run.invoker

4. Functions Framework for .NET

Functions Framework for .NET is an open source FaaS (Function as a Service) framework for writing portable .NET functions – brought to you by the Google Cloud Functions team.

The Functions Framework lets you write lightweight functions that run in many different environments, including:

  • Google Cloud Run functions
  • Your local development machine
  • Cloud Run and Cloud Run on GKE
  • Knative-based environments

In this codelab, you will use Functions Framework for .NET and its templates to create and deploy Cloud Functions in C#.

Inside Cloud Shell, run the following command to install Cloud Functions templates for dotnet:

dotnet new install Google.Cloud.Functions.Templates

This installs 3 templates for dotnet. Each template is available in C#, F# and VB (but you'll only use C# in this lab). You can verify that templates are installed by running:

dotnet new list

Templates                                                 Short Name            
-----------------------------------------------------------------------
Google Cloud Functions CloudEvent Function                gcf-event
Google Cloud Functions CloudEvent Function (Untyped)      gcf-untyped-event
Google Cloud Functions HttpFunction                       gcf-http

5. HTTP Function

You will create and deploy an HTTP Function responding to HTTP requests.

Create an HTTP Function using the gcf-http template:

mkdir HelloHttp
cd HelloHttp
dotnet new gcf-http

This creates a project and a Function.cs file responding to HTTP requests.

Change the target framework to net8.0 in the .csproj file:

<TargetFramework>net8.0</TargetFramework>

To deploy a Cloud Run function directly onto Cloud Run, run the following command:

gcloud beta run deploy hello-http-function \
    --source . \
    --function HelloHttp.Function \
    --base-image dotnet8 \
    --region $REGION \
    --allow-unauthenticated

If you prefer to deploy as a Cloud Functions 2nd gen, use the following command:

gcloud functions deploy hello-http-function \
    --allow-unauthenticated \
    --entry-point HelloHttp.Function \
    --gen2 \
    --region $REGION \
    --runtime dotnet8 \
    --trigger-http

Once your function has been deployed, you can invoke it using the following curl command:

SERVICE_URL=$(gcloud run services describe hello-http-function --platform managed --region $REGION --format 'value(status.url)')

curl $SERVICE_URL

6. CloudEvent Function - GCS

You will create and deploy a CloudEvent Function responding to Google Cloud Storage (GCS) events.

First, create a Cloud Storage bucket. This is the bucket you will listen events from later:

BUCKET_NAME="cloud-functions-bucket-${PROJECT_ID}"
gsutil mb -l us-central1 gs://${BUCKET_NAME}

Create a CloudEvent Function using the gcf-event template:

cd ..
mkdir HelloGcs
cd HelloGcs
dotnet new gcf-event

This creates a project and a Function.cs file responding to CloudEvent requests. It also parses the data of CloudEvent into StorageObjectData.

Change the target framework to net8.0 in the .csproj file:

<TargetFramework>net8.0</TargetFramework>

To deploy a Cloud Run function directly onto Cloud Run, you will first deploy the function and then create a trigger for it.

gcloud beta run deploy hello-gcs-function \
      --source . \
      --function HelloGcs.Function \
      --region $REGION \
      --base-image dotnet8 \
      --no-allow-unauthenticated

Now create the trigger for the Cloud Run Function

BUCKET_REGION=$REGION

gcloud eventarc triggers create hello-gcs-function-trigger \
     --location=$REGION \
     --destination-run-service=hello-gcs-function \
    --destination-run-region=$BUCKET_REGION \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=$BUCKET_NAME" \
     --service-account=$SERVICE_ACCOUNT_ADDRESS

If you prefer to deploy as a Cloud Functions 2nd gen, you can use the following command to deploy the function using trigger-event and trigger-resource flags:

gcloud functions deploy hello-gcs-function \
    --allow-unauthenticated \
    --entry-point HelloGcs.Function \
    --gen2 \
    --region $REGION \
    --runtime dotnet8 \
    --trigger-event google.storage.object.finalize \
    --trigger-resource ${BUCKET_NAME} \
    --service-account=$SERVICE_ACCOUNT_ADDRESS

After a couple of minutes, the function should be visible in Cloud Console:

c28654d74bb31420.png

Trigger the function by uploading a file to the storage bucket:

echo "Hello from Storage" > random.txt
gsutil cp random.txt gs://${BUCKET_NAME}

Verify that the function was triggered by reading the logs:

For a Cloud Run function, you can run this command:

gcloud logging read "resource.labels.service_name=hello-gcs-function AND textPayload: Name" --format=json

For a 2nd gen function, you can run this command:

gcloud functions logs read hello-gcs-function \
    --gen2 \
    --region us-central1

7. CloudEvent Function - Pub/Sub

You will create and deploy a CloudEvent Function responding to Cloud Pub/Sub events.

First, create a Cloud Pub/Sub topic that will emit events:

TOPIC_NAME=cloud-functions-topic
gcloud pubsub topics create ${TOPIC_NAME}

Create a CloudEvent Function using the gcf-event template:

cd ..
mkdir HelloPubSub
cd HelloPubSub
dotnet new gcf-event

This creates a project and a Function.cs file responding to CloudEvent requests. It also parses the data of CloudEvent into StorageObjectData by default.

Change the target framework to net8.0 in the .csproj file:

<TargetFramework>net8.0</TargetFramework>

Change StorageObjectData to MessagePublishedData to parse Pub/Sub messages. Change Google.Events.Protobuf.Cloud.Storage.V1 to Google.Events.Protobuf.Cloud.PubSub.V1.

In the end, your function should look like this:

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.PubSub.V1;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace HelloPubSub;

public class Function : ICloudEventFunction<MessagePublishedData>
{
    public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken)
    {
        var nameFromMessage = data.Message?.TextData;
        var name = string.IsNullOrEmpty(nameFromMessage) ? "world" : nameFromMessage;
        Console.WriteLine($"Hello {name}");
        return Task.CompletedTask;
    }
}

To deploy a Cloud Run function directly onto Cloud Run, you will first deploy the function and then create a trigger for it.

gcloud beta run deploy hello-pubsub-function \
      --source . \
      --function HelloPubSub.Function \
      --region $REGION \
      --base-image dotnet8 \
      --no-allow-unauthenticated \
      --service-account=$SERVICE_ACCOUNT_ADDRESS

Now create the trigger for the Cloud Run function

gcloud eventarc triggers create my-pubsub-trigger \
    --location=$REGION \
    --service-account=$SERVICE_ACCOUNT_ADDRESS \
    --destination-run-service=hello-pubsub-function \
    --destination-run-region=$REGION \
    --destination-run-path="/" \
    --event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
    --transport-topic=projects/$PROJECT_ID/topics/$TOPIC_NAME

If you prefer to deploy as a Cloud Functions 2nd gen, you can use the following command to deploy the function using the trigger-topic flag:

gcloud functions deploy hello-pubsub-function \
    --allow-unauthenticated \
    --entry-point HelloPubSub.Function \
    --gen2 \
    --region us-central1 \
    --runtime dotnet8 \
    --trigger-topic ${TOPIC_NAME}

After a couple of minutes, the function should be visible in Cloud Console:

3443808da7caf3bc.png

Trigger the function by publishing a message to the topic:

gcloud pubsub topics publish ${TOPIC_NAME} --message="World"

Verify that the function was triggered by reading the logs.

For a Cloud Run function, you can run this command:

gcloud logging read "resource.labels.service_name=hello-pubsub-function AND textPayload: World" --format=json

For a 2nd gen function, you can run this command:

gcloud functions logs read hello-pubsub-function \
    --gen2 \
    --region us-central1

8. CloudEvent Function - Untyped

If you are experimenting with CloudEvents and don't yet have a payload data model you wish to commit to, or you want your function to be able to handle any Cloud Event, you can use an untyped CloudEvent function.

Create an CloudEvent Function using the gcf-untyped-event template:

cd ..
mkdir HelloUntyped
cd HelloUntyped
dotnet new gcf-untyped-event

This creates a project and a Function.cs file responding to CloudEvent requests without any attempt to parse the data of the CloudEvent.

Change the target framework to net8.0 in the .csproj file:

<TargetFramework>net8.0</TargetFramework>

To deploy a Cloud Run function directly onto Cloud Run, you will first deploy the function and then create a trigger for it.

gcloud beta run deploy hello-untyped-function \
      --source . \
      --function HelloUntyped.Function \
      --region $REGION \
      --base-image dotnet8 \
      --no-allow-unauthenticated

Now create the trigger for the Cloud Run Function

BUCKET_REGION=$REGION

gcloud eventarc triggers create hello-untyped-function-trigger \
     --location=$REGION \
     --destination-run-service=hello-untyped-function \
    --destination-run-region=$BUCKET_REGION \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=$BUCKET_NAME" \
     --service-account=$SERVICE_ACCOUNT_ADDRESS

If you prefer to deploy as a Cloud Functions 2nd gen, you can use the following command to deploy the function using trigger-event and trigger-resource flags:

gcloud functions deploy hello-untyped-function \
    --allow-unauthenticated \
    --entry-point HelloUntyped.Function \
    --gen2 \
    --region us-central1 \
    --runtime dotnet8 \
    --trigger-event google.storage.object.finalize \
    --trigger-resource ${BUCKET_NAME}

The function will trigger when a file is uploaded to a storage bucket.

After a couple of minutes, the function should be visible in Cloud Console:

afe56530826787c6.png

Trigger the function by uploading a file to the storage bucket:

echo "Hello from Storage" > random.txt
gsutil cp random.txt gs://${BUCKET_NAME}

Verify that the function was triggered by reading the logs.

For a Cloud Run function, you can run this command:

gcloud logging read "resource.labels.service_name=hello-gcs-function AND textPayload: Name" --format=json

For a 2nd gen function, you can run this command:

gcloud functions logs read hello-untyped-function \
    --gen2 \
    --region us-central1

9. Congratulations!

Congratulations for completing the codelab.

What we've covered

  • Functions Framework for .NET.
  • How to write an HTTP Cloud Function.
  • How to write a CloudEvent Function responding to Cloud Storage events.
  • How to write a CloudEvent Function responding to Cloud Pub/Sub events.
  • How to write a CloudEvent Function responding to any type of event.