iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
📘

Modernizing ASP.NET Applications: Infrastructure Strategy

に公開

Introduction

At the .NET Lab study session in April 2025, I discussed the modernization of ASP.NET applications from the application perspective. In this article, I will focus on infrastructure-side considerations for running Web applications, discussing how to select services from an application developer's standpoint, including the thought process involved. 🚀

What is Modernization?

Modernization is the process of refreshing an aging existing system to meet current needs.

In this article, I refer to refreshing systems built on .NET Framework to a .NET base and, if necessary, refreshing the UI. Specifically, we are assuming migration to targets such as the following:

  • 🌐 Blazor Web Apps - Hybrid rendering for server-side and client-side
  • 🖥️ Blazor WebAssembly - .NET execution on the client-side
  • 🏗️ ASP.NET Core MVC - Inheriting the traditional MVC pattern
  • 🔌 ASP.NET Core Web API - Building RESTful APIs

Why must we modernize?

There is a saying, "If it ain't broke, don't fix it," but leaving legacy systems alone carries many risks. 🤔

1. Clarification of costs 💰

Especially when considering migration to the cloud, pay-as-you-go billing makes costs visible. Infrastructure costs that were difficult to see in an on-premises environment become clear, creating opportunities for optimization.

2. Access to the latest features 🤖

Cutting-edge technologies like AI assume the use of modern frameworks. There are increasing numbers of features and libraries that cannot be used in .NET Framework, leading to a decline in business competitiveness.

3. Security issues 🔒

In some cases, old frameworks are not fixed even when vulnerabilities are discovered. Continuing to operate a system for which security patches are no longer provided is a major risk for an organization.

4. Performance issues ⚡

.NET Core and .NET have undergone significant performance improvements compared to .NET Framework. You can achieve higher throughput on the same hardware.

Classification of .NET Framework-based applications

The difficulty of migration varies greatly depending on the type of application. 📊

Group that is relatively easy to migrate ✅

Because similar frameworks exist in .NET, migration can proceed relatively smoothly:

Framework Migration Target
Windows Forms Windows Forms (.NET)
Console App Console App (.NET)
ASP.NET MVC ASP.NET Core MVC
WCF (Client) CoreWCF / gRPC

Group that is difficult to migrate ⚠️

Because similar frameworks do not exist in .NET, a review of the architecture is necessary:

Framework Challenges
ASP.NET Web Forms No equivalent framework exists. Requires rewriting to Blazor
WCF (Server) Requires migration to CoreWCF or redesign to gRPC

Regarding .NET Support Periods

When planning modernization, understanding support periods is crucial. 📅

  • 🏢 .NET Framework - Follows OS support periods (synchronized with Windows Server)
  • 🟢 .NET LTS (e.g., .NET 8) - 36 months of support from release
  • 🟡 .NET STS (e.g., .NET 9) - 24 months of support from release

You must also consider runtime support for Azure resources. Since .NET is supported on services like App Service immediately upon release, you can perform cloud-native development smoothly. ☁️


Still, Azure is difficult for developers

If you have read this far and thought, "Alright, let's move to Azure!" please wait a moment. To be honest, Azure is difficult for developers. 😅

There are over 200 services, and skill sets different from application development, such as networking, security, and identity management, are required. However, that is exactly why you should study it!

To grasp the overall picture of Azure, systematic learning is effective:

  • 📘 Azure Knowledge Map (ISBN: 978-4-297-14903-1) - Covers everything from cloud basics to implementation and operations management
  • 🎓 Microsoft Learn - A free online learning platform
  • 📜 Azure Certification - Start from AZ-900 and learn systematically

Read the Azure Cloud Adoption Framework (CAF)

To succeed in modernization, you need not only technology but also organizational preparation. Microsoft provides the Cloud Adoption Framework (CAF) as a best practice for Azure adoption. 🏗️

Elements of CAF

CAF is composed of the following phases:

Phase Content
🎯 Cloud Adoption Motivation Clarifying business goals and reasons for moving to the cloud
📋 Adoption Strategy Migration approach and prioritization
🛬 Ready (Landing Zone) Building a secure and scalable foundation environment
🚀 Adopt Migration, modernization, and innovation
🔧 Govern Operations, monitoring, and optimization
🔒 Security Zero trust and compliance support

Modernization adoption strategy

CAF emphasizes the following two perspectives in modernization:

1. Modernizing processes 🔄

  • Adopt DevOps practices - Remove the barriers between development and operations to establish a continuous improvement cycle
  • Adopt CI/CD for rapid delivery - Automation using GitHub Actions or Azure Pipelines

2. Modernizing applications and databases 💾

  • Adopt PaaS solutions - Reduce the burden of infrastructure management and focus on application development

Read the Architecture Center

For the question, "How should I design it specifically?" the Azure Architecture Center provides the answer. 📐

Design patterns based on real examples

In the Architecture Center, instructions on how to design are explicitly provided based on real examples. For instance:

  • 🌐 Reliable Web App Pattern for .NET - Multi-region configuration, private endpoints, and caching strategies
  • 🔌 Microservices architecture - Inter-service communication and event-driven design
  • 📊 Data analysis pipeline - Combinations of real-time and batch processing

A typical .NET Web application architecture includes the following elements:

  • 🌍 DNS + Web Application Firewall + Load Balancer
  • 🔐 Identity and Access Management (Entra ID)
  • 🖥️ Application Platform (Web App Code)
  • 📈 Application Performance Monitoring (Application Insights)
  • 🔒 Virtual Network + Private Endpoints
  • 💾 Cache + Database + Other Azure Services

Application Modernization: Thinking about service selection

Here comes the main point. How do you choose the service to host an ASP.NET Core application? 🤔

Balance between solution control and productivity

The most important perspective in service selection is the trade-off between control (flexibility) and productivity (development speed).

Control and Productivity Solution Description
🔧 Maximum Control AKS, Azure Red Hat OpenShift Allows infrastructure control. Operations can be a bit demanding
⚖️ Balance Azure App Service, Azure Spring Apps, Azure Functions Focus on code development. Infrastructure automation
🚀 Maximum Productivity Power Apps, Power Automate Shortest lead time. Low-code/No-code

Recommendations for ASP.NET Core developers

For ASP.NET Core applications, balanced-type services are appropriate in many cases:

  • 🌐 Azure App Service - The most common choice. Optimal for web apps and APIs
  • Azure Functions - Event-driven processing. Optimal for microservices
  • 🐳 Azure Container Apps - Container-based while hiding Kubernetes complexity

However, maximum control might be necessary in the following cases:

  • 🔧 Special runtime requirements exist
  • 🌐 Complex network configurations are required
  • 📦 You want to leverage existing Kubernetes manifests

IaaS or PaaS?

The first issue you will face when selecting infrastructure for modernization is "IaaS or PaaS?" 🤔

Reasons to choose PaaS

In the context of modernization, choosing PaaS is obviously the correct path.

  • Relief from all operational headaches - You can leave OS patching and middleware updates to Azure
  • Clearly recommended by CAF - In the context of modernization, PaaS selection is explicitly stated
  • Scalability - Easy automatic scaling based on demand
  • Focus on development - Spend time on business logic implementation rather than infrastructure management

Cases where IaaS (VM) is chosen

It is important to adopt the mindset that IaaS (VM) is only used when something absolutely cannot be done with PaaS:

Case Reason
🖥️ Temporary migration target for legacy apps Moving them just to get them running via lift-and-shift
🔧 Special software requirements Middleware not supported by PaaS
📜 Licensing constraints Permission to run only on specific VMs
🔒 Regulatory requirements Requires a dedicated computing environment

Can it be made stateless?

An unavoidable topic when choosing PaaS is "stateless design." 🔄

Why statelessness is important

Essentially, applications should be redesigned to be stateless. The biggest reason for this is:

⚠️ You cannot scale out if the application maintains state

During scale-out (horizontal scaling), multiple instances process requests simultaneously. If the state stored in instance A cannot be accessed by instance B, the user will not have a consistent experience. 😵

Stateful vs. Stateless

Design Operation Scalability
😢 Stateful (Traditional) Session stored in instance -> Fixed to same instance Scale-out difficult
😊 Stateless (Recommended) Can be processed by any instance -> State shared via external storage (Redis, etc.) Scale-out easy

What about sessions?

A particular issue for ASP.NET Core MVC applications is "session management." 🍪

Problem

In traditional ASP.NET applications, it was common to keep session information in the application server's memory (InProc). However:

  • ❌ Keeping it in the application server makes scale-out impossible
  • ❌ Sessions disappear when the instance restarts
  • ❌ Sessions cannot be shared between multiple instances

Solution: Azure Managed Redis

By ensuring that information is kept in services like Azure Managed Redis (formerly Azure Cache for Redis), you can resolve these issues. 🚀

⚠️ Note: Azure Cache for Redis is scheduled for retirement, and migration to Azure Managed Redis as the successor service is recommended.

// Configuration example in Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "MyApp_";
});

// Enable distributed sessions
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

Benefits of Azure Managed Redis

Benefit Description
🔄 High availability Supports active geo-replication and failover
Fast Low latency with in-memory data store
📈 Scalable Horizontal scaling via clustering
🔒 Secure VNet integration and private endpoint support
🆕 Latest features Supports Redis 7.4, RediSearch, RedisBloom, and other modules

State management other than sessions

Besides sessions, the following states must also be externalized:

  • 📁 File uploads -> Azure Blob Storage
  • 🔐 Authentication tokens -> Azure Managed Redis / Database
  • 📊 Temporary calculation results -> Azure Managed Redis
  • 📝 User settings -> Database (Azure SQL, Cosmos DB, etc.)

Selecting Computing Resources

From here, let's look at specific Azure services for hosting ASP.NET Core applications. 🖥️

The first choice is App Service

In conclusion, the first choice is Azure App Service.

This is because it is the most likely to provide the best results with the minimum amount of changes for existing applications. It is efficient to first consider whether your requirements can be met by App Service, and then consider other services only if they cannot.

Azure App Service

First, consider if App Service can be used

App Service is a service that should be considered as the first choice for both code-based and container-based applications. 🌟

Feature Description
🔧 Easiest for operations Leave OS patching and runtime updates to Azure
📈 Multiple instances Handles load with automatic scaling
💎 Select Premium plan VNet integration and private endpoint support

Key features of App Service

  • Deployment slots - Swap after testing in a staging environment
  • Automatic scaling - Scale-out based on CPU/memory usage
  • Custom domains & SSL - Support for custom domains and HTTPS
  • Authentication/Authorization - Easy Auth (built-in authentication)
  • Application Insights integration - Performance monitoring

Use the Premium plan for production environments

While Basic or Standard plans are fine for development and testing environments, we recommend the Premium plan (P1v3 or higher) for production environments:

  • 🔒 VNet integration - Secure communication within a private network
  • 🚀 Higher performance - More CPU and memory
  • 📊 Zone redundancy - Realizing high availability

App Service Managed Instance (Preview) 🆕

App Service Managed Instance is a new hosting option that supports the phased PaaS migration of legacy systems.

Consider Managed Instance in these cases

Use case Description
🏛️ Legacy Windows compatibility Requires COM components, registry changes, or MSI installers
🔧 IIS Manager access Customizing IIS settings or RDP access for diagnostics
📁 Network shares Requires UNC paths or drive mapping
🔄 Migration with minimal refactoring "Lift and Improve" migration without complete rewrites

Comparison with standard App Service

Feature Standard App Service Managed Instance
OS Windows / Linux Windows Only
COM components
Registry access
MSI installers
RDP access ✅ (For diagnostics)
PowerShell installation scripts
Custom fonts
GAC (Global Assembly Cache)

Main features

  • 📜 Configuration (installation) scripts - Configure OS/middleware at startup using PowerShell scripts
  • 🔐 Registry adapter - Safely manage registry keys via Azure Key Vault integration
  • 📂 Storage mounting - Supports Azure Files, UNC paths, and drive mapping
  • 🖥️ RDP access - Just-in-Time diagnostic access via Azure Bastion
  • 🔒 Plan-level VNet integration - Private network isolation

Phased migration scenarios

Managed Instance bridges the gap between "complete cloud-native" and "IaaS Lift & Shift."

Migration strategy Target Service
🚀 Replatform (Cloud Native) Modern .NET Core apps Standard App Service
🔄 Lift and Improve Legacy .NET Framework apps with dependencies Managed Instance
📦 Lift and Shift Legacy apps that cannot be modified Azure VM (IaaS)
// Example: Legacy code usable with Managed Instance
// Utilizing COM components
var excelApp = new Microsoft.Office.Interop.Excel.Application();

// Registry access
var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\MyApp");

// Rendering using a custom font
using var font = new Font("MyCustomFont", 12);

Usage notes

  • ⚠️ Changes made via RDP are not persistent - They are lost upon restart or maintenance
  • ✅ Always perform persistent configuration via installation scripts
  • 🔐 Centrally manage secrets in Key Vault

5 📚 Details: App Service Managed Instance documentation

Azure Static Web Apps

Essentially designed to be used with Azure Functions

Azure Static Web Apps is a service specialized for hosting static content. 📄

Usage Description
🖥️ Consider for Blazor WebAssembly Best for apps that are self-contained on the client side
🔌 When backend is a Web API Consider combining with App Service

Configuration patterns

Pattern Frontend Backend Usage
Pattern 1 Static Web Apps Built-in Azure Functions Simple API integration
Pattern 2 Static Web Apps Azure App Service (Web API) Complex processing or utilizing existing APIs

💡 If you need complex processing on the backend or want to leverage an existing ASP.NET Core Web API, consider combining it with App Service.

Azure Functions

Build as a backend with HTTP triggers

Azure Functions is an event-driven serverless computing service. ⚡

Feature Description
💰 Likely the lowest cost for small-scale Web systems Pay only for what you use with the Consumption plan
🔌 HTTP trigger Can be used as a REST API
🎯 Microservices Ideal for breaking down into functional units

Cases where Functions are suitable

  • ✅ APIs with low to medium request frequency
  • ✅ Batch processing or scheduled execution
  • ✅ Event-driven processing (queues, Blob changes, etc.)
  • ✅ Webhook endpoints

Cases where Functions are not suitable

  • ❌ Requires persistent connections (WebSockets, etc.) → Consider App Service
  • ❌ Requires long-running processing (more than 10 minutes) → Consider Durable Functions or other services
  • ❌ Requires complex routing → Consider App Service

Azure Spring Apps

⚠️ New adoption should be avoided as it has been announced for retirement

Since Azure Spring Apps has been announced for retirement, it should not be adopted for new projects going forward. 🚫

If you are currently using Azure Spring Apps, consider migrating to the following:

Migration Destination Description
🐳 Azure Container Apps The closest choice as a container-based PaaS
🌿 VMware Tanzu If you need an environment dedicated to Spring

📝 While this service is irrelevant for ASP.NET Core developers, organizations with Java teams need to plan for migration.

Azure Container Apps

Consider this after App Service

Azure Container Apps provides the flexibility of containers while abstracting away the complexities of Kubernetes. 🐳

Feature Description
🎯 Container Native Deploy Docker images as they are
📈 Auto Scaling Based on KEDA (Kubernetes Event-driven Autoscaling)
🔄 Microservices Support Service-to-service communication via Dapr

Cases to choose Container Apps

  • ✅ Containerized applications
  • ✅ Microservices architecture
  • ✅ Requirement for event-driven scaling
  • ✅ Avoiding the operational burden of Kubernetes

Azure Kubernetes Service (AKS)

Consider for large-scale microservices

AKS provides maximum control, but it also comes with a significant operational burden. ☸️

Consideration Description
👨‍💻 Requires experienced infrastructure personnel for operation Kubernetes has a high learning curve
💰 Not cost-effective for small-scale systems Consider TCO, including human resources costs
🎯 Consider for large-scale microservices When managing tens to hundreds of services

AKS Automatic: A new option to reduce operational burden 🆕

AKS Automatic is a new option that significantly reduces the operational burden of traditional AKS (AKS Standard).

4> AKS Automatic makes the most common Kubernetes tasks fast and smooth, while maintaining the flexibility and scalability of Kubernetes.

Features of AKS Automatic

Feature Description
🚀 Production ready by default Pre-configured for production environments. Node pools are managed and scaled automatically
🔒 Built-in best practices and safeguards Security settings enabled by default. Automatic patching of nodes and cluster components
Code to Kubernetes in minutes Deploy applications following best practices from container images in minutes

AKS Standard vs AKS Automatic

Perspective AKS Standard AKS Automatic
Node Management Manually create/manage node pools Automatically managed via Node Autoprovisioning
Scaling Manual or Cluster Autoscaler Automatic (HPA, KEDA, VPA enabled)
Security Configurable as an option Deployment Safeguards enabled by default
Networking Choice of multiple options Azure CNI Overlay with Cilium (pre-configured)
SLA Choose from Free/Standard/Premium Standard tier + Pod readiness SLA (99.9%)

Cases to choose AKS Automatic

  • ✅ Want to use Kubernetes but minimize operational burden
  • ✅ Want to automatically apply settings based on best practices
  • ✅ Container Apps is not flexible enough, but AKS Standard is too complex

4> 💡 Point: AKS Automatic is positioned between Container Apps and AKS Standard. Consider it if you want to maintain Kubernetes flexibility while reducing operational burden.

Cases to choose AKS

  • ✅ Large-scale microservices architecture
  • ✅ Want to leverage existing Kubernetes manifests
  • ✅ Advanced network control required
  • ✅ Multi-cloud/Hybrid cloud strategy

Cases to avoid AKS

  • ❌ No Kubernetes experts on the team
  • ❌ Small number of applications (approx. 1–5)
  • ❌ Want to minimize operational burden

The Batch Processing Problem

In the modernization of web applications, batch processing is also an important consideration. 🔄

In traditional .NET Framework applications, there are many cases where batch processing was executed using the Windows Task Scheduler or console applications, but how should this be handled in a cloud environment?

Expanding Options: WebJobs on Linux

Linux WebJobs have reached GA, expanding the available options. This allows background tasks to be executed on App Service.

Option Description
🖥️ WebJobs (Windows) Traditional option. Executed on App Service
🐧 WebJobs (Linux) Now also available on Linux App Service
Azure Functions Highly flexible. Recommended as the first choice

Azure Functions as the First Choice

That said, Azure Functions is the first choice in terms of flexibility.

Azure Functions offers the following benefits:

  • Diverse Triggers - Timer, queue, Blob, HTTP, etc.
  • Consumption Plan - Pay only for execution time, ideal for small-scale batches
  • Scalability - Easy parallel execution
  • Monitoring & Logging - Integration with Application Insights

Considerations when using Azure Functions for Batching

The biggest consideration when using Azure Functions for batch processing is execution time. ⏱️

Plan Maximum Execution Time Usage
🆓 Consumption Plan 10 minutes (default 5 min) Short-duration batch processing
💎 Premium Plan Unlimited (default 30 min) Long-duration batch processing
🔧 Dedicated (App Service) Plan Unlimited When constant execution is required
Flex Consumption Plan Unlimited (default 30 min) Combining scalability with long execution times

⚠️ Note: Standard consumption-based Functions cannot be used for tasks that take a long time. Consider the Premium plan or Flex Consumption plan.

Long-Running Processes with Durable Functions

For long-running processes or complex workflows, Durable Functions is effective:

// Example of an orchestrator function
[FunctionName("BatchOrchestrator")]
public static async Task RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var items = await context.CallActivityAsync<List<Item>>("GetItems", null);
    
    // Parallel processing
    var tasks = items.Select(item => 
        context.CallActivityAsync("ProcessItem", item));
    
    await Task.WhenAll(tasks);
    
    await context.CallActivityAsync("SendNotification", "Completed");
}

Durable Task Scheduler (DTS): High-Performance Backend Provider 🆕

Since business batch processing is mostly about data processing, it is well-suited for the Durable Task Scheduler (DTS).

DTS is currently available in preview and is a new backend provider for Durable Functions. It achieves approximately 5x the throughput compared to the traditional Azure Storage provider.

Key Features of DTS

Feature Description
🚀 High Performance Approximately 5x throughput of the Azure Storage provider
📊 Built-in Dashboard Easy monitoring and management of orchestrations
🔧 Fully Managed No need to manage storage accounts
🔐 Managed Identity Support Secure authentication

How to use DTS

Existing Durable Functions apps can use DTS without code changes. You can migrate simply by changing the host.json settings:

{
  "extensions": {
    "durableTask": {
      "hubName": "%TASKHUB_NAME%",
      "storageProvider": {
        "type": "azureManaged",
        "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
      }
    }
  }
}

DTS Pricing Models

Plan Description
💎 Dedicated Stable performance with reserved resources
Consumption (Preview) Pay-per-action pricing

💡 DTS is currently in preview, but it is highly anticipated as a savior for business-critical batch processing! Can't wait for it to reach GA 🎉

Selection Flow for Batch Processing

Condition Recommended Service
Execution time within 10 minutes Azure Functions (Consumption) ✅
Over 10 minutes / Simple processing Azure Functions (Premium/Flex) ✅
Complex workflows Durable Functions ✅
Want to execute on the same infra as App Service WebJobs
Distributed processing for large data Durable Functions + DTS

Service Selection Flowchart

Finally, here is a summary of the thought process for service selection. 🗺️

Condition Recommended Service
Containerization not required Azure App Service
Containerization required + No Kubernetes experience Azure Container Apps
Containerization required + Kubernetes experience + Small/Medium Azure Container Apps
Containerization required + Kubernetes experience + Large-scale microservices AKS
Static site + API Static Web Apps + Functions/App Service
Event-driven / Small-scale API Azure Functions

Database Selection 🗄️

In infrastructure modernization, database selection is a critical decision. Azure provides multiple options depending on your use case.

Types of Databases

Databases are broadly categorized into two types:

Type Characteristics Example Azure Services
🗃️ RDB (Relational) Structured data, ACID compliant, SQL Azure SQL Database, MySQL, PostgreSQL
📦 NoSQL Schema-less, scalable Cosmos DB, Table Storage

Migration Mapping from On-premises

On-premises Azure Service
SQL Server Azure SQL Database / SQL Managed Instance
Oracle Azure Database for PostgreSQL (migration) / Oracle on Azure VM
MySQL Azure Database for MySQL
PostgreSQL Azure Database for PostgreSQL

Azure SQL Database as the First Choice 🥇

When migrating from SQL Server, consider Azure SQL Database first.

  • ✅ Fully managed PaaS service
  • ✅ Automated backups and patching
  • ✅ Built-in high availability
  • ✅ Easy scaling
  • ✅ Build caching strategies in combination with Azure Managed Redis

Key features of Azure SQL Database:

  • 🔄 Automatic Tuning - Automatic query performance optimization
  • 🛡️ Advanced Threat Protection - Detection of security threats
  • 📊 Query Performance Insight - Support for query analysis and optimization
  • 🌍 Geo-replication - Global availability

The Option of Azure SQL Managed Instance 🏢

If you have requirements that cannot be covered by SQL Database, consider Azure SQL Managed Instance.

Consider Managed Instance if:

  • 📅 Time zone - You need to operate in a specific time zone like JST
  • 🔗 SQL Server Agent - You need job scheduling
  • 📧 Database Mail - You need email functionality
  • 🔐 Cross-database queries - Queries across multiple databases
  • 🏛️ CLR Integration - Execution of .NET assemblies
Feature SQL Database SQL Managed Instance
Time zone setting UTC fixed Customizable ✅
SQL Server Agent
Linked Servers
CLR Integration Limited
VNet integration Service endpoint Native ✅

Table Storage - Simple Key-Value Store 🔑

Azure Table Storage is a simple data store based on the key-value model.

Characteristics:

  • 💰 Very inexpensive
  • 📈 Ideal for small datasets
  • 🔧 Schema-less
  • ⚡ Fast key-value lookups

However, applicable use cases are limited. Consider it when complex queries are not required, such as for storing configuration data or simple log data.

Cosmos DB - Globally Scalable NoSQL 🌏

Azure Cosmos DB is an option when global distribution is required or when handling schema-less documents.

  • 🌍 Multi-region writes
  • ⚡ Single-digit millisecond latency
  • 📊 Multiple API models (SQL, MongoDB, Cassandra, etc.)

Database Selection Flowchart

Condition Recommended Service
Migration from SQL Server + No special features Azure SQL Database
Migration from SQL Server + Special features (Agent, time zone, etc.) needed Azure SQL Managed Instance
Migration from MySQL Azure Database for MySQL
Migration from PostgreSQL Azure Database for PostgreSQL
NoSQL + Global distribution needed Cosmos DB
NoSQL + Simple key-value Table Storage (inexpensive)
NoSQL + Other Cosmos DB

Introduce DevOps Tools 🔧

In modernization, it is important to modernize not only the infrastructure but also the development process. Let's introduce DevOps tools to establish a continuous improvement cycle.

List of DevOps Tool Categories

Category Description Tool Examples
📁 Source Control Code repository GitHub, Azure Repos
🔄 CI/CD Pipeline Continuously build and test deploy GitHub Actions, Azure Pipelines
📋 Task Board Plan, track, and discuss work GitHub Projects, Azure Boards, Redmine
📦 Package Manager Publish packages Azure Artifacts, GitHub Packages
🧪 Test Management Execute tests and quality assurance Azure Test Plans

The "Which CI/CD?" Problem 🤔

Choosing a CI/CD tool is a tricky problem, but to conclude, I recommend GitHub Actions.

Reasons to choose GitHub:

  1. 🤖 The presence of GitHub Copilot

    • Dramatic improvement in development efficiency
    • Good enough reason to migrate your repository base to GitHub
    • There is no option not to use Copilot now, right?
  2. 🌐 GitHub hosted runners are now available in Azure VNet

    • The reason for building self-hosted runners has almost vanished
    • You can use managed runners while maintaining network security
  3. ⚖️ Small functional differences from a CI/CD perspective

    • There is no major difference in what you can do between GitHub Actions and Azure Pipelines
    • Rich ecosystem and marketplace

GitHub Actions vs Azure Pipelines

Perspective GitHub Actions Azure Pipelines
Integration with Source Code ⭐⭐⭐ Native ⭐⭐ Requires integration
GitHub Copilot ⭐⭐⭐ Full integration ⭐ Limited
VNet Integrated Runners ✅ Supported ✅ Supported
Marketplace Rich Rich
Azure Integration ✅ Good ⭐⭐⭐ Native

Azure VNet Integrated GitHub Hosted Runners 🔒

Previously, self-hosted runners were essential for environments with strict security requirements, but the situation has changed with Azure VNet integration.

Perspective Challenges in the Past After VNet Integration
Runner Management ❌ Required building/operating self-hosted runners ✅ Run GitHub-managed runners within VNet
Scaling ❌ Managing scaling was cumbersome ✅ Supports auto-scaling
Security ❌ Required applying security patches ✅ Access via private endpoints
Operational Load ❌ High ✅ Significantly reduced

GitHub (Main)

Tool Purpose
📁 GitHub Repos Source control
🔄 GitHub Actions CI/CD (with VNet integration support)
📋 GitHub Projects Task management
🤖 GitHub Copilot AI development support (Use it!)

Azure (Complementary)

Tool Purpose
📦 Azure Artifacts Package management
🧪 Azure Test Plans Test management (if needed)

Conclusion

I have summarized the key points for selecting infrastructure when modernizing ASP.NET applications. 📝

🎯 Basic Policy for Selection

  1. Consider PaaS as your first choice.
  2. Consider App Service first, and evaluate others only if it doesn't meet your requirements.
  3. Assume a stateless design when architecting.
  4. Externalize sessions and state to services like Azure Managed Redis.
  5. Use Azure SQL Database as your first choice for databases.
  6. Keep DevOps simple with GitHub + GitHub Actions.

📊 Summary of Service Comparison

Category First Choice Alternatives
🖥️ Computing App Service Container Apps, AKS
🗄️ Database Azure SQL Database SQL Managed Instance
⚡ Cache Azure Managed Redis -
📁 Source Control GitHub Azure Repos
🔄 CI/CD GitHub Actions Azure Pipelines

🚀 Next Steps

Modernization is not something that can be completed overnight. I recommend proceeding with the following steps:

  1. 📖 Learning - Read the Azure CAF and Architecture Center.
  2. 🔍 Status Analysis - Identify dependencies and state management in your existing apps.
  3. 🛠️ DevOps Preparation - Build pipelines using GitHub + GitHub Actions.
  4. 🧪 PoC - Try deploying a small app to App Service.
  5. 📋 Planning - Create a phased migration plan.
  6. 🚀 Execution - Migrate while leveraging your CI/CD pipelines.

🗺️ Overall Architecture Image

Development Environment

Component Description
💻 VS Code + Copilot Development IDE
🔄 GitHub Actions CI/CD Pipeline
🤖 GitHub Copilot AI development support (Essential!)

⬇️ Deployment via CI/CD

Azure Environment

Component Description
🌐 App Service Web Application
🗄️ Azure SQL Database Database
Azure Managed Redis Cache
⚙️ Azure Functions Batch/Event processing

Considerations for Cloud Applications ⚠️

In Azure (cloud) environments, be aware of operating environments that differ from on-premises. There are points often overlooked, especially in systems in Japan.

Resources Running in UTC 🌐

Many Azure resources run in UTC (Coordinated Universal Time).

Perspective On-Premises Azure
🕐 System Time JST (Japan Standard Time) UTC
📅 Log Timestamps JST UTC
⏰ Scheduled Execution Based on JST Often based on UTC

Services with workarounds:

  • 🏃 App Service - Can be changed via Time Zone setting (WEBSITE_TIME_ZONE)
  • 🏃 SQL Managed Instance - Can be set to JST or any arbitrary time zone
  • Azure SQL Database - Fixed to UTC (Application-side handling required)

Running in Invariant Culture 🌍

Many Azure services run in Invariant Culture (a setting that does not depend on a specific culture).

Perspective Local Dev Environment Azure
🗾 Culture ja-JP (Japanese) Invariant Culture
📝 String Comparison Japanese locale Locale-independent
🔢 Number/Date Format Japanese format Standard format

Time Zone Design 🕐

Time zone design in a cloud environment is one of the most overlooked aspects of modernization.

Design Policy

Layer Recommendation Reason
💾 Database UTC Standardized, resilient to time zone changes
🔧 Business Logic UTC Calculations and comparisons are clear
👁️ View (Display) JST Conversion Easier for users to understand

Use DateTimeOffset 📅

Use DateTimeOffset as the type to represent dates and times.

// ❌ Deprecated: DateTime (lacks time zone information)
DateTime now = DateTime.Now;  // Local time, but the time zone is unknown

// ✅ Recommended: DateTimeOffset (includes time zone information)
DateTimeOffset nowUtc = DateTimeOffset.UtcNow;  // UTC time
DateTimeOffset nowJst = TimeZoneInfo.ConvertTime(
    DateTimeOffset.UtcNow, 
    TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time")
);

Why use DateTimeOffset:

  • ⏰ It forces you to be time zone aware because representations accounting for time differences are required.
  • 🔧 Since the type returned by TimeProvider is also DateTimeOffset, it is effectively the standard.
  • 🌍 Easier to support future global requirements.

Conversion Pattern for Display

// Service Layer: Process in UTC
public class OrderService
{
    public DateTimeOffset GetOrderTime() => DateTimeOffset.UtcNow;
}

// Display Layer (View/ViewModel): Convert to JST
public class OrderViewModel
{
    private static readonly TimeZoneInfo JstTimeZone = 
        TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
    
    public string OrderTimeDisplay => 
        TimeZoneInfo.ConvertTime(Order.OrderTime, JstTimeZone)
            .ToString("yyyy/MM/dd HH:mm:ss");
}

💡 Tip: Perform the conversion to JST in the View or as close to the View as possible.


About Culture 🗾

Pay Special Attention to the string Class ⚠️

Some methods in the string class change behavior depending on the culture.

// ❌ Culture-dependent (results may vary depending on the environment)
string upper = text.ToUpper();
bool equals = string.Equals(a, b, StringComparison.CurrentCulture);

// ✅ Culture-independent (same result in any environment)
string upper = text.ToUpper(CultureInfo.InvariantCulture);
bool equals = string.Equals(a, b, StringComparison.Ordinal);

Examples of Culture-dependent Methods

Method Problem Countermeasure
ToUpper() / ToLower() e.g., Turkish 'I' issue Use ToUpperInvariant()
string.Compare() Comparison results vary Specify StringComparison.Ordinal
DateTime.Parse() Depends on format Specify CultureInfo.InvariantCulture

Caution for Unit Testing 🧪

// Example of a culture-aware unit test
[Theory]
[InlineData("ja-JP")]
[InlineData("en-US")]
[InlineData("")] // Invariant Culture
public void DateFormat_ShouldBeConsistent_AcrossCultures(string cultureName)
{
    // Arrange
    var culture = string.IsNullOrEmpty(cultureName) 
        ? CultureInfo.InvariantCulture 
        : new CultureInfo(cultureName);
    
    Thread.CurrentThread.CurrentCulture = culture;
    
    // Act & Assert
    var date = new DateTimeOffset(2025, 12, 24, 0, 0, 0, TimeSpan.Zero);
    var result = date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
    
    Assert.Equal("2025-12-24", result);  // Consistent across all cultures
}

Considerations for Authentication and Authorization 🔐

During modernization, the mechanism for authentication and authorization also needs to be reviewed.

Challenges of Legacy Intranet Systems

Pattern Problem
🔓 IP address-based authentication Doesn't work in the cloud
🔓 Login using employee ID only No password = not authentication
🔓 Windows Authentication only Requires additional handling in Azure
🔓 Custom session management Security risk

Transition to Modern Authentication and Authorization

Adopt standard authentication and authorization mechanisms for cloud environments.

Method Description Azure Service
🔐 Microsoft Entra ID Corporate IdP (formerly Azure AD) Microsoft Entra ID
🔐 OAuth 2.0 / OIDC Standard authentication protocols Easy Auth, MSAL
🔐 Managed Identity Service-to-service authentication Managed Identity

Authentication Options for App Service

App Service provides Easy Auth (Built-in Authentication).

  • ✅ Add authentication without changing code
  • ✅ Supports Microsoft Entra ID, Google, Facebook, etc.
  • ✅ Automatic token management
// If using Easy Auth, retrieve user info from headers
public class UserService
{
    public string GetCurrentUserId(HttpRequest request)
    {
        // When Easy Auth is configured
        return request.Headers["X-MS-CLIENT-PRINCIPAL-ID"].FirstOrDefault()
            ?? throw new UnauthorizedException();
    }
}

Authorization Design

Level Implementation Method
🎯 API Level [Authorize] attribute, policy-based authorization
🎯 Data Level Row-Level Security, filtering
🎯 UI Level Display control based on permissions

Conclusion (Revised) 📝

🎯 Baseline Configuration

As a baseline for modernization, the following configuration is recommended:

Category Recommended Service Reason
🖥️ Computing App Service PaaS, minimal operational overhead
🗄️ Database Azure SQL Database Fully managed, SQL Server compatible
📁 Source Control / CI/CD GitHub Copilot integration, VNet runner support

🎯 Basic Policy for Selection

  1. Consider PaaS as your first choice.
  2. Consider App Service first, and evaluate others only if it doesn't meet your requirements.
  3. Assume a stateless design when architecting.
  4. Externalize sessions and state to Azure Managed Redis.
  5. Use Azure SQL Database as your first choice for databases.
  6. Keep DevOps simple with GitHub + GitHub Actions.
  7. Handle time in UTC and convert to JST for display.
  8. Strive for culture-independent code.

📊 Summary of Service Comparison

Category First Choice Alternatives
🖥️ Computing App Service Container Apps, AKS
🗄️ Database Azure SQL Database SQL Managed Instance
⚡ Cache Azure Managed Redis -
📁 Source Control GitHub Azure Repos
🔄 CI/CD GitHub Actions Azure Pipelines

🚀 Next Steps

Modernization is not something that can be completed overnight. I recommend proceeding with the following steps:

  1. 📖 Learning - Read the Azure CAF and Architecture Center.
  2. 🔍 Status Analysis - Identify dependencies, state management, and time processing in existing apps.
  3. 🛠️ DevOps Preparation - Build pipelines using GitHub + GitHub Actions.
  4. 🔐 Authentication Review - Consider migration to Microsoft Entra ID.
  5. 🧪 PoC - Try deploying a small app to App Service.
  6. 📋 Planning - Create a phased migration plan.
  7. 🚀 Execution - Migrate while leveraging your CI/CD pipelines.

🗺️ Overall Architecture Image

Development Environment

Component Description
💻 VS Code + Copilot Development IDE
🔄 GitHub Actions CI/CD Pipeline
🤖 GitHub Copilot AI development support (Essential!)

⬇️ Deployment via CI/CD

Azure Environment

Component Description
🌐 App Service Web Application
🗄️ Azure SQL Database Database
Azure Managed Redis Cache
⚙️ Azure Functions Batch/Event processing

💡 Reminders

Item Content
🕐 Time Zone Design based on UTC, use DateTimeOffset
🌍 Culture Support Invariant Culture, perform testing
🔐 Authentication Migrate to Microsoft Entra ID
📊 State Management Make stateless, externalize to Redis

The journey of modernization is long, but let's take it one step at a time!

I hope this article is helpful.

If you have any questions or feedback, please let me know in the comments!

GitHubで編集を提案

Discussion