The Best C# .NET Web Application Tech Stack: Deploying to Azure
We’re continuing our journey to go over the best modern web technologies by Microsoft for building a web application. We started by choosing a client-side framework in the first blog post and went on to choose a server-side tech in the 2nd post, and now it’s time to actually have your app make its way to the internet. And since our constraint is to use Azure as the cloud provider, we’ll see all the options to deploy and host in Azure.
Azure conveniently has an offering for every possible use case. You can deploy every which way possible, whether you want your own virtual machine, deploying containers, or a managed platform. You can choose cozy managed services that take care of everything you can imagine, like auto-scaling, OS updates, identity solutions, etc. Azure has built-in monitoring, amazing integration with CI/CD tools like GitHub Actions, PowerShell CLI, and a powerful portal. Sounds good? It’s not a coincidence that Azure is a 50$ billion a year business, and Microsoft makes a lot of effort to keep it that way. Hosting on Azure will be a good competitive option for every use case.
Depending on your app, you can have very different deployment requirements. You might have a single server project or a big distributed system. You might want to control all your virtual machines or you could be happy with Azure taking care of everything except the code itself. You might need a private virtual network, a role-based access system between services, global geo-distribution, or very specific scaling needs. We’ll talk about all of those things in this blog post and more. We’ll see many Azure offerings, what they support, their pros and cons, and when one is better than the other. There are many possible options, so let’s dive right into it.
Windows or Linux
One of the choices you’ll have to make is whether to host your app on Windows or Linux. .NET Core supports both and all the technologies we talked about, like ASP.NET Core, and Blazor Server, are cross-platform. So which OS should you choose? There are pros and cons to each, but often there are constraints that force your hand. Constraints are a good thing because they make decisions easier. A constraint might be that you’re porting from Windows VMs where there are Windows-specific services. Or you might be migrating a containerized application that runs on Linux images. If you have some greenfield app and you can go both ways, here are some things to consider:
- Linux is faster, which means you get more throughput on to the same Windows infrastructure.
- Some things run better on Windows or Linux. Those might be specific libraries or specific tools that you want to use.
- .NET tooling has been around longer on Windows and still is better than on Linux. This includes profilers like PerfView, Application Performance Monitoring (APM) tools, debuggers like dnSpy, and crash dump support, and others.
- If your team is more familiar with Linux hosting, like Nginx or Apache servers, Linux might be better. Likewise, if your team has a lot of expertise in IIS, that’s a plus for Windows. Although if you’re choosing a PaaS solution, it shouldn’t matter too much.
- If you’re planning to use docker containers, Linux containers are much more mature.
To containerize or not to containerize
Containers, specifically docker containers, transformed the world of software. They provided something that wasn’t possible up to this point: an isolated lightweight virtual machine that can be quickly loaded from an image and run the same everywhere. This means your container will execute identically on your local machine, on-prem, or in any cloud. No more “but it works on my machine…” arguments.
Containers can speed up development. For example, you no longer need to install a database locally. Just run a container image with SQL Server and you’re done. Or in the case of a microservice environment, you can set up a full orchestration locally or in the cloud and develop a single service against it. You’ll no longer have to multi-start a myriad of projects from Visual Studio.
If your application’s backend is running microservices, containers are a great choice. Running each service in a container that’s isolated and runs the same everywhere is like a match made in heaven. Kubernetes (aka K8s) comes to mind as the classic choice for container orchestration. But Azure offers other alternatives like Service Fabric and Azure Functions. We’ll talk about the pros and cons of each.
In our case, the API containers will usually run ASP.NET Core on Linux images.
For single-service applications, like when having a single ASP.NET server project, the common route is not to deploy to a container but rather to an Azure App Service or Azure Virtual Machine. That reduces the complexity a bit: you don’t have to learn to work with docker, deploy container images to a registry, manage image tags, etc. But containers offer some advantages even for that, e.g if you’re planning to expand to multiple services, or if you want to make your app more portable.
Azure has extensive support for containers with so many different offerings that it’s hard not to get confused. But you’re in good hands because we’re going to make sense of it all.
Microservices or not
I won’t list the pros and cons of microservices here. That’s a long debate with many articles and books written on it already.
There’s also the distinction between microservices, which implies many small services, and a distributed system, which can be anything from microservices to a couple of Web API projects and a database. So we’ll be discussing any type of distributed system, whatever that might be.
If you do go this way, Azure has lots of great offerings for distributed systems, including Azure Functions that have a simple serverless model, Azure Kubernetes Services which is a fully featured managed Kubernetes solution, Containers Apps which is branded as serverless containers and provides a simple way to host and scale distributed containerized apps, Azure Container Instances which allows deploying containers on demand but you’ll have to manage orchestration and scaling on your own, and Service Fabric which is a microservice orchestrator that’s an alternative to K8s. And you can even use an App Service with docker-compose for simple distributed systems.
You can deploy a distributed system without any managed Azure orchestrator just by deploying multiple Virtual Machines / App Services. Then, maybe apply a private VNet, expose a public endpoint, manage scale for each VM, etc. It involves a ton of work that frameworks already solved, so you probably wouldn’t want to reinvent the wheel here unless you have the resources of the likes of Dropbox or Google.
Let’s talk about specific offerings. I already mentioned Azure is a huge mingle of cloud products. For our purposes of deploying an application, we can minimize the offerings to just these nine:
|Built-in microservice support||Auto-scaling*||Container support||Learning curve|
|Azure Storage + CDN||-||Built in||-||Low|
|Static Web Apps||-||Built in||-||Very low|
|Virtual Machines||-||[W/ scale sets](W/ scale sets)||Not built-in||Medium|
|App Service||docker-compose only||Built in||Yes||Low|
|Azure Functions||Yes||Built in||Yes||Low|
|Azure Kubernetes Service (AKS)||Yes||Configurable||Yes||High|
|Container Apps||Yes||Built in||Yes||Low|
|Service Fabric||Yes||Built in||Yes||High|
*All offerings (that aren’t static content) support both Windows and Linux
Azure Storage and Azure CDN
Azure Storage along with Azure CDN can host static content as the name suggest. That would be your static website or a single-page application like React or Svelte. The Storage hosts your content and the CDN replicates and delivers it from various places around the globe for faster downloads. Right now, Azure CDN has over 100 pop locations. This is perfect for your client-side SPA while the ASP.NET server can be hosted in something like an Azure App Service.
There’s support for SSL certificates and custom domains.
Static Web Apps
Static web apps is a relatively new offering that’s tailored to host static websites. They do the same thing Azure Storage + Azure CDN can do for you in our context. The difference between them is that Static Web Apps provide a few extra features:
- Free web hosting of up to 100 GB / month
- Authentication in Azure AD or other providers via EasyAuth
- Routing support with different authentication settings
- An easier deploy and CI/CD process from Visual Studio Code or GitHub Actions
- Managed integration with Azure Functions for your backend. Azure Storage/CDN can use Functions as well, of course, but they will be hosted separately.
In terms of the geographical distribution differences, it looks similarly appealing with Static Web Apps powered by Azure Front Door that has 100+ locations worldwide, much like Azure CDN’s 100+ pop locations.
I’d carefully say that Static Web Apps seems to be better in every way than Azure Storage + Azure CDN to host static websites.
You could also host your static website on Azure App Service, but that would be more expensive and you’ll be missing out on the global distribution feature.
Azure App Service
App Service is the flagship PaaS offering from Azure to deploy web apps. It’s a fully managed solution to host your HTTP REST APIs and applications. Among its many features, It supports a bunch of languages and runtimes, autoscaling, load balancing, managing certificates, staging environments, authorization, deployment slots, and more. I think you can deploy to an App Service every possible .NET project type in existence, including ASP.NET MVC, ASP.NET Web API, and Blazor.
An App Service is a great candidate when you have a single server project or a small distributed system. The other candidate for this scenario is Azure Virtual Machine with the difference between them is that an App Service is a Platforms as a Service (PaaS), whereas a VM is Infrastructure as a service (IaaS). Let’s talk some about Virtual Machines and then try to decide when one is better than the other.
Azure Virtual Machines
Azure Virtual Machines are just as they sound, Virtual Machines on demand. This is known as Infrastructure as a Service (IaaS). It has several benefits over server computers in your company’s basement: you don’t have to worry about upkeep time, virtualization, security, or network problems. And if you need to scale up, you get instant resources on demand. Whereas if you scale down, you don’t have to pay for what you’re not using.
Although IaaS was a huge deal at the beginning of cloud services, a PaaS solution like an App Service gives you a lot more (like automatic OS updates, deployments without downtime, etc). So what are the advantages of using Virtual Machines? The big advantage is that they are completely customizable. You get the VMs you pay for and you can do whatever you want with them. You can install 3rd-party software, add Windows Services, run monitoring processes, etc. The virtual machine is your oyster.
While App Service tries to include and manage all the features your company will ever need, this goal is impossible. Yes, they’ve added monitoring, profiling, logs, site extensions, and deployment slots. But those are mostly Microsoft solutions. If you want your own custom monitoring process or some 3rd party tool that reports performance counters or whatever, you need a virtual machine.
One notable use-case to use Azure VMs is when you’re porting from on-prem or from another cloud where you’re already working with customized VMs.
All the technologies we talked about so far in part 1 (client-side) and part 2 (server-side) can be hosted on a VM. But it’s harder than deploying to an App Service. Depending on the operating system, you’ll need to install the right runtimes and SDKs, install IIS, install web management service, set port rules, install Web Deploy, etc. Well, I guess it could all be done in a day’s work and it’s probably negligible in comparison to the years you’ll be developing your application, but it’s still harder than the three minutes I need to deploy to an App Service.
I suggest using App Services for a greenfield project, and if you ever get to a point where you need something more customized, and have the resources to manage VMs, moving shouldn’t be too hard.
We already talked about Azure Functions in part 2 (choosing the server side), comparing them to other server technologies like ASP.NET Web API. Functions are a bit strange because they’re both a web API framework and a deployment target, so we’ll talk about them here as well. Let’s try to break it down and see when Functions might be a good fit.
In part 2, the conclusion was that Functions isn’t a great fit as a full-blown API in comparison to something like ASP.NET Web API. I think that when you’re building a monolith server app, that’s a good recommendation. The difference is not that great really when compared to an App Service, which is also completely managed, also auto-scales, and also has built-in goodies like authorization and certificates. But when you’re building a distributed system/microservices, there’s a case to be made for starting with Azure Functions. Consider what you’re trying to achieve with microservices:
- Independent modules
- Isolated points of failure
- Independent scaling
- Quicker development time
- Different tech per service if needed
Functions check every box in this list. They are independent and isolated in the sense that a catastrophic crash won’t bring down other services or other Azure Functions. They scale independently inherently. You can choose a different language and runtime for each function as you please. I can even agree that development time is faster since the projects are smaller. So why wouldn’t you use Functions for your microservices? I can think of a few reasons actually:
- Something like Kubernetes offers more granular control on system behavior like scaling.
- Functions are limited in VM offerings with only 3 types of instances to choose from in the premium plan. AKS or Service Fabric have every possible machine configuration that’s available on Azure. Like the E96as v4 with 96 vCPUs and 672GB RAM. Hey, you never know…
- There are a lot of advantages to using containers for microservices. Portability, runs the same everywhere, etc. And although you can use Functions to run containers, it’s kind of missing the point of the service. Might as well go with Container Apps.
- A lot of important features, like network isolation, exist only in the premium plan, which is pricier than similar offerings in Service Fabric or AKS.
I think it’s worth considering starting with Azure Functions or even committing to them for smaller projects. But if you’re building the next Amazon marketplace, Functions aren’t a good solution.
Azure Kubernetes Services (AKS)
Microservices and containers are like a match made in heaven. Containers are perfect as a lightweight box to run a service identically in any environment. Once the software community embraced this approach, the demand to automate scale, versioning, and access control required some sort of container orchestration technology. I believe docker swarm was the first solution, but pretty quickly Kubernetes swept the market and became the industry standard orchestrator.
Azure Kubernetes Services (AKS) is Azure’s managed K8s offering. Azure will install Kubernetes for you and take care of all the background services you might need, like updating the containers, updating Kubernetes itself, scaling, monitoring, a virtual network, identity (with Azure AD), a persistent data storage, and a private container registry (ACR). All those services are free and you’ll pay just for the infrastructure used in your cluster.
So assuming you’re doing microservices, should you always go with AKS? I’d say as a rule of thumb, go with K8s if you’re already experienced with it or when you need advanced control over your distributed system, which is usually the case for very big projects. Kubernetes is powerful but has a high learning curve and maintenance cost. You need to learn how to define scaling policies, manage access control, secure the network flow, set up ingress controller routing, etc. Or consider other Azure alternatives that we’ll see next.
If you just want to deploy your services and have Azure take care of everything as it sees fit, you can go with Azure Container Apps: a serverless container solution that’s based on AKS under the hood.
With Container Apps, you’ll get a Kubernetes cluster that comes along with Distributed Application Runtime (Dapr) and Kubernetes Event-Driven Autoscaling (KEDA).
Dapr is a bunch of APIs and services to help manage a distributed application cluster, like [pub/sub messaging](pub/sub messaging), service discovery, service invocation, secret management, state stores, and more.
KEDA is an auto-scale solution for your K8s cluster. It has a lot of built-in scalers that can trigger based on CPU, memory, queue message size, blob storage size, and many more.
With Dapr and KEDA supported out of the box, which you might want to use anyway, the opinionated basic Kubernetes cluster that Container Apps provides can go a very long way. And since it’s all based on AKS anyway, you can turn Container Apps into a regular AKS offering with the click of a button. So for a greenfield project where you want a microservice/distributed system with little initial effort, Container Apps looks like a great choice. I’d go as far as to say that unless you already know Kubernetes well or have some specific use cases where K8s is needed, start with Container Apps and continue to AKS if necessary. Having said that, there are even more Azure options to consider.
Azure Container Instances
Azure Container Instances (ACI) is not an orchestrator or a framework. It’s a simple API to deploy containers. Containers as a service if you will. You tell it what container image to run, and it runs it. If you need some sort of orchestration or auto-scale, you do it yourself. A pretty simple concept that fits rather well in a lot of scenarios. I used it myself with a basic orchestrator I’ve written and it worked rather well.
ACI is not the classic fit for a regular distributed application, but if you need something executed on containers by demand, without having to configure Kubernetes or dealing with KEDA, this might be the solution for you.
Service Fabric is Microsoft’s in-house solution to deploy distributed systems and microservices. It’s a competitor to Kubernetes in the sense that both are service orchestrators. But I’d say K8s is mostly an orchestrator, whereas Service Fabric also provides other functionality, most notably stateful service support.
There’s no doubt that K8s is the world’s most popular orchestrator. But Service Fabric is pretty big as well and isn’t going anywhere because a great deal of Azure itself, as well as other Microsoft systems, run on Service Fabric.
It’s hard to tell which is better, K8s or Service Fabric, there sure are many articles on the subject  . It’s pretty clear that there is a much more active community to K8s, and you’ll be more likely to find developers that are familiar with it, more documentation, more extensions, etc. When looking at the feature sets though, it’s doesn’t seem like Kubernetes has richer features, and if anything, Service Fabric trumps on this account. One advantage of Service Fabric is the native support of stateful services. In K8s you’ll have to use storage volumes or a database. Not that it’s a deal breaker by any means.
A Contest of Popularity
For some kind of reference as to what the community prefers, we can see the number of questions in Stack Overflow for each category:
The Azure Functions tag has the most questions with over 13,300 at the time I’m writing this, followed by Azure App Service with 10,600 questions. AKS and Service Fabric are similar with 2720 and 3115 questions, though I suppose a lot of questions are relevant just to Kubernetes tag, which has almost 50,000 questions. Azure CDN has more questions than Static Web Apps, but note that CDN has been around much longer. The same goes for Container Apps that only has 48 questions but this is a brand new offering that got to GA just over two months ago.
All in all, there aren’t a lot of surprises here, at least for me, though I expected App Service to be more popular than Functions. But there are so many variables with this type of comparison that any meaningful insight is almost impossible.
Making a choice
There sure are a lot of options to choose from, but as we talked about so far, your application’s natural constraints make the choice much easier. Here are a few rules of thumb and a few opinionated recommendations:
If you have a static client side, prefer Static Web Apps over App Service and Azure Storage + Azure CDN. Azure CDN is more expensive and lacks some of the features in comparison. App Service is also more expensive and isn’t distributed globally (not per instance anyway).
If you have a single Web API server, prefer an App Service over a Virtual Machine for new projects. You’ll save time managing the service and gain in features like deployment slots and auto-updates.
For a small greenfield distributed system, consider Azure Functions. It will save you the trouble of dealing with containers, creating docker images, or learning a framework like Service Fabric. Having said that, Azure Functions has a lot of limitations in comparison to AKS and Service Fabric and is fitted for smaller projects in my opinion. Or for background jobs like scheduled tasks, producing reports on demand, etc.
For microservices, containers on Linux are more mature and performant than Windows containers.
Container Apps is an easy way to start with your distributed system/microservices. It will save you the trouble of learning Kubernetes, at least at the beginning of the project. If you ever need more control, you can convert to a regular AKS cluster.
Kubernetes and Service Fabric are both great offerings for microservices. I can’t recommend which is better, but you probably won’t go too wrong with either.
So we chose the client side, the server side, and now the deployment strategy on Azure. Is there anything left to choose? Well, there’s always something more to develop, we’re not making a bridge here. One of the most important parts of an application is the database(s) you’re going to use, which you can read about in the next part of the series The Best C# .NET Web Application Tech Stack: Choosing a Database.
Hope you enjoyed this one, cheers.