Microsoft Teams' journey to .NET Core

Customer
Microsoft Teams (Microsoft)

Products & services
ASP.NET Core 3.1

Industry
Technology

Organization Size
Large (1000+ employees)

Country/region
United States

Microsoft Teams "MiddleTier" is an internal service that powers various scenarios in Microsoft Teams. It's one of the largest services consisting of 700+ APIs that are maintained by 10+ teams at Microsoft. Over the last two years, 50+ projects (libraries, tests, applications) under this service have been converted to .NET Standard 2.0 and .NET Core 3.1, having functional and performance equivalence (or better), and are now almost entirely running on .NET Core 3.1 in production, and looking to move to .NET 6 next. Before this migration, the service was running on .NET Framework 4.6.2 using ASP.NET Core 2.2 MVC pipeline. It runs on Azure Service Fabric with deployments in 35 Azure datacenters.

The scope of this migration was large because MiddleTier is an extra-large service in terms of the functionality that it provides with hundreds of developers that work on it every day.

Motivation for migration

The team was motivated to move to .NET Core 3.1 for the following reasons:

  • Performance and cost-efficiency improvements
  • .NET Framework 4.6.2 is likely reaching end of life soon
  • Cross-platform support
  • Move to a modern framework for better developer experience

Benefits after migrating to .NET Core 3.1

After migrating to .NET Core 3.1, the team has noticed the following improvements:

  • 25% CPU improvement
  • Around 25% reduction in infrastructure cost
  • Improved thread pool usage
  • Reduced technical debt and efforts in moving to annual .NET releases

The following charts show comparisons between .NET Framework and .NET Core. In the future, with .NET 6, we should see even further improvements.

Chart showing 57% peak CPU on .NET Framework and 42% peak CPU on .NET Core
CPU comparison

Chart showing a decline of busy worker threads after migration to .NET Core
Busy Worker Thread comparison

Chart showing a decline of busy IO threads after migration to .NET Core
Busy IO Thread comparison

Approach

The overall migration was divided into three stages:

Graphic showing the activities for the three migration stages (preparation, execution, and validation and rollout)

They also chose to multi-target the application to .NET Framework and .NET Core so that they would have both binaries available, and they could continue to roll out .NET Core slowly.

Learnings

OData and other REST APIs can't share route prefix

Their service has few OData endpoints as well along with many REST endpoints. These two shared the same routing prefix for the endpoints. This used to work fine in .NET Framework but because of routing changes, this stopped working in .NET Core. They had to move OData APIs to a different route prefix to resolve this.

Performance issues with OData client libraries

The HttpWebRequest pattern with OData clients to make calls to downstream OData APIs results in higher latency compared to .NET Framework. This was due to a regression in .NET Core in which the framework doesn't cache connections. This is already resolved in newer versions of .NET.

Issues with Azure Service Bus SDKs

The Azure Service Bus SDK had to be upgraded as part of this migration since the old version isn't .NET Standard compatible. The latest version of the Azure Service Bus SDK sends the request payload in JSON format whereas the older SDK sends the payload in XML format. To continue using XML payload, they had to use the DataContractSerializer.

Issues in Service Fabric project for multi-targeting

The Service Fabric Project (sfproj) doesn't support multi-targeting inherently. They had to do workarounds in the build pipeline to produce Service Fabric packages for both target frameworks.

Issues with the older version of MimeKit NuGet

The older version of MimeKit can have issues with double byte characters, thus language specific validation is advised in this scenario. They uncovered similar issues when they rolled out to deployments located in Asian geography.

Classic ASP.NET quirks

  • Had to remove usage of some of the .NET Framework classes that were marked internal in .NET Core.
  • MVC async suffix dropped from action names as mentioned in the ASP.NET Core breaking changes for versions 3.0 and 3.1 article. If any of the code paths are dependent on the action name, it can cause a change in behavior.
  • Synchronous IO operations are by default turned off on all servers starting in .NET Core 3.0 as mentioned in the dotnet/aspnetcore#7644 GitHub issue.
  • Content-Length header not set in Content.Headers when sending StreamContent as HTTP request content. It can result in errors from downstream calls.
  • .NET framework produces stable hash code for a string, but .NET Core doesn't.
  • The Required attribute from the System.ComponentModel.DataAnnotations namespace behaves differently in .NET Core. On .NET Framework, this attribute doesn't do any model validation for non-null fields but on .NET Core it does.

Future

Every new release of .NET comes with tremendous productivity and performance improvements that continue to help accomplish our goals to build resilient, scalable, performant, and secure services. The team will continue to leverage the improvements made in .NET by upgrading to .NET 6 next.

Ready to get started?

Our step-by-step tutorial will help you get ASP.NET running on your computer.

Get started