Create Azure Functions for APIs
This guide covers the AZ-204 exam topics for creating and configuring Azure Functions:
- Create and configure an Azure Functions app
- Implement input and output bindings
- Implement function triggers by using data operations, timers, and webhooks
Prerequisites
- .NET SDK 8.0 (Install via winget:
winget install Microsoft.DotNet.SDK.8
) - Azure CLI (Install)
- Azure Functions Core Tools (Install)
- Azure Subscription (Free tier or $200 credit recommended for minimal costs)
- Azurite (Install)
- Existing Resource Group
Setup Azure Functions App
Follow these steps to set up and deploy an Azure Functions app:
-
Create a new Function App in the Azure Portal.
-
Create a Function project locally.
-
Move into the project directory.
-
Build the Function.
-
Test the Function locally.
-
Deploy the Function to Azure.
-
Retrieve the Function key.
-
Test the deployed Function.
Step 1: Create a New Function App
In the Azure Portal, create a new Function App:
- Check out the tabs for a learning experience; we are leaving them as default.
Step 2: Create a Function Project Locally
From your root directory, build your Function project.
func init TaskManagerFunctions --dotnet-isolated --target-framework net8.0
Step 3: Move into the Project Directory
Navigate into the TaskManagerFunctions
directory.
cd TaskManagerFunctions
Step 4: Build the Function
Create a new HTTP-triggered Function.
func new --template "HttpTrigger" --name CreateTask
Step 5: Test the Function Locally
Run the Function locally to test it.
func start
You should see output like:
Functions:
CreateTask: [GET,POST] http://localhost:7071/api/CreateTask
Open a browser and go to:
http://localhost:7071/api/CreateTask?name=Test
Expected response:
This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.
Step 6: Deploy to Azure
Deploy the Function to Azure.
func azure functionapp publish taskmanagerfunc-yourname #replace with your function name
Watch for the “Functions in taskmanagerfunc-yourname” section in the output. It should list:
Functions in taskmanagerfunc-yourname:
CreateTask - [httpTrigger]
Invoke url: https://taskmanagerfunc-yourname.azurewebsites.net/api/createtask?code=...
Step 7: Retrieve the Function Key
Retrieve the default Function key.
az functionapp function keys list --resource-group az204exam --name taskmanagerfunc-yourname --function-name CreateTask
Save the default key value for the next step.
Step 8: Test the Deployed Function
Construct the URL using the invoke URL from Step 6 and append your default key. It should look like:
https://taskmanagerfunc-yourname.azurewebsites.net/api/createtask?code=THISISYOURCODEFROMSTEP7&name=Test
You should see:
Hello, Test. This HTTP triggered function executed successfully.
Add Blob Storage (Placeholder)
Note: We’ll use Blob Storage to store tasks, prepping for Cosmos DB later. Blob Storage and Cosmos DB are in a separate exam topic, but we need to create a Storage Account for use in triggers and outputs.
Follow these steps to set up Blob Storage:
-
Create a Storage Account.
-
Get the Storage connection string.
-
Add the connection string to the Function App.
Step 1: Create a Storage Account
In the Azure Portal, create a Storage Account:
- Name:
taskmanagerstoreyourname
. - Resource group:
az204
. - Region:
West US3
. - Primary Service:
Azure Blob Storage or Azure Data Lake Storage Gen 2
. - Redundancy:
LRS
.
After creation, go to Data Storage > Containers > Create container named tasks
.
Step 2: Get the Connection String
In the Storage Account, go to Access keys and copy the Connection string for key1
.
Step 3: Add to Function App
In your Function App (taskmanagerfunc-yourname
), go to Settings > Environment Variables.
- Click New+ Add Button.
- Name:
StorageConnection
, Value:[your-connection-string]
. - Save and restart.
Implement Input and Output Bindings
Note: We’ll use HTTP to read task data (input) and Blob Storage to save tasks (output), matching our web app’s tasks.
Follow these steps to implement input and output bindings:
-
Add the Blob Storage SDK.
-
Update the CreateTask Function to use bindings.
-
Test locally.
-
Deploy and test in Azure.
Step 1: Add Blob Storage SDK
In the TaskManagerFunctions
directory, run:
dotnet add package Microsoft.Azure.Functions.Worker
dotnet add package Microsoft.Azure.Functions.Worker.Sdk
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Http
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage
Step 2: Update CreateTask Function
Replace CreateTask.cs
with:
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace TaskManagerFunctions
{
public class CreateTask
{
private readonly ILogger<CreateTask> _logger;
public CreateTask(ILogger<CreateTask> logger)
{
_logger = logger;
}
public class TaskItem
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string? Title { get; set; } // Nullable to avoid CS8618
public string? Description { get; set; } // Nullable
public string? DueDate { get; set; } // Nullable
}
[Function("CreateTask")]
public async Task<MultiOutput> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestData req)
{
_logger.LogInformation("Creating new task.");
// Read the request body
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
TaskItem? task = JsonSerializer.Deserialize<TaskItem>(requestBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (task == null || string.IsNullOrEmpty(task.Title))
{
var badRequestResponse = req.CreateResponse(System.Net.HttpStatusCode.BadRequest);
await badRequestResponse.WriteStringAsync("Please provide a valid task with a title.");
return new MultiOutput
{
Response = badRequestResponse,
BlobOutput = null
};
}
// Generate a new ID for the task
task.Id = Guid.NewGuid().ToString();
_logger.LogInformation($"Saving task: {task.Title}");
// Serialize the task to JSON for Blob storage
string blobOutput = JsonSerializer.Serialize(task);
// Return a success response
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
await response.WriteAsJsonAsync(task);
return new MultiOutput
{
Response = response,
BlobOutput = blobOutput
};
}
// Define the multi-output response
public class MultiOutput
{
public HttpResponseData Response { get; set; }
[BlobOutput("tasks/{Id}.json", Connection = "AzureWebJobsStorage")]
public string BlobOutput { get; set; }
}
}
}
Step 3: Test Locally
Update local.settings.json
:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}
Run the Function locally:
func start
POST a task using curl or Postman:
curl -X POST http://localhost:7071/api/CreateTask -H "Content-Type: application/json" -d '{"Title":"Test Task","Description":"Do this","DueDate":"2025-04-15"}'
Check the tasks
container (local Azurite or Azure Portal) for a [guid].json
file with the task JSON.
Step 4: Deploy and Test
Deploy the Function to Azure:
func azure functionapp publish taskmanagerfunc-yourname
Test:
curl -X POST https://taskmanagerfunc-yourname.azurewebsites.net/api/createtask?code=[your-key] -H "Content-Type: application/json" -d '{"Title":"Azure Task","Description":"Deployed","DueDate":"2025-04-15"}'
Verify the tasks
container in the Azure Portal has a new JSON file.
Why
HTTP input reads task data, and Blob output saves it, hitting exam goals. This also preps for Cosmos DB without needing it now.
Implement Function Triggers
Note: Our HTTP trigger covers webhooks. We’ll add a timer trigger and Blob trigger for data operations.
Follow these steps to implement additional function triggers:
-
Add a Timer Trigger.
-
Add a Blob Trigger.
-
Test locally.
-
Deploy and test in Azure.
Step 1: Add a Timer Trigger
In TaskManagerFunctions
, create a Timer Trigger Function.
func new --template "TimerTrigger" --name CheckTasks
Edit CheckTasks.cs
:
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
namespace TaskManagerFunctions
{
public class CheckTasks
{
private readonly ILogger<CheckTasks> _logger;
public CheckTasks(ILogger<CheckTasks> logger)
{
_logger = logger;
}
[Function("CheckTasks")]
public void Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer)
{
_logger.LogInformation($"Timer triggered at: {DateTime.Now}");
_logger.LogInformation("Checking tasks (placeholder for future logic)...");
}
}
}
Note: This runs every 5 minutes.
Step 2: Add a Blob Trigger
Create a Blob Trigger Function.
func new --template "BlobTrigger" --name ProcessTask
Edit ProcessTask.cs
:
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
namespace TaskManagerFunctions
{
public class ProcessTask
{
private readonly ILogger<ProcessTask> _logger;
public ProcessTask(ILogger<ProcessTask> logger)
{
_logger = logger;
}
[Function("ProcessTask")]
public void Run([BlobTrigger("tasks/{name}.json", Connection = "StorageConnection")] string taskJson)
{
_logger.LogInformation($"New task file detected: {taskJson}");
}
}
}
Step 3: Test Locally
Ensure local.settings.json
has the StorageConnection
.
Run the Function locally:
func start
- For the timer: Wait 5 minutes and see “Checking tasks...” in the console.
- For the Blob: Add a JSON file to the
tasks
container (via Azurite or Azure Portal), and check the console for “New task file detected...”.
Step 4: Deploy and Test
Deploy the Function to Azure:
func azure functionapp publish taskmanagerfunc-yourname
Test:
- Timer: Wait 5 minutes, then check Azure Portal > Functions > CheckTasks > Monitor > Logs.
- Blob: Upload a JSON file to the
tasks
container, then check ProcessTask logs.
Why
Timer (scheduled), Blob (data operation), and HTTP (webhook) triggers cover all exam requirements.
Next Steps
- Do everything again, but use the CLI where possible.
- If you are going to take a break, you could clean up here, but the next section builds on what we have done here.