I recall the days when writing a web application in C# with .NET meant deploying it on an IIS web server for accessibility. Today, this approach seems outdated, especially with the shift towards microservice-based architectures. Fortunately, Microsoft has significantly revamped .NET, releasing it as .NET Core, which caters to modern development needs.
.NET Core
NET Core has been in existence since its initial release in June 2016. It represents a versatile, open-source framework designed for creating modern, cloud-native applications. It marks a significant departure from the traditional .NET framework, which was primarily tied to Windows environments. With .NET Core, developers gain the flexibility to build applications that can seamlessly run on macOS, Linux, and Windows operating systems. This platform independence makes .NET Core an ideal choice for developing microservice-based applications using C#.
Developing a Microservice-Based Application
Developing a microservice-based application involves several key steps that are fundamental regardless of the application's specific requirements. Due to the distributed nature of these applications, developers need to carefully address the following challenges during the development phase:
- Service Invocation: Managing how services communicate with each other efficiently and reliably, considering factors like latency and response times.
- State Handling: Dealing with data consistency and synchronization across distributed services, which becomes challenging when services are updated independently and need to maintain coherent states.
- Asynchronous Messaging: Implementing robust messaging patterns (like queues or event-driven communication) to ensure reliable and scalable interaction between services, while handling potential message losses or delays.
- Service Orchestration: Coordinating the workflow and interaction between multiple microservices to achieve complex business processes, while ensuring fault tolerance and scalability.
- Transfer Encryption: Ensuring end-to-end security by encrypting data transferred between microservices to protect against eavesdropping and data breaches.
Fortunately, since these challenges are common, there are established solutions available to address them.
Dapr
One effective solution for overcoming the challenges of developing distributed applications is Dapr (Distributed Application Runtime). Dapr is an open-source project initiated by Microsoft to simplify the development of distributed systems. It provides a set of building blocks and APIs that abstract and simplify common tasks. These APIs are typically served by a sidecar placed next to your service, allowing for easy integration.
By abstracting these functionalities, Dapr offers API endpoints that enable developers to focus on business logic without worrying about the specific implementation details of the underlying infrastructure. Dapr is environment-agnostic and language-agnostic, supporting any platform that can handle HTTP and potentially gRPC.
Dapr's building blocks can be mixed and matched as needed, offering features such as:
- Direct Service Invocation
- Pub/Sub
- State Management
- Service-to-Service Encryption (mTLS)
- Observability
- Secrets Management
These features make Dapr a versatile tool for developing microservice-based applications. For the German speaking audience, there is a podcast episode available where I discuss some of that as well
Aspire.NET
Aspire.NET is another significant component to consider. Developed and open-sourced by Microsoft, Aspire.NET was recently released in its first major version (1.0) on May 21. It is a cloud-ready stack with strong conventions designed for production-ready distributed applications, streamlining end-to-end development in .NET Core. Aspire.NET focuses on three main areas:
- Orchestration
- Reusable Components
- Tooling
In terms of orchestration, Aspire.NET provides a way to compose your app based on the resources it needs, such as apps and databases. These components can include reusable components like Redis or PostgreSQL databases, distributed as NuGet Packages. Tooling-wise, Aspire.NET follows a specific project structure to streamline development:
- AnyServiceProjects: These can be any services your application needs, such as APIs or frontends. That's your business logic.
- AppHost: This project handles high-level orchestration for your application, integrating various components like APIs, service containers, and executables, and managing their communication.
- ServiceDefaults: This project provides default configurations for your .NET Aspire application, which can be extended and customized as needed. It covers aspects like health checks, OpenTelemetry settings, and more. Aspire.NET thus provides a robust framework for developing and managing distributed applications with .NET Core.
A Concrete Example
Alright, enough with the theoretical explanations. With all the tools at our disposal, let's build a demo app to showcase their combined capabilities. This demo app, is a simple voting application. You can vote for your favourite colour and you'll see the results on the right hand side. For this example, we will have three different services (or projects, in C# terminology) inside our solution:
- Web Service: This will host the web frontend. We will use Razor Components in combination with HTMX to render the page.
- Colour Service: This service will provide a simple HTTP REST API to retrieve all possible/existing colours and allow voting for a given colour.
- Vote Service: This service will be responsible for actually saving a vote. Communication will be established using Pub/Sub for creating a vote and a simple API to get the current vote count.
All three services will utilize a Dapr sidecar to offload complexity:
- The Web Project will use a Dapr sidecar to establish HTTP connectivity, including service discovery.
- The Colour Service will use a Dapr sidecar to implement Pub/Sub as the publisher.
- The Vote Service will use a Dapr sidecar to implement Pub/Sub as the receiver and to handle state management. These components will leverage Dapr to simplify interactions with Redis for Pub/Sub and state management.
You can find the source code here .
Let's Get It Running (Locally)
Remember the AppProject part ensuring orchestration? Now it's getting handy! If you've checked out the code from above, you can see the AppProject code like this:
using Aspire.Hosting.Dapr;
var builder = DistributedApplication.CreateBuilder(args);
var stateStore = builder.AddDaprStateStore("statestore");
var pubSub = builder.AddDaprPubSub("pubsub");
DaprSidecarOptions sidecarOptionsVote = new()
{
EnableApiLogging = true,
LogLevel = "DEBUG",
AppId = "votes"
};
DaprSidecarOptions sidecarOptionsColours = new()
{
EnableApiLogging = true,
LogLevel = "DEBUG",
AppId = "colours"
};
DaprSidecarOptions sidecarOptionWeb = new()
{
EnableApiLogging = true,
LogLevel = "DEBUG",
AppId = "web"
};
var voteApi = builder.AddProject<Projects.voting_app_VotesService>("voteservice")
.WithDaprSidecar(sidecarOptionsVote)
.WithReference(stateStore)
.WithReference(pubSub)
.WithExternalHttpEndpoints();
var colourApi = builder.AddProject<Projects.voting_app_ColourService>("colourservice")
.WithExternalHttpEndpoints()
.WithDaprSidecar(sidecarOptionsColours)
.WithReference(pubSub)
.WithReference(voteApi);
var frontend = builder.AddProject<Projects.voting_app_Web>("web")
.WithExternalHttpEndpoints()
.WithDaprSidecar(sidecarOptionWeb);
builder.Build().Run();
This file describes the overall structure of our landscape. Aspire will take care of the orchestration based on this project. To get it running locally, all we need to do now is a simple:
dotnet run --project voting-app.AppHost
Buildvorgang wird ausgeführt...
info: Aspire.Hosting.Dapr.DaprDistributedApplicationLifecycleHook[0]
Starting Dapr-related resources...
info: Aspire.Hosting.Dapr.DaprDistributedApplicationLifecycleHook[0]
Using default Dapr state store for component 'statestore'.
info: Aspire.Hosting.Dapr.DaprDistributedApplicationLifecycleHook[0]
Using default Dapr pub-sub for component 'pubsub'.
info: Aspire.Hosting.DistributedApplication[0]
Aspire version: 8.0.1+a6e341ebbf956bbcec0dda304109815fcbae70c9
info: Aspire.Hosting.DistributedApplication[0]
Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
Application host directory is: /Users/manuelzapf/Projects/codecentric/pocs/sms_dotnet_core/voting-app.AppHost
info: Aspire.Hosting.DistributedApplication[0]
Now listening on: https://localhost:17168
info: Aspire.Hosting.DistributedApplication[0]
Login to the dashboard at https://localhost:17168/login?t=88984ad3d019e079e78cb118728dd109
info: Aspire.Hosting.DistributedApplication[0]
Distributed application started. Press Ctrl+C to shut down.
As we can see in the logs above, the application and the Aspire Dashboard have started up. We can check the app running by visiting https://localhost:7044.
Additionally, the Aspire Dashboard is now up and running, showing all our deployed components.
The Aspire Dashboard provides more capabilities than just running resources. It's also the key point for structured logging, metrics, and traces. It's a great starting point for observing and debugging your distributed applications, all thanks to the OpenTelemetry integration that comes with Aspire.
Bonus Points: Let's Deploy It Using Azure Development Kit
Alright, we have the application and all components running locally. The easiest next step is to deploy everything to the Azure Cloud using the Azure Developer CLI. Once installed and authenticated against your account, you can deploy with just two commands:
azd init
azd up
After running these commands, the AZD CLI will create Bicep templates (kinda like Cloudformation, the IaaC from Azure) and use them to deploy our described application utilizing Azure Container Apps.
Packaging services (azd package)
Provisioning Azure resources (azd provision)
Provisioning Azure resources can take some time.
Subscription: manuel.zapf@codecentric.de (XXX)
Location: Germany West Central
You can view detailed progress in the Azure Portal:
XXX
(✓) Done: Resource group: rg-voteservice-example
(✓) Done: Log Analytics workspace: law-xmjcalomp65aw
(✓) Done: Container Registry: acrxmjcalomp65aw
(✓) Done: Container Apps Environment: cae-xmjcalomp65aw
(✓) Done: Container App: daprstore
Deploying services (azd deploy)
(✓) Done: Deploying service colourservice
- Endpoint: https://colourservice.bravewater-24618931.germanywestcentral.azurecontainerapps.io/
(✓) Done: Deploying service voteservice
- Endpoint: https://voteservice.bravewater-24618931.germanywestcentral.azurecontainerapps.io/
(✓) Done: Deploying service web
- Endpoint: https://web.bravewater-24618931.germanywestcentral.azurecontainerapps.io/
Aspire Dashboard: https://aspire-dashboard.ext.bravewater-24618931.germanywestcentral.azurecontainerapps.io
SUCCESS: Your up workflow to provision and deploy to Azure completed in 3 minutes 12 seconds.
By browsing to the URL of either the web service or the Aspire Dashboard, you can see all components running and working.
Conclusion
In conclusion, the combination of .NET Core, Dapr, and Aspire offers a powerful toolkit for developing modern distributed applications. With .NET Core's maturity and cross-platform capabilities, coupled with Dapr's simplification of distributed system complexities through its sidecar model and Aspire's robust orchestration and tooling, developers can accelerate their development process significantly.
These technologies provide a streamlined approach to implementing microservices architectures, handling tasks like service invocation, asynchronous messaging, state management, and observability out of the box. By leveraging Dapr's sidecar pattern, developers can focus more on business logic and less on infrastructure concerns, all while benefiting from the reliability and scalability that Azure and other cloud environments provide.
Whether you're building from scratch or modernizing existing applications, the integration of .NET Core, Dapr, and Aspire offers a compelling solution that empowers developers to innovate and deliver robust, scalable applications efficiently.
More articles
fromManuel Zapf
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Manuel Zapf
Solution Architect
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.