34,79 €
This unique ASP.NET Core book will fill in the gaps in your REST API and backend designs. Learn how to build robust, maintainable, and flexible apps using Gang of Four (GoF) design patterns and modern architectural principles. This new edition is updated for .NET 8 and focuses exclusively on the backend, with new content on REST APIs, the REPR pattern, and building modular monoliths.
You’ll start by covering foundational concepts like REST, the SOLID principles, Minimal APIs, dependency injection in .NET, and other ASP.NET Core 8 mechanisms. Then, you’ll learn to develop components using design patterns, including many from the GoF. Finally, you’ll explore organizing your application code with patterns that vary from layers to feature-oriented vertical slice designs, covering CQS and a deep dive into microservices along the way. A brand-new e-commerce project at the end of the book will tie it all together.
This how-to guide will teach you how to assemble your own APIs from building blocks, to suit whatever real-world requirements you may have.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 1017
Architecting ASP.NET Core Applications
Third Edition
An atypical design patterns guide for .NET 8, C# 12, and beyond
Carl-Hugo Marcotte
BIRMINGHAM—MUMBAI
Architecting ASP.NET Core Applications
Third Edition
Copyright © 2024 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Publishing Product Manager: Lucy Wan
Acquisition Editor – Peer Reviews: Gaurav Gavas
Project Editor: Rianna Rodrigues
Content Development Editor: Rebecca Youé
Copy Editor: Safis Editor
Technical Editor: Srishty Bhardwaj
Proofreader: Safis Editor
Indexer: Rekha Nair
Presentation Designer: Rajesh Shirsath
Senior Developer Relations Marketing Executive: Priyadarshini Sharma
First published: December 2020
Second published: March 2022
Third edition: March 2024
Production reference: 1150324
Published by Packt Publishing Ltd.
Grosvenor House
11 St Paul’s Square
Birmingham
B3 1RB, UK.
ISBN 978-1-80512-338-5
www.packt.com
I’ve been programming for over 20 years now. Some of those years were spent as a kid trying to build stuff for fun, but for the majority of that time I’ve been building software professionally. C# has been my go-to when building software systems, and I’ve been fortunate to work in organizations that fully embrace it (including Microsoft!).
Early on in my professional career I became a software engineering manager, and I was able to shift my efforts toward helping other software engineers become better at their craft. I’ve spent years working with software developers, helping to keep them engaged in their work and their learning. It’s my personal mission to ensure the barrier to becoming an awesome software engineer is brought down, and C# and .NET have been excellent programming tools to assist in that journey.
Carl-Hugo Marcotte’s Architecting ASP.NET Core Applications is fully in line with my preferred way to teach others software engineering concepts. The content in the book is organized in a way that gradually builds on programming principles as demonstrated in C#, giving readers some theory that is quickly followed by code examples. From there, learning is solidified with mini projects where readers can apply their theory immediately to something practical.
Readers that work through the content in this book should expect to build confidence in design and architectural patterns in ASP.NET Core. This will allow for building more robust and scalable systems. Having tangible and concrete code examples to refer back to every step of the way through this book is one of its greatest strengths.
The design patterns are gradually introduced with explanations regarding their purpose and guidelines for implementations. From there, they’re given a practical application in ASP.NET Core. The visuals coupled with hands-on code examples and projects all play together to reinforce learning from different angles. Seeing concepts directly applied in realistic scenarios helps reinforce the pros and cons of each.
Additionally, testing approaches are built into the content of this book at every opportunity. This is a refreshing take on software engineering material where there’s typically a hyper-focus on design, performance, and computer science. However, Carl-Hugo Marcotte’s examples are backed by excellent testing examples that highlight how to validate the different concepts being implemented.
For software developers reading this book who are newer to developing ASP.NET Core applications, I hope that you feel more confident in your skillset by the end. For those readers that have more experience developing web applications in C#, I believe that this book will help to reinforce good design decisions and offer you some alternative perspectives on things you are already familiar with. Regardless of your prior skill level, by the end of this book, you will be positioned to build better applications.
Nick Cosentino, Principal Software Engineering Manager at Microsoft
Carl-Hugo Marcotte is a software craftsman who has developed digital products professionally since 2005; his coding journey started around 1989 for fun. He has a bachelor’s degree in computer science. He has acquired a solid background in software architecture and expertise in ASP.NET Core through creating a wide range of web and cloud applications, from custom e-commerce websites to enterprise applications. He served many customers as an independent consultant, taught programming, and is now a Principal Architect at Export Development Canada. Passionate about C#, ASP.NET Core, AI, automation, and cloud computing, he fosters collaboration and the open-source ethos, sharing his expertise with the tech community.
I want to thank everyone who has supported me throughout my career, from my mom, who bought my first computers when I was a kid, to my other half and partner in life, Cathie, who is always there no matter the idea I pursue.
Davide Bellone is a backend developer with over 10 years of professional experience. Based in Italy, he’s worked with Microsoft platforms and tools since 2014. He has worked with .NET Core, SQL, Azure, and more. He loves learning new things and thinks the best way to learn is to share; that’s why he started his journey as a content creator and conference speaker. He’s the author of Code4IT.dev, a blog for C# and Azure developers. He’s honoured to be a Microsoft MVP in Developer Technologies.
Kevin Viera is a software engineer passionate about software architecture and emerging technologies. He has a diverse background covering various aspects of web development and computer science. Kevin’s trajectory led him through a spectrum of languages and technologies such as C++, C#, JavaScript, and Python, before he began specializing in the .NET environment.
Throughout his career, Kevin has shown a strong interest in delving into the complexities of the .NET ecosystem. He began with the Windows-only .NET Framework, progressed to the cross-platform .NET Core, and finally focused on the current .NET environment as his main career path.
To join the Discord community for this book – where you can share feedback, ask questions to the author, and learn about new releases – follow the QR code below:
https://packt.link/ArchitectingASPNETCoreApps3e
Preface
Who this book is for
What this book covers
To get the most out of this book
Get in touch
Section 1: Principles and Methodologies
Introduction
What is a design pattern?
Anti-patterns and code smells
Anti-patterns
Anti-pattern: God class
Code smells
Code smell: Control Freak
Code smell: long methods
Understanding the web: request/response
Getting started with .NET
.NET SDK versus runtime
.NET 5+ versus .NET Standard
The command-line interface versus Visual Studio Code versus Visual Studio
An overview of project templates
Running and building your program
Technical requirements
Summary
Questions
Further reading
Answers
Automated Testing
An overview of automated testing
Unit testing
Integration testing
End-to-end testing
Other types of tests
Finding the right balance
Enhancing code quality
Improving quality with refactoring
Managing technical debt
Testing approaches
TDD
ATDD
BDD
Testing techniques
White-box testing
Black-box testing
Gray-box testing
White-box vs. black-box vs. gray-box testing
Conclusion
Test case creation
Equivalence partitioning
Boundary value analysis
Decision table testing
State transition testing
Use case testing
Introducing the xUnit framework
How to create an xUnit test project
Key xUnit features
FactAttribute
Assertions
TheoryAttribute
Fixture
Arrange, Act, Assert
Organizing your tests
Unit tests
Namespaces
Test class names
Test code inside the test class
Integration tests
Writing ASP.NET Core integration tests
Classic web application
Minimal hosting
Workaround
Alternative to using fixtures
Creating a reusable test application
Important testing principles
Summary
Questions
Further reading
Answers
Architectural Principles
Separation of concerns (SoC)
Don’t repeat yourself (DRY)
Keep it simple, stupid (KISS)
You Aren’t Gonna Need It (YAGNI)
The SOLID principles
Single responsibility principle (SRP)
Project – Single Responsibility
Open/closed principle (OCP)
Project – Open Close
Liskov substitution principle (LSP)
The LSP explained
Covariance and contravariance
Project – Liskov Substitution
Conclusion
Interface segregation principle (ISP)
What is an interface?
Project – Interface Segregation
Conclusion
Dependency inversion principle (DIP)
Direct dependency
Inverted dependency
Direct subsystems dependency
Inverted subsystems dependency
Project – Dependency inversion
Conclusion
Summary
Questions
Further reading
Answers
REST APIs
REST and HTTP
HTTP methods
HTTP status codes
HTTP headers
Versioning
Default versioning strategy
Versioning strategy
Wrapping up
The Data Transfer Object (DTO) pattern
Goal
Design
Conceptual examples
Registering for an activity
Fetching activity registration details
Conclusion
API contracts
Code-first API contract
The first endpoint
The second endpoint
Wrapping up
Summary
Questions
Further reading
Answers
Section 2: Designing with ASP.NET Core
Minimal APIs
Top-level statements
Minimal hosting
Minimal APIs
Mapping a route to a delegate
Configuring endpoints
Inputs
Outputs
Metadata
Configuring JSON serialization
Leveraging endpoint filters
Leveraging the endpoint filter factory
Organizing endpoints
MapGroup
Creating a custom Map extension method
Class libraries
Using Minimal APIs with Data Transfer Objects
Goal
Design
Project – Minimal API
Raw CRUD endpoints
DTO-enabled endpoints
Conclusion
Summary
Questions
Further reading
Answers
Model-View-Controller
The MVC design pattern
Goal
Design
Anatomy of ASP.NET Core web APIs
The entry point
Directory structure
Controller
Returning values
Attribute routing
Conclusion
Using MVC with DTOs
Goal
Design
Project – MVC API
Raw CRUD controller
DTO controller
Conclusion
Summary
Questions
Further reading
Answers
Strategy, Abstract Factory, and Singleton Design Patterns
The Strategy design pattern
Goal
Design
Project – Strategy
Conclusion
The Abstract Factory design pattern
Goal
Design
Project – Abstract Factory
Project – the mid-range vehicle factory
Impacts of the Abstract Factory
Conclusion
The Singleton design pattern
Goal
Design
An alternative (better) way
Code smell – Ambient Context
Conclusion
Summary
Questions
Answers
Dependency Injection
What is dependency injection?
The composition root
Striving for adaptability
Understanding the use of the IoC container
The role of an IoC container
Code smell – Control Freak
Stable dependencies
Volatile dependencies
Conclusion
Object lifetime
What’s an object’s lifetime?
.NET object lifetime
Registering our dependencies
Registering your features elegantly
Project – Registering the demo feature
Using external IoC containers
Revisiting the Strategy pattern
Constructor injection
Property injection
Method injection
Project – Strategy
Shared building blocks
Control Freak controllers
Injecting an implementation in the controllers
Injecting an abstraction in the controller
Constructing the InjectAbstractionLocationsController
Conclusion
Revisiting the Singleton pattern
Project – Application state
First implementation
Second implementation
Using the implementations
Project – Wishlist
Conclusion
Understanding guard clauses
Understanding the Service Locator pattern
Project – ServiceLocator
Implementing method injection
Implementing constructor injection
Implementing a minimal API
Conclusion
Revisiting the Factory pattern
Project – Factory
Summary
Questions
Further reading
Answers
Application Configuration and the Options Pattern
Loading the configuration
Learning the options interfaces
IOptionsMonitor<TOptions>
IOptionsFactory<TOptions>
IOptionsSnapshot<TOptions>
IOptions<TOptions>
Exploring common usage scenarios
Manual configuration
Using the settings file
Injecting options
Named options
IOptionsFactory<MyOptions>
IOptionsMonitor<MyOptions>
IOptionsSnapshot<MyOptions>
Bind options to an existing object
Reloading options at runtime
Learning options configuration
Creating the program
Configuring the options
Implementing a configurator object
Adding post-configuration
Using multiple configurator objects
Exploring other configuration possibilities
Validating our options objects
Eager validation
Data annotations
Validation types
Validating options using FluentValidation
Injecting options objects directly
Centralizing the configuration for easier management
Using the configuration-binding source generator
Using the options validation source generator
Using the ValidateOptionsResultBuilder class
Wrapping up
Summary
Questions
Further reading
Answers
Logging Patterns
What is logging?
Writing logs
Log levels
Logging providers
Configuring logging
Structured logging
Summary
Questions
Further reading
Answers
Section 3: Component Patterns
Structural Patterns
The Decorator design pattern
Goal
Design
Project – Adding behaviors
DecoratorA
DecoratorB
Project – Decorator using Scrutor
Conclusion
The Composite design pattern
Goal
Design
Project – BookStore
Conclusion
The Adapter design pattern
Goal
Design
Project – Greeter
Conclusion
The Façade design pattern
Goal
Design
Project – The façades
Opaque façade
Transparent façade
The program
Flexibility in action
Alternative façade patterns
Conclusion
Summary
Questions
Further reading
Answers
Behavioral Patterns
The Template Method pattern
Goal
Design
Project – Building a search machine
Unit tests
Conclusion
The Chain of Responsibility pattern
Goal
Design
Project – Message interpreter
Conclusion
Mixing the Template Method and Chain of Responsibility patterns
Project – Improved message interpreter
Project – A final, finer-grained design
Conclusion
Summary
Questions
Answers
Operation Result Pattern
The Operation Result pattern
Goal
Design
Project – Implementing different Operation Result patterns
The Program.cs file
The simplest form of the Operation Result pattern
A single error message
Adding a return value
Multiple error messages
Adding message severity
Sub-classes and factories
Project – Registration Application
Advantages and disadvantages
Advantages
Disadvantages
Conclusion
Summary
Questions
Further reading
Answers
Section 4: Application Patterns
Anti-pattern – Big Ball of Mud
Layering and Clean Architecture
Introducing layering
Classic layering model
Splitting the layers
Layers versus tiers versus assemblies
What is a tier?
What is a layer?
What is an assembly?
Responsibilities of the common layers
Presentation
Domain
Rich domain model
Anemic domain model
The Service layer
Data
Overview of the Repository pattern
Overview of the Unit of Work pattern
Abstract layers
Sharing the model
Clean Architecture
Implementing layering in real life
To be or not to be a purist?
Building a façade over a database
Summary
Questions
Further reading
Answers
Object Mappers
The Object Mapper pattern
Goal
Design
Project – Mapper
Conclusion
Code smell – too many dependencies
Overview of the Aggregate Services pattern
Implementing a mapping façade
Implementing a mapping service
Exploring AutoMapper
Project – AutoMapper
Exploring Mapperly
Project – Mapperly
Summary
Questions
Further reading
Answers
Mediator and CQS Patterns
A high-level overview of Vertical Slice Architecture
The Mediator pattern
Goal
Design
Project – Mediator (IMediator)
Project – Mediator (IChatRoom)
Conclusion
The CQS pattern
Goal
Design
Project – CQS
Conclusion
Code smell – Marker Interfaces
Metadata
Dependency identifier
Using MediatR as a mediator
Project – Clean Architecture with MediatR
Summary
Questions
Further reading
Answers
Getting Started with Vertical Slice Architecture
Vertical Slice Architecture
What are the advantages and disadvantages?
Advantages
Disadvantages
Downsides or upsides?
Project – Vertical Slice Architecture
Project organization
Exploring a feature
Testing
Continuing your journey – a few tips and tricks
Agile and DevOps synergy
Conclusion
Summary
Questions
Further reading
Answers
Request-EndPoint-Response (REPR)
The Request-EndPoint-Response (REPR) pattern
Goal
Design
Project – SimpleEndpoint
Feature: ShuffleText
Feature: RandomNumber
Feature: UpperCase
Conclusion
An e-commerce application—a slice of the real-world
Assembling our stack
Dissecting the code structure
Exploring the shopping basket
AddItem feature
Managing exception handling
Creating an exception handler middleware
Exception handling using ExceptionMapper
Leveraging exceptions to propagate errors
Gray-box testing
AddItemTest
Summary
Questions
Further reading
Answers
Introduction to Microservices Architecture
What are microservices?
Cohesive unit of business
Ownership of data
Microservice independence
An introduction to Event-Driven Architecture
Types of events
Domain events
Integration events
Application events
Enterprise events
Getting started with message queues
Overview of the Publish-Subscribe pattern
Message brokers
Overview of the Event Sourcing pattern
Example
Conclusion
Introducing Gateway patterns
Overview of the Gateway Routing pattern
Overview of the Gateway Aggregation pattern
Overview of the Backend for Frontend pattern
Mixing and matching gateways
Conclusion
Project – BFF
Layering APIs
Advantages of a layered API design
Disadvantages of a layered API design
Running the microservices
Manually starting the projects
Using Docker Compose to run the projects
Briefly testing the services
Creating typed HTTP clients using Refit
Creating a service that serves the current customer
Features
Fetching the catalog
Fetching the shopping cart
Managing the shopping cart
Conclusion
Revisiting the CQRS pattern
Advantages and potential risks
Benefits of the CQRS pattern
Potential risks of using the CQRS pattern
Conclusion
Overview of the Microservice Adapter pattern
Adapting an existing system to another
Decommissioning a legacy application
Adapting an event broker to another
Conclusion
Summary
Questions
Further reading
Answers
Modular Monolith
What is a Modular Monolith?
What are traditional monoliths?
What are microservices?
Advantages of Modular Monoliths
Key components of a Modular Monolith
Implementing a Modular Monolith
Planning the project
Analyzing the domain
Identifying the modules
Identifying the interactions between modules
Defining our stack
The module structure
The URI space
The data space
The message broker
Project – Modular Monolith
Sending events from the catalog module
Consuming the events from the basket module
Inside the aggregator
Exploring the REST API HttpClient
Sending HTTP requests to the API
Validating the existence of a product
Transitioning to microservices
Challenges and pitfalls
Summary
Questions
Further reading
Answers
An end is simply a new beginning
Other Books You May Enjoy
Index
Cover
Index
Once you’ve read Architecting ASP.NET Core Applications, Third Edition, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.
Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.
Thanks for purchasing this book!
Do you like to read on the go but are unable to carry your print books everywhere?
Is your eBook purchase not compatible with the device of your choice?
Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.
Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.
The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily
Follow these simple steps to get the benefits:
Scan the QR code or visit the link belowhttps://packt.link/free-ebook/9781805123385
Submit your proof of purchaseThat’s it! We’ll send your free PDF and other benefits to your email directlyThis section delves into the core architectural principles and testing methodologies that form the backbone of modern software engineering practices, setting a solid foundation for the book. By exploring these essential concepts, you gain the insights needed to navigate the complexities of software architecture and ASP.NET Core development.
We begin with an introduction to the book’s structure and the essential knowledge required to make the most out of it, including a brief overview of design patterns, their significance, and the role of experience in shaping architectural decisions. We emphasize understanding rather than memorizing patterns, encouraging you to experiment and learn through hands-on experimentation.
The journey continues with a look at automated testing, a critical component of software quality assurance. Chapter 2, Automated Testing, introduces the fundamentals of testing, including unit tests, integration tests, and the principles of test-driven development (TDD). With a focus on the xUnit framework, you will learn how to apply various testing strategies to improve code reliability and maintainability. This chapter introduces integrating testing into the development process, emphasizing its role in modern workflows.
We explore architectural principles in Chapter 3, Architectural Principles, where we dissect the building blocks of software architecture, including discussions on responsibility segregation, the significance of design patterns, and strategies for creating robust and scalable systems. The chapter aims to equip you with the knowledge to make informed architectural decisions, emphasizing the rationale behind common design choices.
Chapter 4, REST APIs, covers REST APIs, an essential aspect of modern web development. This chapter provides a practical guide to designing and implementing RESTful APIs, including best practices and considerations for security, performance, and scalability. Through this exploration, you will understand how REST APIs fit into the architectural landscape.
By the end of these chapters, you will understand crucial concepts and have a strong foundation in testing methodologies, architectural principles, and REST APIs. Tying these concepts together lays the groundwork for a successful journey into software architecture, development, and the rest of the book.
This section comprises the following chapters:
Chapter 1, IntroductionChapter 2, Automated TestingChapter 3, Architectural PrinciplesChapter 4, REST APIsThis book organizes chapters according to scale and topic, which allows you to start with a solid foundation and gradually build upon it, much like constructing a program. We aim to delve into the thought processes behind the systems we design from a software engineer’s perspective.
While many resources focus on presenting just a handful of ways to apply design patterns, this book diverges from that path. We emphasize understanding the underlying principles and the rationale behind architectural choices hands-on.
This is not a magic recipe book. From experience, there is no magical recipe when designing software, only your logic, knowledge, experience, and analytical skills. Let’s define “experience” as your past successes and failures. And don’t worry, you will fail during your career, but don’t get discouraged by it. The faster you fail, the faster you can recover and learn, leading to successful products. Many techniques covered in this book should help you achieve success. Everyone has failed and made mistakes; you aren’t the first and certainly won’t be the last. To paraphrase a well-known saying by Roosevelt: The people who never fail are the ones who never do anything.
At a high level, this book introduces you sequentially to topics including:
Principles and methodologies: Automated testing, architectural principles, the fundamentals of REST APIs, and so onDesigning with ASP.NET Core: ASP.NET Core mechanisms, such as minimal APIs, MVC, and dependency injectionComponent patterns: Create small chunks of software by leveraging Gang of Four structural and behavioral patternsApplication patterns: Explore ways to structure an application, from layering to microservices architectureSome subjects covered throughout the book could have a book of their own, so after this book, you should have plenty of ideas about where to continue your journey into software architecture.
Here are a few pointers about this book that are worth mentioning:
The chapters are organized to start with small-scale patterns and then progress to higher-level ones, making the learning curve easier.Instead of giving you a recipe, the book focuses on the thinking behind the task at hand and shows the evolution of some techniques to help you understand why the shift happened.Many use cases combine more than one design pattern to illustrate alternate usage so you can understand and use the patterns efficiently. This also shows that design patterns are not beasts to tame but tools to use, manipulate, and bend to your will.As in real life, no textbook solution can solve all our problems; real problems are always more complicated than what’s explained in textbooks. In this book, I will show you how to mix and match patterns to think “architecture” instead of giving you step-by-step instructions to reproduce.The rest of this chapter introduces the concepts we explore throughout the book. We cover refreshers on a few notions—for intermediate and seasoned developers—to ensure everyone is on the same page. We also touch on .NET, its tooling, and some technical requirements.
In this chapter, we will cover the following topics:
What is a design pattern?Anti-patterns and code smellUnderstanding the web: request/responseGetting started with .NETSince you just purchased a book about design patterns, I guess you have some idea of what design patterns are, but let’s make sure that we are on the same page.
”A design pattern is a proven technique that we can use to solve a specific problem.”
In this book, we apply different patterns to solve various problems and leverage some open-source tools to go further, faster! Abstract definitions make people sound smart, but understanding concepts requires more practice, and there is no better way to learn than by experimenting with something. Design patterns are no different.
If that definition does not make sense to you yet, don’t worry. You should have enough information by the end of the book to correlate the multiple practical examples and explanations with that definition, making it crystal clear.
I like to compare programming to playing with LEGO® because what you must do is very similar: put small pieces together to create something bigger. With a lack of guidance and experience, your construction might not be as sturdy as it could be. With that analogy in mind, a design pattern is a plan to assemble a solution that fits one or more scenarios. For example, when building a castle, we could design a single tower (a plan) and produce it multiple times by following the same steps (instances of the plan). Design patterns act as that tower plan and give you the tools to assemble reliable pieces to improve your masterpiece (program).
However, instead of snapping LEGO® blocks together, you nest code blocks and interweave objects in a virtual environment.
Applying design patterns effectively will improve your designs, whether designing a small component or a whole system. However, be careful; throwing patterns into the mix just to use them can lead to the opposite result: over-engineering. Instead, aim to write the least amount of readable code that solves your issue or automates your process.
As we have briefly mentioned, design patterns apply to different software engineering levels, and in this book, we start small and grow to a cloud scale. We follow a smooth learning curve, starting with simpler patterns and code samples that bend good practices to focus on the patterns—and finally ending with more advanced topics and good practices.
Of course, some subjects are overviews more than deep dives, like automated testing, because no one can fit it all in a single book. Nonetheless, I have made sure to give you as much information about architecture-related subjects as possible to ensure the proper foundations are in place for you to get as much as possible out of the more advanced topics, and I sincerely hope you’ll find this book a helpful and enjoyable read.
Let’s start with the opposite of design patterns because it is essential to identify wrong ways of doing things to avoid making those mistakes or to correct them when you see them. Of course, knowing the right way to overcome specific problems using design patterns is also crucial.
Anti-patterns are proven bad architectural practices, while code smells are tips about possible flawed design. Learning about bad practices is as important as learning about the best ones. The book highlights multiple anti-patterns and code smells to help you get started. Here, we briefly explore a few of them.
An anti-pattern is the opposite of a design pattern: it is a proven flawed technique that will most likely cause you trouble and cost you time and money (and probably give you headaches).
An anti-pattern is a pattern that seems a good idea and seems to be the solution you were looking for, but it causes more harm than good. Some anti-patterns started as legitimate design patterns and were labeled anti-patterns later. Sometimes, it is a matter of opinion, and sometimes the classification can be influenced by the programming language or technologies.
Let’s begin with an example. We will explore some other anti-patterns throughout the book.
A God class is a class that handles too many things. Typically, this class serves as a central entity that many other classes inherit or use within the application. It is the class that knows and manages everything in the system; it is the class. On the other hand, it is also the class that nobody wants to update, which breaks the application every time somebody touches it: it is an evil class.
The best way to fix this is to segregate responsibilities and allocate them to multiple classes rather than concentrating them in a single class. We look at how to split responsibilities throughout the book, which helps create more robust software.
If you have a personal project with a God class at its core, start by reading the book and then try to apply the principles and patterns you learn to divide that class into multiple smaller classes that interact together. Try to organize those new classes into cohesive units, modules, or assemblies.
To help fix God classes, we dive into architectural principles in Chapter 3, Architectural Principles, opening the way to concepts such as responsibility segregation.
A code smell is an indicator of a possible problem. It points to areas of your design that could benefit from a redesign. By “code smell,” we mean “code that stinks” or “code that does not smell right.”
It is important to note that a code smell only indicates the possibility of a problem; it does not mean a problem exists. Code smells are usually good indicators, so it is worth analyzing your software’s “smelly” parts.
An excellent example is when a method requires many comments to explain its logic. That often means that the code could be split into smaller methods with proper names, leading to more readable code and allowing you to get rid of those pesky comments.
Another note about comments is that they don’t evolve, so what often happens is that the code described by a comment changes but the comment remains the same. That leaves a false or obsolete description of a block of code that can lead a developer astray.
The same is also true with method names. Sometimes, the method’s name and body tell a different story, leading to the same issues. Nevertheless, this happens less often than orphan or obsolete comments since programmers tend to read and write code better than spoken language comments. Nonetheless, keep that in mind when reading, writing, or reviewing code.
An excellent example of a code smell is using the new keyword. This indicates a hardcoded dependency where the creator controls the new object and its lifetime. This is also known as the Control Freak anti-pattern, but I prefer to box it as a code smell instead of an anti-pattern since the new keyword is not intrinsically wrong.
At this point, you may be wondering how it is possible not to use the new keyword in object-oriented programming, but rest assured, we will cover that and expand on the Control Freak code smell in Chapter 7, Strategy, Abstract Factory, and Singleton Design Patterns.
The long methods code smell is when a method extends to more than a certain number of lines of code. For most teams, 10 to 15 lines is enough to fall into this specific case. That is a good indicator that you should think about that method differently. Having comments that separate multiple code blocks is a good indicator of a method that may be too long.
Here are a few examples of what the case might be:
The method contains complex logic intertwined in multiple conditional statements.The method contains a big switch block.The method does too many things.The method contains duplications of code.To fix this, you could do the following:
Extract one or more private methods.Extract some code to new classes.Reuse the code from external classes.If you have a lot of conditional statements or a huge switch block, you could leverage a design pattern such as the Chain of Responsibility, or CQS, which you will learn about in Chapter 12, Behavioral Patterns, and Chapter 16, Mediator and CQS Patterns.Usually, each problem has one or more solutions; you need to spot the problem and then find, choose, and implement one of the solutions. Let’s be clear: a method containing 16 lines does not necessarily need refactoring; it could be OK. Remember that a code smell indicates that there might be a problem, not that there necessarily is one—apply common sense.
Before going any further, as an ASP.NET Core developer, it is imperative to understand the basic concept of the web. The idea behind HTTP 1.X is that a client sends an HTTP request to a server, and then the server responds to that client. That can sound trivial if you have web development experience. However, it is one of the most important web programming concepts, irrespective of whether you are building web APIs, websites, or complex cloud applications.
Let’s reduce an HTTP request lifetime to the following:
The communication starts.The client sends a request to the server.The server receives the request.The server does something with the request, like executing code/logic.The server responds to the client.The communication ends.After that cycle, the server is no longer aware of the client. Moreover, if the client sends another request, the server is unaware that it responded to a request earlier for that same client because HTTP is stateless.
There are mechanisms for creating a sense of persistence between requests for the server to be “aware” of its clients. The most well-known of these is cookies.
If we dig deeper, an HTTP request comprises a header and an optional body. Then, requests are sent using a specific method. The most common HTTP methods are GET and POST. On top of those, extensively used by web APIs, we can add PUT, DELETE, and PATCH to that list.
Here is a quick reference table; we will explore the concept of idempotence afterward:
Method
Request has body
Response has body
Idempotent
GET
No*
Yes
Yes
POST
Yes
Yes
No
PUT
Yes
No
Yes
PATCH
Yes
Yes
No
DELETE
May
May
Yes
Table 1.1: Structure of common HTTP methods
* Sending a body with a GET request is not forbidden by the HTTP specifications, but the semantics of such a request are not defined either. It is best to avoid sending GET requests with a body.
An idempotent request is a request that always yields the same result, whether it is sent once or multiple times. For example, sending the same DELETE request multiple times should delete a single entity (idempotent), while sending the same POST request multiple times should create multiple similar entities (not idempotent). The status code of an idempotent request may vary, but the server state should remain the same. We explore those concepts in more depth in Chapter 4, REST APIs.
Now that we have explored HTTP methods, let’s have a look at an example of a GET request to become more familiar with HTTP:
GET http: //www.forevolve.com/ HTTP/1.1 Host: www.forevolve.com Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9,fr-CA;q=0.8,fr;q=0.7 Cookie: ...The HTTP header comprises a list of key/value pairs representing metadata that a client wants to send to the server. In this case, I queried my blog using the GET method, and Google Chrome attached some additional information to the request. I replaced the Cookie header’s value with ... because it can be pretty large and that information is irrelevant to this sample. Nonetheless, cookies are passed back and forth like any other HTTP header.
Important note about cookies and request size
The client sends cookies, and the server returns them for every request-response cycle. This could kill your bandwidth or slow down your application if you pass too much information back and forth (cookies or otherwise). An example would be a serialized identity cookie that is very large.
Another example, unrelated to cookies but that created such a back-and-forth, was the good old Web Forms ViewState. This was a hidden field sent with every request. That field could become very large when left unchecked.
Nowadays, with high-speed internet, it is easy to forget about those issues, but they can significantly impact the user experience of someone on a slow network.
When the server decides to respond to the request, it returns a header and an optional body, following the same principles as the request. The first line indicates the request’s status: whether it was successful. In our case, the status code was 200, which indicates success. Each server can add more or less information to its response. You can also customize the response with code.
Here is the response to the previous request, which mainly contains the response header as I removed most of the body for brevity reasons:
HTTP/1.1 200 OK Server: GitHub.com Content-Type: text/html; charset=utf-8 Last-Modified: Wed, 03 Oct 2018 21:35:40 GMT ETag: W/"5bb5362c-f677" Access-Control-Allow-Origin: * Expires: Fri, 07 Dec 2018 02:11:07 GMT Cache-Control: max-age=600 Content-Encoding: gzip X-GitHub-Request-Id: 32CE:1953:F1022C:1350142:5C09D460 Content-Length: 10055 Accept-Ranges: bytes Date: Fri, 07 Dec 2018 02:42:05 GMT Via: 1.1 varnish Age: 35 Connection: keep-alive X-Served-By: cache-ord1737-ORD X-Cache: HIT X-Cache-Hits: 2 X-Timer: S1544150525.288285,VS0,VE0 Vary: Accept-Encoding X-Fastly-Request-ID: 98a36fb1b5642c8041b88ceace73f25caaf07746 <!DOCTYPE html> <html lang="en"> Response body truncated for brevity </html>Now that the browser has received the server’s response, it renders the HTML web page. Then, for each resource, it sends another HTTP call to its URI and loads it. A resource is an external asset, such as an image, a JavaScript file, a CSS file, or a font.
After the response, the server is no longer aware of the client; the communication has ended. It is essential to understand that to create a pseudo-state between each request, we need to use an external mechanism. That mechanism could be ASP.NET Core session state (leveraging cookies under the hood), manually using cookies, or we could create a stateless application. I recommend going stateless whenever possible. We will write primarily stateless applications in the book.
If you want to learn more about session and state management, I left a link in the Further reading section at the end of the chapter.
As you can imagine, the backbone of the internet is its networking stack, built upon the Open Systems Interconnection model (OSI model), which defines seven layers. The Hypertext Transfer Protocol (HTTP) is the highest layer of that stack (layer 7). HTTP is an application layer built on the Transmission Control Protocol (TCP). TCP (layer 4) is the transport layer, which defines how data is moved over the network (for instance, the transmission of data, the amount of transmitted data, and error checking). TCP uses the Internet Protocol (IP) layer to reach the computer it tries to talk to. IP (layer 3) represents the network layer, which handles packet IP addressing. Understanding the book’s content does not require OSI proficiency, but you cannot have too much foundational knowledge. See the Further reading section for more on this subject.
A packet is a chunk of data that is transmitted over the wire. We could send a large payload directly from a source to a destination machine, but that is not practical, so the network layer breaks down the payload into smaller packets. The network layer uses the maximum transmission unit (MTU) to know whether it needs to break down payloads.
For example, Machine A (source) sends a file to Machine B (destination), and the router (network layer) breaks the payload (file) into multiple packets, sends them to Machine B, and then Machine B reassembles the packets back into the file sent by Machine A. This process allows numerous senders to use the same wire instead of waiting for the first transmission to be done. If a packet gets lost in transit, the source machine can also send only that packet back to the target machine.
Rest assured, you don’t need to understand every detail behind networking to program web applications, but it is always good to know that HTTP uses TCP/IP and chunks big payloads into smaller packets. Moreover, HTTP/1 limits the number of parallel requests a browser can open simultaneously. This knowledge can help you optimize your apps. For example, a high number of assets to load, their size, and the order in which they are sent to the browser can increase the page load time, the perceived page load time, or the paint time.
To conclude this subject and not dig too deep into networking, HTTP/1 is older but foundational. HTTP/2 is more efficient and supports streaming multiple assets using the same TCP connection. It also allows the server to send assets to the client before it requests the resources, called a server push.
If you find HTTP interesting, HTTP/2 is an excellent place to start digging deeper, as well as the HTTP/3 proposed standard that uses the QUIC transport protocol instead of HTTP (RFC 9114). ASP.NET Core 7.0+ supports HTTP/3, which is enabled by default in ASP.NET Core 8.0.
Next, let’s quickly explore .NET.
Let’s start with a bit of history. .NET Framework 1.0 was first released in 2002. .NET is a managed framework that compiles your code into an Intermediate Language (IL) named Microsoft Intermediate Language (MSIL). That IL code is then compiled into native code and executed by the Common Language Runtime (CLR). The CLR is now known simply as the .NET runtime. After releasing several versions of .NET Framework, Microsoft never delivered on the promise of an interoperable stack. Moreover, many flaws were built into the core of .NET Framework, tying it to Windows.
Mono, an open-source project, was developed by the community to enable .NET code to run on non-Windows OSs. Mono was used and supported by Xamarin, acquired by Microsoft in 2016. Mono enabled .NET code to run on other OSes like Android and iOS. Later, Microsoft started to develop an official cross-platform .NET Software Development Kit (SDK) and runtime that they named .NET Core.
The .NET team did a magnificent job building ASP.NET Core from the ground up, cutting out compatibility with the older .NET Framework versions. That brought its share of problems at first, but .NET Standard alleviated the interoperability issues between the old .NET and the new .NET.
After years of improvements and two major versions in parallel (Core and Framework), Microsoft reunified most .NET technologies into .NET 5+ and the promise of a shared Base Class Library (BCL). With .NET 5, .NET Core simply became .NET while ASP.NET Core remained ASP.NET Core. There is no .NET “Core” 4, to avoid any potential confusion with .NET Framework 4.X.
New major versions of .NET are released every year now. Even-numbered are Long-Term Support (LTS) releases with free support for three years, and odd-numbered releases (Current) have free support for only 18 months.
The good thing behind this book is that the architectural principles and design patterns covered should remain relevant in the future and are not tightly coupled with the versions of .NET you are using. Minor changes to the code samples should be enough to migrate your knowledge and code to new versions.
Next, let’s cover some key information about the .NET ecosystem.
You can install different binaries grouped under SDKs and runtimes. The SDK allows you to build and run .NET programs, while the runtime only allows you to run .NET programs.
As a developer, you want to install the SDK on your deployment environment. On the server, you want to install the runtime. The runtime is lighter, while the SDK contains more tools, including the runtime.
When building .NET projects, there are multiple types of projects, but basically, we can separate them into two categories:
ApplicationsLibrariesApplications target a version of .NET, such as net5.0 and net6.0. Examples of that would be an ASP.NET Core application or a console application.
Libraries are bundles of code compiled together, often distributed as a NuGet package. .NET Standard class library projects allow sharing code between .NET 5+, and .NET Framework projects. .NET Standard came into play to bridge the compatibility gap between .NET Core and .NET Framework, which eased the transition. Things were not easy when .NET Core 1.0 first came out.
With .NET 5 unifying all the platforms and becoming the future of the unified .NET ecosystem, .NET Standard is no longer needed. Moreover, app and library authors should target the base Target Framework Moniker (TFM), for example, net8.0. A TFM is a way to identify and target a certain version of .NET (net8.0 targets .NET 8 while net8.0-ios targets .NET 8 for IOS devices). You can also target netstandard2.0 or netstandard2.1 when needed, for example, to share code with .NET Framework. Microsoft also introduced OS-specific TFMs with .NET 5+, allowing code to use OS-specific APIs like net8.0-android and net8.0-tvos, which give access to OS-specific APIs. You can also target multiple TFMs when needed.
I’m sure we will see .NET Standard libraries stick around for a while. All projects will not just migrate from .NET Framework to .NET 5+ magically, and people will want to continue sharing code between the two.
The next versions of .NET are built over .NET 5+, while .NET Framework 4.X will stay where it is today, receiving only security patches and minor updates. For example, .NET 8 is built over .NET 7, iterating over .NET 6 and 5.
Next, let’s look at some tools and code editors.
How can one of these projects be created? .NET Core comes with the dotnetcommand-line interface (CLI), which exposes multiple commands, including new. Running the dotnet new command in a terminal generates a new project.
To create an empty class library, we can run the following commands:
md MyProject cd MyProject dotnet new classlibThat would generate an empty class library in the newly created MyProject directory.
The -h