iTranslated by AI
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!
📖 Recommended resources
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:
-
🤖 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?
-
🌐 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
-
⚖️ 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 |
Recommended DevOps Configuration 🏆
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
- Consider PaaS as your first choice.
- Consider App Service first, and evaluate others only if it doesn't meet your requirements.
- Assume a stateless design when architecting.
- Externalize sessions and state to services like Azure Managed Redis.
- Use Azure SQL Database as your first choice for databases.
- 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:
- 📖 Learning - Read the Azure CAF and Architecture Center.
- 🔍 Status Analysis - Identify dependencies and state management in your existing apps.
- 🛠️ DevOps Preparation - Build pipelines using GitHub + GitHub Actions.
- 🧪 PoC - Try deploying a small app to App Service.
- 📋 Planning - Create a phased migration plan.
- 🚀 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
TimeProvideris alsoDateTimeOffset, 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
- Consider PaaS as your first choice.
- Consider App Service first, and evaluate others only if it doesn't meet your requirements.
- Assume a stateless design when architecting.
- Externalize sessions and state to Azure Managed Redis.
- Use Azure SQL Database as your first choice for databases.
- Keep DevOps simple with GitHub + GitHub Actions.
- Handle time in UTC and convert to JST for display.
- 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:
- 📖 Learning - Read the Azure CAF and Architecture Center.
- 🔍 Status Analysis - Identify dependencies, state management, and time processing in existing apps.
- 🛠️ DevOps Preparation - Build pipelines using GitHub + GitHub Actions.
- 🔐 Authentication Review - Consider migration to Microsoft Entra ID.
- 🧪 PoC - Try deploying a small app to App Service.
- 📋 Planning - Create a phased migration plan.
- 🚀 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!
Discussion