DEV Community

Douglas SantAnna Figueredo
Douglas SantAnna Figueredo

Posted on

Azure Function with .NET Isolated Worker: Sharing Services Between API and Function Apps

This tutorial demonstrates how to create an Azure Function with a .NET isolated worker process that utilizes services defined in a separate API project. This architecture allows you to share business logic and data access code between your API and functions, promoting code reuse and maintainability.

Prerequisites:

  • Visual Studio Code: Installed and configured for .NET development.
  • SQL Server Instance: A running SQL Server instance with a database and a "Users" table already created. (We'll focus on the integration, not the full database setup.)
  • Azurite: Installed for local Azure Storage emulation (if using triggers that require storage).
  • .NET SDK and Runtime (Version 8): .NET 8 SDK and runtime environment installed on your machine.
  • Azure Functions Core Tools: Azure Functions Core Tools installed for local function development and deployment.

For this example, we'll use a simplified scenario with fake data and methods. It's assumed you have a database server running and a database with a "Users" table already created. We'll focus on the integration between the API and Function rather than full database setup.

Step-by-Step Guide

Project Setup

  • Create a project folder:
mkdir az-func-api cd az-func-api 
Enter fullscreen mode Exit fullscreen mode
  • Create the API project:
mkdir api cd api dotnet new webapi -f net8.0 # Use webapi template for cleaner API project 
Enter fullscreen mode Exit fullscreen mode
  • Create the Function project:
cd .. mkdir functions cd functions func init --worker-runtime dotnet-isolated # Or func init http for an HTTP triggered function 
Enter fullscreen mode Exit fullscreen mode

Follow the prompts to configure your function. Choose the dotnet (isolated worker model) and then c#-isolated.

  • Add both projects to the solution:
cd .. dotnet new sln # Create Solution File dotnet sln add api dotnet sln add functions 
Enter fullscreen mode Exit fullscreen mode
  • Add a project reference from the Functions project to the API project:
cd functions dotnet add reference ../api/ 
Enter fullscreen mode Exit fullscreen mode

API Project Setup

  • Install NuGet Packages:
cd api dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 8.0.1 dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8.0.1 
Enter fullscreen mode Exit fullscreen mode

Configure Database Connection:
Add your database connection string to appsettings.json in the API project:

{ "ConnectionStrings": { "DefaultConnection": "Server=localhost,1433;Database=MyDatabase;User Id=sa;Password=MyPassword;Encrypt=True;TrustServerCertificate=True" } } 
Enter fullscreen mode Exit fullscreen mode
  • Create Data Model (User.cs):
// api/Models/User.cs public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } // Added Email property for demonstration } 
Enter fullscreen mode Exit fullscreen mode
  • Create Data Context (DataContext.cs):
// api/Data/DataContext.cs public class DataContext : DbContext { public DataContext(DbContextOptions<DataContext> options) : base(options) { } public DbSet<User> Users { get; set; } } 
Enter fullscreen mode Exit fullscreen mode
  • Create Repository Interface and Implementation (IUserRepository.cs, UserRepository.cs):
// api/Interfaces/IUserRepository.cs public interface IUserRepository { Task<List<User>> GetUsers(CancellationToken cancellationToken); Task CreateUsers(List<User> users); } // api/Repositories/UserRepository.cs public class UserRepository : IUserRepository { private readonly DataContext _context; public UserRepository(DataContext context) { _context = context; } public async Task<List<User>> GetUsers(CancellationToken cancellationToken) { return await _context.Users.ToListAsync(cancellationToken); } public async Task CreateUsers(List<User> users) { await _context.Users.AddRangeAsync(users); await _context.SaveChangesAsync(); } } 
Enter fullscreen mode Exit fullscreen mode
  • Register Services (ServiceExtensions.cs):
// api/Extensions/ServiceExtensions.cs public static class ServiceExtensions { public static void AddApplicationServices(this IServiceCollection services, string connectionString) { services.AddDbContext<DataContext>(opt => opt.UseSqlServer(connectionString)); services.AddTransient<IUserRepository, UserRepository>(); } } 
Enter fullscreen mode Exit fullscreen mode
  • Configure API Endpoints (Program.cs):
// api/Program.cs var builder = WebApplication.CreateBuilder(args); // Retrieve the connection string (better practice than hardcoding) var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); if (string.IsNullOrEmpty(connectionString)) { throw new InvalidOperationException("Connection string 'DefaultConnection' is missing."); } builder.Services.AddApplicationServices(connectionString); // Pass connection string here var app = builder.Build(); app.MapPost("/users", async (IUserRepository userRepository) => { var fakeUsers = new List<User> // List of fake users  { new User { Name = "Alice", Email = "alice@example.com" }, new User { Name = "Bob", Email = "bob@example.com" }, new User { Name = "Charlie", Email = "charlie@example.com" }, // Add more fake users as needed  }; await userRepository.CreateUsers(fakeUsers); return Results.Created($"/users", fakeUsers); }); app.MapGet("/users", async (IUserRepository userRepository, CancellationToken cancellationToken) => { return Results.Ok(await userRepository.GetUsers(cancellationToken)); }); app.Run(); 
Enter fullscreen mode Exit fullscreen mode
  • Test API Endpoints: Use Postman or a similar tool to test the /users POST and GET endpoints.

Functions Project Setup

  • Configure local.settings.json:
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", "DefaultConnection": "Server=localhost,1433;Database=MyDatabase;User Id=sa;Password=MyPassword;Encrypt=True;TrustServerCertificate=True" } } 
Enter fullscreen mode Exit fullscreen mode
  • Configure Program.cs:
// functions/Program.cs using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System; var builder = FunctionsApplication.CreateBuilder(args); builder.ConfigureFunctionsWebApplication(); builder.Configuration .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); var connectionString = builder.Configuration.GetValue<string>("Values:DefaultConnection"); if (string.IsNullOrEmpty(connectionString))     throw new InvalidOperationException("Connection string 'DefaultConnection' is missing."); builder.Services.AddApplicationServices(connectionString); // Use the same extension method builder.Build().Run(); 
Enter fullscreen mode Exit fullscreen mode
  • Create the Function by running command func new --name users, select function type TimerTrigger
// functions/listUsers.cs using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using api.Interfaces; // Important: Use the interface from the API project namespace MyFunctionApp { public class listUsers { private readonly ILogger _logger; private readonly IUserRepository _userRepository; // Inject the repository public listUsers(ILoggerFactory loggerFactory, IUserRepository userRepository) { _logger = loggerFactory.CreateLogger<listUsers>(); _userRepository = userRepository; } [Function("listUsers")] public async Task Run([TimerTrigger("0/10 * * * * *")] CancellationToken cancellationToken) // Example: Timer trigger { var users = await _userRepository.GetUsers(cancellationToken); // Use the repository foreach (var user in users) { _logger.LogInformation($"Username: {user.Name}, Email: {user.Email}"); } } } } 
Enter fullscreen mode Exit fullscreen mode

Run the Projects

Start Azurite (if needed): If your Azure Function uses any triggers or bindings that require Azure Storage (like Queue Storage, Blob Storage, or Table Storage), you'll need to start Azurite, the local Azure Storage emulator.

  • Using the VS Code Extension: The easiest way is often through the Azurite extension in VS Code. Install the "Azurite" extension. Then, use Ctrl+Shift+P (or Cmd+Shift+P on macOS) and type "Azurite: Start." This will start Azurite. The extension usually configures the connection strings automatically.
  • Start the API project: open a terminal, navigate to the api directory, and run dotnet run.
  • Start the Functions project: open a new terminal window, navigate to the functions project, and run the command func start --dotnet-isolated
  • Verify: Observe the output in the functions terminal. You should see the users being logged there, confirming that the function is running correctly and accessing the API.

Top comments (0)