- •Table of Contents
- •About the Author
- •Acknowledgments
- •Introduction
- •Version Support
- •Supported Versions
- •A Unified Platform
- •Roadmap
- •Supported Operating Systems
- •Command Line Interface
- •Desktop Development
- •Blazor
- •MAUI
- •Wrapping Up
- •.NET 6 Architecture
- •Runtimes
- •CoreCLR
- •Mono
- •WinRT
- •Managed Execution Process
- •Desktop Packs
- •Wrapping Up
- •Dotnet New
- •Dotnet Restore
- •NuGet.config
- •Dotnet Build
- •Dotnet Publish
- •Dotnet Run
- •Dotnet Test
- •Using the CLI in GitHub Actions
- •Other Commands
- •Wrapping Up
- •WinAPI
- •WinForms
- •STAThread
- •WinForms Startup
- •DPI Mode
- •Responding to Scale Events
- •Visual Styles
- •Text Rendering
- •The Message Loop
- •The Form Designer
- •WPF Startup
- •XAML Layout
- •Visual Tree
- •Data Binding
- •Windows App SDK
- •Building a Windows App SDK application
- •Using Windows APIs with Windows App SDK
- •Packaging
- •Migrating to .NET 6
- •Upgrade Assistant
- •Wrapping Up
- •Blazor WebAssembly
- •Creating a Blazor Wasm Project
- •Blazor Progressive Web Apps
- •Exploring the Blazor Client Project
- •Blazor in .NET 6
- •Blazor Component System
- •Creating Blazor Pages
- •Running a Blazor App
- •Blazor Server
- •SignalR
- •Blazor Desktop
- •Wrapping Up
- •Project Structure
- •Exploring MAUI
- •The Cross-Platform World
- •Application Lifecycle
- •MVVM
- •MVVM Toolkit
- •Wrapping Up
- •Model-View-Controller
- •Routing
- •Views
- •Controllers
- •Controller-Based APIs
- •Minimal APIs
- •Wrapping Up
- •Web Apps
- •Creating an App Service
- •Static Web Apps
- •Web App for Containers
- •Docker
- •Azure Functions
- •Deploying Azure Functions
- •Wrapping Up
- •Record Types
- •Monolith Architecture
- •Microservices
- •Container Orchestration
- •Kubernetes
- •Docker Compose
- •Dapr
- •Installing Dapr
- •Dapr State Management
- •Wrapping Up
- •Roslyn
- •Compiler API
- •Diagnostic API
- •Scripting API
- •Workspace API
- •Syntax Tree
- •Roslyn SDK
- •Source Generators
- •Writing a Source Generator
- •Debugging Source Generators
- •Wrapping Up
- •Garbage Collector
- •The Heap
- •The Stack
- •Garbage Collection
- •A Look at the Threadpool
- •Async in .NET 6
- •Await/Async
- •Cancellations
- •WaitAsync
- •Conclusion
- •Index
Chapter 8 Microsoft Azure
Figure 8-32. Calling a Function from Postman
Wrapping Up
Microsoft Azure is a powerful, globally distributed, versatile platform. The vast majority of available resources is large enough to fill multiple books on its own, so we have only scratched the surface here. I do hope that it has triggered you enough to go explore further and dive into the wonderous world of cloud-native applications and hybrid applications. With .NET being a first-class citizen in the Microsoft world, and .NET 6 being an important major release, it comes as no surprise that Azure was day 1 ready for
.NET 6. Multiple services have supported it even back when .NET 6 was in preview. All of this is made complete with great Azure integration in Visual Studio 2022, allowing us to create resources and publish new code to them without leaving the IDE.
257
CHAPTER 9
Application Architecture
Together with .NET 6 came tooling to help developers build better architectures. Projects like Dapr and example project like eShop On Containers help tremendously with building well-designed and well-architected platforms.
So where can .NET 6 help in building great architectures? There are a few concepts in .NET that help simplify some things; but not to worry, .NET is not pushing you into any direction. We still have full flexibility to architect our applications however we see fit. What we do have is numerous syntax concepts that help keep our code small and readable.
Record Types
The quickest win is Data Transfer Objects, or DTOs. DTOs are a representation of an entity that will be passed over the wire over a data transfer protocol such as HTTP. DTOs are important because they prevent leaking nonpublic, internal information about entities to foreign systems. In most cases, they are a basic class containing auto- properties that map in a straightforward way onto the entity that they represent.
Listing 9-1 shows an example of an entity.
Listing 9-1. The entity
public class Event : Entity, IAggregateRoot
{
private readonly List<Attendee.Attendee> _attendees;
public string Title { get; private set; } public DateTime StartDate { get; private set; } public DateTime EndDate { get; private set; } public decimal Price { get; private set; }
259
© Nico Vermeir 2022
N. Vermeir, Introducing .NET 6, https://doi.org/10.1007/978-1-4842-7319-7_9
Chapter 9 Application Architecture
public int? AddressId { get; private set; } public Address Address { get; private set; }
public virtual IReadOnlyCollection<Attendee.Attendee> Attendees => _attendees;
public Event(string title, DateTime startDate, DateTime endDate, decimal price)
{
_attendees = new List<Attendee.Attendee>();
Title = title; StartDate = startDate; EndDate = endDate; Price = price;
}
public void SetAddress(Address address)
{
AddressId = address.Id; Address = address;
}
}
This is a very basic example of an entity from an application build using the Domain- Driven-Design principles. It inherits from Entity, which has an Id property to give us a uniquely identifiable property, and it is an IAggregateRoot, which means that this object can be stored and retrieved on its own. Entities who are not an IAggregateRoot are not meant to exist by themselves; they depend on other objects to be a member of.
Let’s say we need to fetch a list of events to show in our frontend; not using DTOs would mean that we could possibly fetch hundreds of events with all Attendee and Address details, while maybe all we want to do is show a list of upcoming events. To simply, list all events that would be too much data. Instead, we use a DTO to simplify the object that goes over the wire according to the use case we need.
Listing 9-2 shows an example what a DTO could look like for when we want a list of events.
260
Chapter 9 Application Architecture
Listing 9-2. DTO for listing events
public class EventForList
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Way less data to send over the wire, and just enough. When needing to fetch the details for an Event, we of course need another DTO containing all the info an event detail page might need. You may realize that this can become tedious quite fast, writing DTO after DTO, mapping them to the entity, and so on. A neat compiler trick that came with .NET 5 can help speed this process up; that trick is called records. Listing 9-3 shows the DTO from Listing 9-2 again but written as a record.
Listing 9-3. DTO as a record
public record ActivityForListRecord (int Id, string Title, DateTime StartDate, DateTime EndDate);
That is one line of code to replace all the auto-properties. A record is a shorthand for writing a class, but there is more to it. Equality, for example, in a normal class, two variables of the same reference type are equal when they point to the same reference. With a record, they are equal when they have the same value. In this case, a class that only contains properties. Another difference is that a record is immutable. The complete documentation on records can be found here https://docs.microsoft.com/en-us/ dotnet/csharp/language-reference/builtin-types/record.
The values between the brackets are not parameters; they are properties, hence the Pascal Casing. As for the output, records are nothing more than a clever compiler trick, a pinch of syntactic sugar. Listing 9-4 compares the intermediate language definition of the EventForList class with the EventForList record. I have renamed them EventForListClass and EventForListRecord for convenience.
261
Chapter 9 Application Architecture
Listing 9-4. IL output for a record and a class
.class public auto ansi beforefieldinit ActivityForListRecord extends [System.Runtime]System.Object
.class public auto ansi beforefieldinit ActivityForListClass extends [System.Runtime]System.Object
As you can see, the outputs are identical, meaning records are known to the C# compiler but not to the runtime.
New to the C# language since C# 10 is value-type records. Up until now, records could only be reference types, classes. C# 10 introduces record structs which are value- type records. Listing 9-5 shows the earlier record example as a value type; notice the struct keyword.
Listing 9-5. Value-type records
public record struct ActivityForListRecord (int Id, string Title, DateTime StartDate, DateTime EndDate);
Let’s have a look at the IL again. Listing 9-6 shows the generated IL code.
Listing 9-6. IL output for a record struct and a class
.class public sequential ansi sealed beforefieldinit ActivityForListRecord extends [System.Runtime]System.ValueType
.class public auto ansi beforefieldinit ActivityForListClass extends [System.Runtime]System.Object
Struct records follow the same rules as normal structs. Structs are often used because they are cheaper and memory-wise because they are value-typed. This often results in better performance. They do have limitations when compared to classes, for example, structs don’t allow inheritance. A major difference between records and record structs is that record structs are not immutable by default; they can be if we mark them as readonly.
262