Skip to main content

Azure Blob Storage - Design & Implementation

Version: 1.0 Date: October 20-21, 2025 Status: ✅ Implemented and Complete Feature: EPANET Input File Cloud Storage


Table of Contents

  1. Overview
  2. Architecture
  3. Implementation Summary
  4. Storage Services
  5. Frontend Integration
  6. Security
  7. Future Enhancements

1. Overview

1.1 Purpose

Migrate EPANET input file storage from local file system to Azure Blob Storage for:

  • Improved scalability
  • Centralized management
  • Cloud-native architecture
  • Multi-environment file sharing

1.2 Migration Complete ✅

Before:

  • Scenarios stored input file paths as strings (InputFile column)
  • Files stored on local file system only
  • Each EpanetConsoleCore instance needed local file access
  • No centralized file management

After:

  • Input files stored in Azure Blob Storage (with local fallback)
  • InputFile as a proper database entity with FK relationships
  • EpanetConsoleCore downloads files to temp directories on demand
  • Centralized file management through API
  • Full CRUD operations via frontend

1.3 Key Design Decisions

No File Validation on Upload

  • Files stored as-is without parsing or validation
  • EPANET validates during simulation runs
  • Simplifies upload process

Container-per-Network Architecture

  • Each network/client gets its own blob container
  • Scoped access using SAS tokens (not account keys)
  • Better security isolation and access control

Backward Compatibility

  • Supports legacy InputFile string paths during migration
  • Graceful fallback to local file system

2. Architecture

2.1 System Overview

┌─────────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ - Upload INP files │
│ - Manage input files │
│ - Assign files to scenarios │
└──────────────────────┬──────────────────────────────────────┘
│ HTTPS
┌──────────────────────▼──────────────────────────────────────┐
│ API (ASP.NET Core) │
│ - InputFileController (upload, list, download) │
│ - ScenarioController (modified to use InputFileId) │
│ - IInputFileStorageService (abstraction) │
└──────────────────────┬──────────────────────────────────────┘

┌───────────────┴───────────────┐
│ │
┌──────▼───────┐ ┌───────▼────────┐
│ Azure │ │ Local File │
│ Blob │ │ System │
│ Storage │ │ (Dev/Test) │
└──────┬───────┘ └───────┬────────┘
│ │
└───────────────┬───────────────┘
│ SAS Token (scoped)
┌──────────────────────▼──────────────────────────────────────┐
│ EpanetConsoleCore (Simulation) │
│ - Downloads files to temp directories │
│ - Automatic cleanup after simulation │
│ - Uses container-scoped SAS tokens (read-only) │
└─────────────────────────────────────────────────────────────┘

2.2 Database Schema

InputFile Entity:

public class InputFile
{
public int Id { get; set; }
public string Name { get; set; } // User-friendly name
public string Description { get; set; }
public string ContainerName { get; set; } // Azure container
public string BlobName { get; set; } // File identifier
public string FilePath { get; set; } // Local fallback
public long FileSize { get; set; }
public StorageProvider StorageProvider { get; set; } // AzureBlob | LocalFileSystem
public bool IsActive { get; set; }
public DateTime UploadedDate { get; set; }

// Navigation
public virtual ICollection<Scenario> Scenarios { get; set; }
}

public enum StorageProvider
{
AzureBlob = 0,
LocalFileSystem = 1
}

Scenario Entity (Modified):

public class Scenario
{
// ...existing properties...

// New FK relationship
public int? BaseInputFileId { get; set; }
public virtual InputFile BaseInputFile { get; set; }

// Legacy support (kept for backward compatibility)
public string InputFile { get; set; }
}

2.3 Storage Provider Interface

public interface IInputFileStorageService
{
Task<string> UploadFileAsync(string containerName, string blobName,
Stream fileStream, CancellationToken cancellationToken = default);

Task<Stream> DownloadFileAsync(string containerName, string blobName,
CancellationToken cancellationToken = default);

Task<string> DownloadToTempFileAsync(string containerName, string blobName,
CancellationToken cancellationToken = default);

Task<bool> DeleteFileAsync(string containerName, string blobName,
CancellationToken cancellationToken = default);

Task<bool> ExistsAsync(string containerName, string blobName,
CancellationToken cancellationToken = default);

Task<long> GetFileSizeAsync(string containerName, string blobName,
CancellationToken cancellationToken = default);
}

3. Implementation Summary

Phase 1: Database Schema & Models ✅

Files Created:

  1. efcorelibraries/HTModels/Models/EN2Models/InputFile.cs
  2. Modified Scenario.cs with new FK relationship
  3. Updated EN2PostgresContext.cs with DbSet and relationships

Phase 2: Storage Services ✅

Shared Services Location: efcorelibraries/HTModels/Services/

Files:

  1. IInputFileStorageService.cs - Interface
  2. AzureBlobStorageService.cs - Azure implementation
    • Supports both account key (API) and SAS token (Console) modes
    • Auto-detects mode based on configuration
  3. LocalFileStorageService.cs - Local file system implementation
    • Supports legacy paths for migration

Service Registration:

  • API: Registered in Program.cs based on configuration
  • Console: Configured via appsettings.json

Phase 3: API Layer ✅

New Controller: InputFileController.cs

Endpoints:

  • POST /api/inputfile/upload - Upload file with metadata
  • GET /api/inputfile - List all input files
  • GET /api/inputfile/{id} - Get single file metadata
  • GET /api/inputfile/{id}/download - Download file
  • PUT /api/inputfile/{id} - Update metadata
  • DELETE /api/inputfile/{id} - Soft delete file

Modified Controller: ScenarioController.cs

  • Updated to support BaseInputFileId FK
  • Maintains backward compatibility with legacy InputFile paths

Phase 4: EpanetConsoleCore Integration ✅

Modified Files:

  1. Program.cs - Integrated storage service
  2. Simulation workflow updated to:
    • Download files to temp directory when using blob storage
    • Use local paths for legacy scenarios
    • Automatic temp file cleanup after simulation

Key Features:

  • Uses container-scoped SAS tokens (read-only)
  • No account keys stored in console configuration
  • Automatic temp directory management
  • Fallback to local file system if configured

4. Storage Services Consolidation

Refactoring Completed ✅

Problem: Originally duplicated storage service code between API and Console projects (~600 lines duplicated).

Solution: Consolidated into shared location (efcorelibraries/HTModels/Services/)

Benefits:

  • ✅ Single source of truth
  • ✅ No code duplication
  • ✅ Fix once, benefits all projects
  • ✅ Consistent behavior
  • ✅ Easier testing and maintenance

Service Implementation Details

AzureBlobStorageService:

  • Supports two authentication modes:
    1. Account Key Mode (API): Full access for upload/management
    2. SAS Token Mode (Console): Scoped read-only access
  • Auto-detects mode based on configuration presence
  • Container-level isolation for security

LocalFileStorageService:

  • Development and testing support
  • Supports both legacy paths and new structure
  • Maintains folder hierarchy
  • Automatic directory creation

5. Frontend Integration

Input Files Management Page ✅

Location: /input-files

Features Implemented:

  1. Upload Dialog - File upload with drag-and-drop

    • Name, description, container selection
    • Upload progress indicator
    • File size validation
    • .inp file type filtering
  2. File Table View - Complete file listing

    • Columns: Name, Description, Container, Storage Provider, Size, Date, Status
    • Actions: Download, Edit, Delete
    • Storage provider badges (Azure Blob / Local)
    • Active/Inactive status chips
  3. Edit Dialog - Update file metadata

    • Edit name, description, active status
    • Read-only: Container, blob name, storage provider
  4. Delete Confirmation - Safe deletion with warnings

Scenario Integration ✅

Scenario Create/Edit Dialog:

  • Dropdown to select Base Input File
  • Displays: File name, container, size
  • Link to manage files
  • Backward compatibility with legacy paths
  • Migration helper for old scenarios
  • Added "Input Files" to sidebar menu
  • Route configured and protected
  • Folder icon for menu item

API Service Layer ✅

File: src/services/inputFileService.js

Functions:

  • uploadInputFile() - With progress tracking
  • getInputFiles() - List all files
  • getInputFile(id) - Get single file
  • downloadInputFile(id) - Download file
  • updateInputFile(id) - Update metadata
  • deleteInputFile(id) - Delete file

6. Security

Security Model

API Tier:

  • Uses account-level keys for upload/management operations
  • Keys stored in appsettings (use Azure Key Vault in production)
  • Full CRUD access to blob storage

EpanetConsoleCore:

  • Uses container-scoped SAS tokens (read-only)
  • Tokens generated by API with expiration
  • No account keys in console configuration
  • Principle of least privilege

Container Isolation

  • Each network/client gets dedicated container
  • Scoped access prevents cross-container access
  • SAS tokens limit permissions and duration

Best Practices

Implemented:

  • Container-scoped access
  • Read-only tokens for simulation engine
  • Automatic token expiration
  • Soft delete for audit trail

💡 Recommended for Production:

  • Use Azure Key Vault for account keys
  • Enable Managed Identity authentication
  • Implement SAS token rotation
  • Enable blob storage logging and monitoring

7. Future Enhancements

NetworkManager Integration 💡

Status: Idea - Not Implemented Priority: LOW

Concept:

  • Upload source INP file when importing networks
  • Create audit trail of network imports
  • Enable network versioning
  • Re-import from stored files

Use Cases:

  1. Network import audit trail
  2. Re-import from previously stored files
  3. Network versioning and rollback capability

Architecture:

Local INP file

NetworkManager parses

Database updated (Nodes, Links, etc.)

Upload INP file to blob storage

Create InputFile + NetworkImport records

Preserve original file for audit/rollback

Other Potential Enhancements

  1. File Versioning - Multiple versions of same file
  2. Bulk Upload - Upload multiple files at once
  3. File Templates - Library of template INP files
  4. File Comparison - Compare two INP files
  5. File Validation - Optional pre-validation before storage
  6. CDN Integration - Faster downloads via CDN
  7. File Compression - Compress large files for storage

Summary

Complete Implementation:

  • Database schema with InputFile entity
  • Dual storage providers (Azure Blob + Local)
  • API layer with full CRUD operations
  • Frontend file management interface
  • EpanetConsoleCore integration
  • Security with scoped SAS tokens
  • Service consolidation eliminating code duplication

Production Ready:

  • Backward compatible with legacy paths
  • Automatic temp file cleanup
  • Error handling and logging
  • Configurable via appsettings

Well Documented:

  • Configuration guides available
  • API documentation in Swagger
  • Frontend component documentation
  • Security best practices documented

Next Steps:

  • Deploy to test environment
  • Migrate existing scenarios to use InputFile entities
  • Monitor performance and adjust as needed
  • Consider future enhancements based on usage patterns

Related Documentation:

  • Configuration Guide: configuration.md
  • API Reference: Available in Swagger UI
  • Database Migration: See efcorelibraries documentation