8.2 KiB
IoT Edge Module Structure Reference
This document describes the standard structure and files for IoT Edge modules in this project.
Module Directory Structure
Each module follows this standard structure:
<modulename>/ # Lowercase with "module" suffix
├── .dockerignore # Docker build exclusions
├── .gitignore # Git exclusions (bin/, obj/)
├── Dockerfile.amd64 # Production build
├── Dockerfile.amd64.debug # Debug build with vsdbg
├── module.json # IoT Edge module metadata
├── <ModuleName>.csproj # .NET 9.0 project file
├── Program.cs # Application entry point
├── GlobalUsings.cs # Global namespace imports
├── LoggingEventIdConstants.cs # Logging event IDs
├── <ModuleName>Service.cs # Main hosted service
├── <ModuleName>ServiceLoggerMessages.cs # Logging messages
├── Properties/
│ └── launchSettings.json # Local debugging configuration
├── Services/ # Optional: Business logic services
├── Contracts/ # Optional: Module-specific contracts
├── Options/ # Optional: Configuration option classes
├── Jobs/ # Optional: Quartz scheduler jobs
└── [Other domain-specific folders]
Required Files
1. module.json
Purpose: IoT Edge module metadata for build and deployment.
Location: <modulename>/module.json
Schema:
{
"$schema-version": "0.0.1",
"description": "Module description",
"image": {
"repository": "yourregistry.azurecr.io/<modulename>",
"tag": {
"version": "0.0.${BUILD_BUILDID}",
"platforms": {
"amd64": "./Dockerfile.amd64",
"amd64.debug": "./Dockerfile.amd64.debug"
}
},
"buildOptions": [],
"contextPath": "../../../"
},
"language": "csharp"
}
Key fields:
repository: Azure Container Registry URL + lowercase module nameversion: Uses${BUILD_BUILDID}for CI/CD buildsplatforms: Maps platform to DockerfilecontextPath: Points to repo root (../../../) for multi-project Docker builds
2. .csproj
Purpose: .NET project configuration.
Target framework: net9.0
Output type: Exe (console application)
Docker target OS: Linux
Required dependencies:
Atc- Common utilitiesAtc.Azure.IoTEdge- IoT Edge abstractionsMicrosoft.Azure.Devices.Client- IoT Hub SDKMicrosoft.Extensions.Hosting- Generic Host
Optional project reference:
- Shared contracts project (e.g.,
Company.ProjectName.Modules.Contracts) - Shared constants and contracts across modules
3. Program.cs
Purpose: Application entry point using .NET Generic Host.
Standard pattern:
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddLogging(builder =>
{
builder.AddModuleConsoleLogging();
});
if (hostContext.IsStandaloneMode())
{
services.AddSingleton<IModuleClientWrapper, MockModuleClientWrapper>();
}
else
{
services.AddModuleClientWrapper(TransportSettingsFactory.BuildMqttTransportSettings());
}
services.AddSingleton<IMethodResponseFactory, MethodResponseFactory>();
// Add your service registrations here
services.AddHostedService<YourModuleService>();
})
.UseConsoleLifetime()
.Build();
await host.RunAsync();
Key components:
AddModuleConsoleLogging()- Structured console loggingIsStandaloneMode()- Detects local vs. edge runtimeAddModuleClientWrapper()- IoT Hub connectivity with MQTTAddHostedService<>()- Main service registration
4. Main Service File
Purpose: Main module logic as a BackgroundService.
Naming: <ModuleName>Service.cs
Responsibilities:
- Open IoT Hub connection on startup
- Register direct method handlers
- Implement core module logic
- Handle graceful shutdown
5. GlobalUsings.cs
Purpose: Global namespace imports for cleaner code.
Standard imports:
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
6. LoggingEventIdConstants.cs
Purpose: Centralized logging event IDs.
Standard event IDs:
1000- ModuleStarting1001- ModuleStarted1002- ModuleStopping1003- ModuleStopped
7. ServiceLoggerMessages.cs
Purpose: Compile-time logging using source generators.
Pattern:
internal static partial class YourModuleServiceLoggerMessages
{
[LoggerMessage(
EventId = LoggingEventIdConstants.ModuleStarting,
Level = LogLevel.Information,
Message = "Module is starting")]
internal static partial void LogModuleStarting(this ILogger logger);
}
8. Dockerfile.amd64
Purpose: Production Docker image build.
Multi-stage build:
- Build stage: .NET SDK 9.0, restore dependencies, publish release build
- Runtime stage: .NET Runtime 9.0, non-root user, security hardening
Security features:
- Non-root user (
moduleuser, UID 2000) - TPM access group (GID 3000)
- Minimal runtime image
9. Dockerfile.amd64.debug
Purpose: Debug Docker image with remote debugging support.
Additional features:
- vsdbg debugger installation
- Debug build configuration
10. .dockerignore
Purpose: Exclude files from Docker build context.
Excludes: bin/, obj/, .git, .vs, node_modules, etc.
11. .gitignore
Purpose: Exclude build artifacts from Git.
Excludes: bin/, obj/
12. Properties/launchSettings.json
Purpose: Local debugging configuration.
Required environment variables:
IOTEDGE_MODULEID- Module identifierEdgeHubConnectionString- Local connection string for standalone modeEdgeModuleCACertificateFile- Certificate file path (can be empty)
Shared Contracts
Module Constants
Location: <contracts-project-path>/<ModuleName>/<ModuleName>Constants.cs
Purpose: Shared constants for module identification and direct methods.
Structure:
namespace Company.ProjectName.Modules.Contracts.<ModuleName>;
public static class <ModuleName>Constants
{
public const string ModuleId = "<modulename>";
// Direct method names
public const string DirectMethodExample = "ExampleMethod";
}
Naming Conventions
- Module directory: Lowercase with "module" suffix (e.g.,
mynewmodule) - C# classes: PascalCase without "module" suffix (e.g.,
MyNewModule) - Namespace: PascalCase matching class name (e.g.,
namespace MyNewModule;) - Constants file:
<ModuleName>Constants.csin shared contracts - Dockerfile:
Dockerfile.amd64andDockerfile.amd64.debug
Configuration Pattern
Modules use IOptions<T> for configuration:
-
Define options class:
public class MyModuleOptions { public string Setting { get; set; } } -
Register in DI:
services.Configure<MyModuleOptions>(hostContext.Configuration); -
Inject options:
public MyService(IOptions<MyModuleOptions> options) -
Set via environment variables in deployment manifest:
"env": { "MyModuleOptions__Setting": { "value": "value" } }
Optional Folders
Services/- Business logic and integration servicesContracts/- Module-specific data contracts (not shared)Options/- Configuration option classesJobs/- Quartz scheduler job definitions (requiresAddQuartz())Filters/- Domain-specific filtering logicProviders/- Factory patterns, client providersPublishers/- Message publishersScrapers/- Data scraping logic
README.md Documentation
When creating a new module, update README.md in the repository root:
Section: "Solution project overview for IoTEdge modules"
Add your module to the list with a brief description of its purpose.