Azure Functions & Durable Functions

Creating the Project with CoPilot

Bootstrapped a complete ASP.NET Core Web API with SQL Server backend, Azure Functions for async processing, and Azurite-backed message queues. The architecture separates GET operations (handled by durable functions) from POST operations (handled by queue-triggered functions).

Database Schema

  • Books table: Id, Title, ISBN, PublishedDate, Price
  • Authors table: Id, FirstName, LastName, Email, BookId (FK → Books)
  • One-to-many relationship via BookId foreign key
  • EF Core migrations included

Architecture Layers

Repository Pattern

  • IBookRepository / BookRepository – Book data access
  • IAuthorRepository / AuthorRepository – Author data access
  • Async operations, includes navigation properties

Service Layer

  • IBookService / BookService – Book business logic
  • IAuthorService / AuthorService – Author business logic
  • QueueService – Azure Queue message dispatch

API Controllers

  • BooksController – GET triggers durable function via book-get-queue, POST queues to book-queue
  • AuthorsController – Standard CRUD operations

Azure Functions

BookQueueProcessor (Queue-triggered)

[Function("ProcessBookCreation")]
public async Task Run(
    [QueueTrigger("book-queue", Connection = "AzureWebJobsStorage")] string queueMessage)
{
    var bookMessage = JsonSerializer.Deserialize<BookMessage>(queueMessage);
    // Creates book + authors in single transaction
}

BookDurableFunction (Orchestrator)

[Function("BookDurableOrchestrator")]
public async Task<string> RunOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
    var result = await context.CallActivityAsync<string>("ProcessGetRequest", input);
    return result;
}

Message Queue Flow

GET  /api/books → book-get-queue → Durable Function → Orchestration
POST /api/books → book-queue     → Azure Function   → DB Insert

Testing

16 xUnit tests with Moq covering:

  • Service layer behavior (BookService)
  • Repository CRUD operations (InMemory EF provider)

Configuration

  • appsettings.json – SQL Server connection, Azurite queue config
  • local.settings.json – Azure Functions runtime settings
  • Two queues: book-queuebook-get-queue
  • Connection string: UseDevelopmentStorage=true (Azurite)

Documentation

  • SETUP.md – Installation and database setup
  • QUICKSTART.md – Three-terminal startup sequence
  • ARCHITECTURE.md – System diagrams and data flows

Note that along the way I added to my prompt, and CoPilot adjusted what it was doing accordingly.

Merge info

Review requested

Review has been requested on this pull request. It is not required to merge. Learn more about requesting a pull request review.

No conflicts with base branch

Merging can be performed automatically.

This pull request is still a work in progress

CoPilot then asked me to review the code (Pull Request):

I then clicked on Merge and it provided me with a form for the title and description, already filled out for me to modify and approve

Readme file

CoPilot documented what it did in the ReadMe…

Architecture Overview

This is an ASP.NET Core Web API with a SQL Server backend, using Azure Functions and Azure Storage Queues (via Azurite) for asynchronous processing.

Key Components

  1. ASP.NET Web API – RESTful API with controllers
  2. SQL Server Database – Two tables: Books and Authors
  3. Azure Functions – Asynchronous processing
    • Durable Function for GET requests
    • Queue-triggered Function for POST requests
  4. Azure Storage Queues (via Azurite) – Message bus
  5. Service Layer – Business logic
  6. Repository Pattern – Data access layer
  7. Unit Tests – xUnit with Moq

Database Schema

  • Books Table: Id, Title, ISBN, PublishedDate, Price
  • Authors Table: Id, FirstName, LastName, Email, BookId (FK)

Relationship: One Book can have many Authors

Request Flow

GET /api/books

  1. API receives request
  2. Sends message to book-get-queue (Azurite)
  3. Durable Function reads from queue and orchestrates processing
  4. API returns book data from database

POST /api/books

  1. API receives BookMessage
  2. Sends message to book-queue (Azurite)
  3. Azure Function (queue-triggered) reads from queue
  4. Function creates book and authors in database
  5. API returns 202 Accepted

Quick Start

See SETUP.md for detailed setup instructions.

Running Tests

cd BookstoreAPI.Tests
dotnet test

All 16 unit tests cover:

  • Service layer (8 tests)
  • Repository layer (8 tests)

Starting

CoPilot gave me step by step instructions on how to use this…

Bookstore API Setup Guide

Prerequisites

  • .NET 8.0 or later SDK
  • SQL Server or LocalDB
  • Node.js (for Azurite)
  • Azurite (Azure Storage Emulator)

Setup Instructions

1. Install Azurite

npm install -g azurite

2. Start Azurite

Run Azurite to provide local Azure Storage emulation (including queues):

azurite --silent --location ./azurite --debug ./azurite/debug.log

This will start:

  • Blob service on http://127.0.0.1:10000
  • Queue service on http://127.0.0.1:10001 (message bus)
  • Table service on http://127.0.0.1:10002

3. Database Setup

Update the connection string in appsettings.json if needed:

"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BookstoreDb;Trusted_Connection=True;MultipleActiveResultSets=true"

Create the database:

cd BookstoreAPI
dotnet ef migrations add InitialCreate
dotnet ef database update

4. Run the Application

Start the API

cd BookstoreAPI
dotnet run

Start the Azure Functions

cd BookstoreAPI.Functions
func start

Or if using dotnet directly:

cd BookstoreAPI.Functions
dotnet run

5. Run Tests

cd BookstoreAPI.Tests
dotnet test

Architecture

Components

  1. ASP.NET Web API (BookstoreAPI)
    • Controllers for Books and Authors
    • Service layer for business logic
    • Repository layer for data access
    • Queue service for message bus integration
  2. Azure Functions (BookstoreAPI.Functions)
    • GET Durable Function: Listens to book-get-queue via Azurite
    • POST Azure Function: Processes book creation from book-queue
  3. Database
    • SQL Server with two tables: Books and Authors
    • Authors has a foreign key to Books (BookId)
  1. Client → GET /api/books
  2. API sends message to book-get-queue (Azurite)
  3. Durable Function reads from queue
  4. API returns book data from database

POST Request Flow

  1. Client → POST /api/books with BookMessage
  2. API sends message to book-queue (Azurite)
  3. Azure Function reads from queue
  4. Azure Function adds book to database
  5. API returns 202 Accepted

Queue Configuration

  • GET Queuebook-get-queue – Used by Durable Function
  • POST Queuebook-queue – Used by regular Azure Function

Both queues use Azurite as the message bus with connection string:

UseDevelopmentStorage=true

API Endpoints

Books

  • GET /api/books – Get all books (triggers durable function)
  • GET /api/books/{id} – Get book by ID
  • POST /api/books – Create book (via Azure Function)
  • PUT /api/books/{id} – Update book
  • DELETE /api/books/{id} – Delete book

Authors

  • GET /api/authors – Get all authors
  • GET /api/authors/{id} – Get author by ID
  • GET /api/authors/book/{bookId} – Get authors by book
  • POST /api/authors – Create author
  • PUT /api/authors/{id} – Update author
  • DELETE /api/authors/{id} – Delete author

Testing

The solution includes unit tests using xUnit and Moq for:

  • Service layer
  • Repository layer
  • Controllers

Clone

Quick Start

I then took a look at the Quick Start Guid

Prerequisites Check

# Check .NET SDK
dotnet --version  # Should be 10.0 or later

# Check Node.js (for Azurite)
node --version

# Install Azurite globally
npm install -g azurite

Oops, didn’t have node. No problem, a quick trip to Google revealed where I could get it and installation was a snap. Note that the Node installer also installed chocolatey, which took a few minutes.

Step-by-Step Startup

1. Start Azurite (Terminal 1)

cd BookstoreAPI
mkdir -p azurite
azurite --silent --location ./azurite --debug ./azurite/debug.log

Azurite will start on:

2. Start Azure Functions (Terminal 2)

cd BookstoreAPI.Functions

# Copy template if needed
cp local.settings.template.json local.settings.json

# Run functions
dotnet run

Functions will listen to:

  • book-queue – For POST requests
  • book-get-queue – For GET requests

3. Start Web API (Terminal 3)

cd BookstoreAPI

# Run the API
dotnet run

API will start on: http://localhost:5000 (or check console for actual port)

4. Test the API

Using curl:

# GET all books (triggers durable function)
curl http://localhost:5000/api/books

# POST a new book (uses Azure Function)
curl -X POST http://localhost:5000/api/books \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Test Book",
    "isbn": "123-456-789",
    "publishedDate": "2024-01-01T00:00:00",
    "price": 29.99,
    "authors": [
      {
        "firstName": "John",
        "lastName": "Doe",
        "email": "john@example.com"
      }
    ]
  }'

# GET a specific book
curl http://localhost:5000/api/books/1

# GET all authors
curl http://localhost:5000/api/authors

# GET authors for a specific book
curl http://localhost:5000/api/authors/book/1

Using the .http file (VS Code with REST Client extension):

# Open BookstoreAPI/BookstoreAPI.http
# Click "Send Request" on any request

Troubleshooting

Database Not Found

cd BookstoreAPI

# Install EF Core tools
dotnet tool install --global dotnet-ef

# Create/update database
dotnet ef database update

Azurite Not Starting

# Kill any running instances
pkill azurite

# Clear data and restart
rm -rf azurite
mkdir azurite
azurite --silent --location ./azurite

Functions Not Triggering

  1. Check Azurite is running
  2. Check connection string in local.settings.jsonUseDevelopmentStorage=true
  3. Check function logs for errors
  4. Verify queues exist in Azurite (can use Azure Storage Explorer)

Build Errors

# Clean and rebuild
dotnet clean
dotnet restore
dotnet build

Running Tests

cd BookstoreAPI.Tests
dotnet test --verbosity normal

Expected output:

Passed!  - Failed:     0, Passed:    16, Skipped:     0, Total:    16

Monitoring

View Queue Messages

Install Azure Storage Explorer to view:

  • Queue messages in book-queue and book-get-queue
  • Connection: Use “Attach to local emulator”

View Function Logs

Azure Functions will output logs to the console showing:

  • Queue triggers firing
  • Durable function orchestrations
  • Database operations

View API Logs

ASP.NET Core will output logs showing:

  • HTTP requests
  • Controller actions
  • Queue message sends

Next Steps

  1. Add Swagger/OpenAPI – Already configured, visit /openapi endpoints
  2. Add Authentication – Implement JWT or OAuth
  3. Add Validation – Data annotations on models
  4. Add More Tests – Controller tests, integration tests
  5. Deploy to Azure – Use Azure App Service + Azure Functions + Azure SQL

Project Structure

BookstoreAPI/
├── BookstoreAPI/              # Web API
│   ├── Controllers/           # API endpoints
│   ├── Models/                # Data models
│   ├── Services/              # Business logic
│   ├── Data/                  # EF Core + Repositories
│   └── Migrations/            # Database migrations
├── BookstoreAPI.Functions/    # Azure Functions
│   ├── BookQueueProcessor.cs  # POST handler
│   └── BookDurableFunction.cs # GET handler
└── BookstoreAPI.Tests/        # Unit tests
    ├── Services/              # Service tests
    └── Repositories/          # Repository tests

Unknown's avatar

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET, is now available wherever you buy your books. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in AI, API, C#, CoPilot, Durable Functions, Essentials, Learning, Programming and tagged , , . Bookmark the permalink.