Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Web API Development with ASP.NET Core 8
Web API Development with ASP.NET Core 8

Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs

By Xiaodi Yan
€29.99 €20.98
Book Apr 2024 804 pages 1st Edition
eBook
€29.99 €20.98
Print
€37.99
Subscription
€14.99 Monthly
eBook
€29.99 €20.98
Print
€37.99
Subscription
€14.99 Monthly

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Apr 5, 2024
Length 804 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781804610954
Vendor :
Microsoft
Category :
Concepts :
Table of content icon View table of contents Preview book icon Preview Book

Web API Development with ASP.NET Core 8

Getting Started with ASP.NET Core Web APIs

ASP.NET Core is a cross-platform, open-source web application framework for building modern, cloud-enabled web applications and APIs. It is primarily used with the C# programming language. ASP.NET Core provides features to help you build web apps in various ways – for example, through ASP.NET MVC, web APIs, Razor Pages, Blazor, and so on. This book will mainly cover web APIs. In this chapter, we will learn how to build a simple REST web API with ASP.NET Core.

In this chapter, we’ll be covering the following topics:

  • Setting up the development environment
  • Creating a simple REST web API project
  • Building and running the project
  • Understanding the MVC pattern
  • Dependency injection (DI)
  • Introduction to minimal APIs

This chapter will provide you with the necessary information to create a basic REST web API project with ASP.NET Core. By the end of this chapter, you should have a better understanding of the steps required to create your first ASP.NET Core web API project.

Technical requirements

You are expected to know the basic concepts of .NET Framework or .NET Core, and object-oriented programming (OOP). You should also have a basic understanding of the C# programming language. If you are not familiar with these concepts, you can refer to the following resources:

The code examples in this chapter can be found at https://github.com/PacktPublishing/Web-API-Development-with-ASP.NET-Core-8/tree/main/samples/chapter2.

Setting up the development environment

.NET Core is fully cross-platform and can run on Windows, Linux, and macOS, so you can use any of these platforms to develop ASP.NET Core applications. The code samples in this book are written on Windows 11. However, you can run the same code on Linux and macOS.

There are also several IDEs available for ASP.NET Core, such as Visual Studio, Visual Studio Code (VS Code), Visual Studio for Mac, and Rider. In this book, we will mainly use VS Code.

Why not Visual Studio?

Visual Studio is a powerful IDE for the .NET platform. It provides a bunch of tools and features to elevate and enhance every stage of software development. However, VS Code is more lightweight and is open-source and cross-platform. We will use VS Code to understand the concepts of ASP.NET Core, then migrate to Visual Studio to use its rich features. If you are familiar with Visual Studio or any other IDE, feel free to use it.

Here is a list of software, SDKs, and tools you need to install:

Both VS Code and the .NET 8 SDK are cross-platform, so please choose the correct one for your OS. When you install VS Code, please make sure you check the Add to PATH option.

If you use Windows, you may want to install Windows Terminal to run the command line. Windows Terminal is available for Windows 10 and above, and it provides a better user experience. But it is optional because you can also use the command line directly.

Configuring VS Code

Strictly speaking, VS Code is a code editor. It cannot recognize all the coding languages. Therefore, you’ll need to install some extensions to support your development workflow. You can browse and install extensions by clicking on the Extensions icon in the Activity bar on the left-hand side of the VS Code interface. Then, you will see a list of the most popular extensions on the VS Code Marketplace:

Figure 2.1 – The C# Dev Kit extension for VS Code

Figure 2.1 – Overview of the C# Dev Kit extension for VS Code

You need to install this extension to support .NET development:

  • C# Dev Kit: This is the official C# extension for VS Code provided by Microsoft. When you install C# Dev Kit, the following extensions will automatically be installed:
    • C# extension: This extension provides C# language support powered by OmniSharp
    • IntelliCode for C# Dev Kit: This extension provides AI-assisted IntelliSense for C#
    • .NET Runtime Install Tool: This extension provides a unified way to install local, private versions of the .NET runtime

The C# Dev Kit extension provides a lot of features to help you develop .NET applications. Press Ctrl + Shift + P (on Windows) or Command + Shift + P (on macOS) to open the Command Palette, then type .net to see the commands provided by the C# Dev Kit extension. You can use these commands to create new projects, generate assets for build and debug, run tests, and more.

You can also install the following extensions to improve your productivity:

  • EditorConfig for VS Code: This extension provides EditorConfig support for VS Code. EditorConfig helps teams of multiple developers maintain consistent coding styles when working on the same project across various editors and IDEs.
  • GitHub Copilot: GitHub Copilot is your AI pair programmer. You can get code suggestions in real-time based on your context and comments in VS Code. This extension is not free, but you can try it for free for 30 days. If you are a student, a teacher, or a maintainer of a popular open-source project, you can get it for free.

To configure EditorConfig, you can create a file named .editorconfig in the root folder of the project. You can find a sample EditorConfig file at https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options.

Checking the .NET SDK

Once you install the .NET SDK, you can check the version by running the following command:

dotnet --version

You should be able to see the version number as follows:

8.0.101-rc.2.23502.2

Microsoft releases new versions of .NET SDKs frequently. If you encounter a different version number, that is acceptable.

You can list all available SDKs by running the following command:

dotnet --list-sdks

The preceding command will list all the available SDKs on your machine. For example, it may show the following output if have multiple .NET SDKs installed:

6.0.415 [C:\Program Files\dotnet\sdk]7.0.402 [C:\Program Files\dotnet\sdk]
8.0.100 [C:\Program Files\dotnet\sdk]
8.0.101 [C:\Program Files\dotnet\sdk]

Multiple versions of .NET SDKs can be installed at the same time. We can specify the version of the .NET SDKs in the project file.

Which version of the SDKs should I use?

Every Microsoft product has a lifecycle. .NET and .NET Core provides Long-term support (LTS) releases that get 3 years of patches and free support. When this book was written, .NET 7 is still supported, until May 2024. Based on Microsoft's policy, even numbered releases are LTS releases. So .NET 8 is the latest LTS release. The code samples in this book are written with .NET 8.0.

To learn more about .NET support policies, please visit https://dotnet.microsoft.com/en-us/platform/support/policy.

We are now prepared to start developing ASP.NET Core applications. Let’s get to work!

Creating a simple REST web API project

In this section, we will use the .NET command-line interface (.NET CLI) to create a basic web API project and see how it works.

The .NET CLI is a command-line tool that helps you to create, develop, build, run, and publish .NET applications. It is included in the .NET SDK.

You have multiple ways to run .NET CLI commands. The most common way is to run the command in the terminal window or command prompt. Also, you can run the command in VS Code directly. VS Code provides an integrated terminal that starts at the root of your workspace. To open the terminal in VS Code, you can do any one of the following:

  • Press Ctrl + ` (on Windows) or Command + ` (on macOS) to open the terminal
  • Use the View | Terminal menu item to open the terminal
  • From the Command Palette, use the View: Toggle Terminal command to open the terminal

In the terminal, navigate to a folder where you want to create the project, then create a web API project by running the following command:

dotnet new webapi -n MyFirstApi -controllerscd MyFirstApi
code .

The preceding commands create a new web API project and open it in VS Code. dotnet new provides many options to create various types of projects, such as web APIs, console apps, class libraries, and so on.

There are some options we can use to specify the project:

  • -n|--name <OUTPUT_NAME>: The name for the created output. If not specified, the name of the current directory is used.
  • -o|--output <OUTPUT_PATH>: The output path for the created project. If not specified, the current directory is used.
  • -controllers|--use-controllers: Indicates whether to use controllers for actions. If not specified, the default value is false.
  • -minimal|--use-minimal-apis: Indicates whether to use minimal APIs. The default value is false, but the -controllers option will override the -minimal option. If neither -controllers nor -minimal is specified, the default value of the -controllers option, which is false, will be used, so a minimal API will be created.

Important note

Since .NET 6.0, ASP.NET Core 6.0 provides a new way to create web API projects, which is called minimal APIs. It is a simplified approach for building APIs without controllers. We will introduce minimal APIs later. For now, we will use the traditional way to create a web API project with controllers. So, we need to specify the --use-controllers option.

To learn more about the dotnet new command, check this page: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new. We will introduce more details on the dotnet command in the following sections.

When you use VS Code to open the project, the C# Dev Kit extension can create a solution file for you. This feature makes VS Code more friendly to C# developers. You can see the following structure in the Explorer view:

The reason is that VS 2022 will create a sln file for the project, but .NET CLI does not. When using VS Code to open the project, the C# DevKit will create the sln file. I think it's worth mentioning it here.

The C# Dev Kit extension provides a new feature, the solution explorer, which is located at the bottom. This feature is especially useful when working with multiple projects in one solution. You can drag and drop the SOLUTION EXPLORER to the top to make it more visible.

When you use VS Code to open the project, the C# Dev Kit extension can create a solution file for you. This feature makes VS Code more friendly to C# developers. You can see the following structure in the Explorer view:

Figure 2.2 – The solution explorer and the folder structure

Figure 2.2 – The solution explorer and the folder structure

Next, we can start to build and run the project.

Building and running the project

In this section, we will learn how to build and run the project and introduce some useful tools to help you test the APIs. To make it compatible with all platforms, we will use .NET CLI commands to build and run the project. We will also learn how to debug the project in VS Code.

Building the project

The easiest way to build and run the project is to use the dotnet command. You can run the following command to build the project:

dotnet build

The preceding command will build the project and its dependencies and generate a set of binaries. You can find these binaries in the bin folder. The bin folder is the default output folder for the dotnet build command. You can use the --output option to specify the output folder. However, it is recommended to use the default bin folder. The binaries are some Intermediate Language (IL) files with a .dll extension.

You might see the following popups when you use VS Code to open the project:

Figure 2.3 – VS Code prompts to restore dependencies

Figure 2.3 – VS Code prompts to restore dependencies

This is because VS Code inspects that the project is a .NET project, and it is trying to restore the dependencies. You can click the Restore button to restore the dependencies. Similarly, if you see other prompts from VS Code to add assets to debug the project, please select Yes in the dialog:

Figure 2.4 – VS Code prompts to add required assets to build and debug

Figure 2.4 – VS Code prompts to add required assets to build and debug

Some commands, such as dotnet build, dotnet run, dotnet test, and dotnet publish, will implicitly restore dependencies. So don't worry if you missed out on these prompts.

If no errors or warnings are shown, that means the build is successful.

Running the project

You can run the following command to run the project:

dotnet run

The dotnet run command is a convenient way of running the project from the source code. Keep in mind that it is useful in development, but not for production. The reason is that if the dependencies are outside of the shared runtime, the dotnet run command will resolve the dependencies from the NuGet cache. To run the application in production, you need to create a deployment package with the dotnet publish command and deploy it. We will explore the deployment process in future chapters.

You should be able to see the following output:

Building...info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7291
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5247
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\example_code\chapter2\MyFirstApi\MyFirstApi
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...

There is a link in the output, such as http://localhost:5247. The port number was randomly generated when we created the project. In a browser, navigate to http://localhost:<your_port>/swagger. You will see the web API documentation with Swagger UI, which offers a web-based UI to provide information and tools to interact with the API. You can use Swagger UI to test APIs:

Figure 2.5 – Swagger UI

Figure 2.5 – Swagger UI

The API project is now running! You can see the web API template provides a /WeatherForecast endpoint. If you navigate to the http://localhost:5247/WeatherForecast link in the browser, you will see the API response.

To support HTTPS, you may need to trust the HTTPS development certificate by running the following command:

dotnet dev-certs https --trust

You will see a dialog if the certificate was not previously trusted. Select Yes to trust the development certificate:

Figure 2.6 – Installing the certificate for local development

Figure 2.6 – Installing the certificate for local development

Please note that the preceding command does not work on Linux. See your Linux distribution documentation for more details.

Changing the port number

The port number is defined in the launchSettings.json file in the Properties folder. You can change the port number by editing the file. Based on the convention, when the web API project was created, a port from 5000 to 5300 will be selected for HTTP, and from 7000 to 7300 for HTTPS. Here is an example of the launchSettings.json file:

{  "$schema": "https://json.schemastore.org/launchsettings.json",
  ...
  "profiles": {
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "swagger",
      "applicationUrl": "https://localhost:7291;http://localhost:5247",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    ...
  }
}

You can update the port number here. Just keep in mind that the port number should be unique on your machine to avoid conflicts.

Hot Reload

When you use dotnet run to run the project, if you change the code, you need to stop the project and start it again. If your project is complicated, it takes time to stop and restart. To speed up development, you can use the dotnet watch command to enable the Hot Reload feature.

.NET Hot Reload is a feature that allows you to apply code changes to a running app without restarting the app. It was first provided with .NET 6. Instead of using dotnet run, you can use dotnet watch to activate Hot Reload in development. Once you update the code, the web browser will automatically refresh the page. However, Hot Reload does not support all code changes. In some cases, dotnet watch will ask you if you want to restart the application. There are some options: Yes, No, Always, and Never. Choose the appropriate option for the code change you want to apply, as shown next:

dotnet watch  File changed: .\Services\IService.cs.dotnet watch  Unable to apply hot reload because of a rude edit.
   Do you want to restart your app - Yes (y) / No (n) / Always (a) / Never (v)?

The API project is now running, and we can start to test the API.

Testing the API endpoint

The browser can send a GET request easily, but it is not as simple for POST endpoints. There are various ways to call the API for testing purposes, such as Swagger UI, Postman, and other tools. In this section, we will introduce some tools you can use in the development stage.

Swagger UI

We introduced how to use SwaggerHub to design APIs in Chapter 1. From version 5.0, ASP.NET Core enables OpenAPI support by default. It uses the Swashbuckle.AspNetCore NuGet package, which provides the Swagger UI to document and test the APIs.

We can use Swagger UI to test the API directly. Expand the first /WeatherForecast API in Swagger UI and click the Try it out button. You will see an Execute button. Click the button, and you will see the following response:

Figure 2.7 – Testing an endpoint in Swagger UI

Figure 2.7 – Testing an endpoint in Swagger UI

Figure 2.7 demonstrates that the API is functioning correctly and is providing the expected response. To learn more about Swagger and OpenAPI, you can check the following links:

Postman

Postman is a powerful API platform for building and using APIs. It is widely used by many individual developers and organizations. You can download it here: https://www.postman.com/downloads/.

Click the + button to create a new tab. Use http://localhost:5247/WeatherForecast as the URL. Then, click the Send button. You will see the response next:

Figure 2.8 – Using Postman to call the API

Figure 2.8 – Using Postman to call the API

Postman provides a rich set of features to test APIs. To learn more about Postman, check the official documentation: https://learning.postman.com/docs/getting-started/introduction/.

HttpRepl

HTTP Read-Eval-Print Loop (HttpRepl) is a command-line tool that allows you to test APIs. It is lightweight and cross-platform, so it can run anywhere. It supports the GET, POST, PUT, DELETE, HEAD, OPTIONS, and PATCH HTTP verbs.

To install HttpRepl, you can use the following command:

dotnet tool install -g Microsoft.dotnet-httprepl

After the installation, you can use the following command to connect to our API:

httprepl <ROOT URL>/

<ROOT URL> is the base URL of the web API, such as the following:

httprepl http://localhost:5247/

After the connection is built, you can use the ls or dir command to list the endpoints, such as the following:

http://localhost:5247/> ls.                 []
WeatherForecast   [GET]

The preceding command shows the WeatherForecast endpoint supports a GET operation. Then, we can use the cd command to navigate to the endpoint, such as the following:

http://localhost:5247/> cd WeatherForecast/WeatherForecast    [GET]

Then, we can use the get command to test the endpoint, such as the following:

http://localhost:5247/WeatherForecast> get

The output looks like this:

Figure 2.9 – Output of HttpRepl

Figure 2.9 – Output of HttpRepl

To disconnect, press Ctrl + C to exit.

You can find more information about HttpRepl at https://docs.microsoft.com/en-us/aspnet/core/web-api/http-repl/.

Thunder Client

If you prefer to do everything in VS Code, Thunder Client is a great tool for testing APIs. Thunder Client is a lightweight REST API client extension for VS Code, allowing users to test their APIs without having to leave VS Code. This makes it an ideal choice for developers who want to streamline their workflow:

Figure 2.10 – The Thunder Client extension for VS Code

Figure 2.10 – The Thunder Client extension for VS Code

After the installation, click the Thunder Client icon on the Action bar. From the sidebar, click the New Request button. The following UI will be shown:

Figure 2.11 – Testing the API with Thunder Client

Figure 2.11 – Testing the API with Thunder Client

To learn more about Thunder Client, visit their GitHub page: https://github.com/rangav/thunder-client-support.

Using .http files in VS 2022

If you use Visual Studio 2022, you can use the .http file to test the API. The .http file is a text file that contains definitions of HTTP requests. The latest ASP.NET Core 8 template project provides a default .http file. You can find it in the MyFirstApi folder. The content of the file is as follows:

@MyFirstApi_HostAddress = http://localhost:5247GET {{MyFirstApi_HostAddress}}/weatherforecast/
Accept: application/json
###

The first line defines a variable named MyFirstApi_HostAddress with the value of the root URL of the API. The second line defines a GET request to the /weatherforecast endpoint. The third line defines an Accept header. In this case, it accepts the application/json content type. Open this file in Visual Studio 2022, and you will see the Send Request button on the left side of the request. Click the button, and you will see the response as follows:

Figure 2.12 – Using the .http file to test the API in Visual Studio 2022

Figure 2.12 – Using the .http file to test the API in Visual Studio 2022

However, when this book was written, the .http files lacked some features, such as environment variables. Also, this feature is only available in Visual Studio 2022, so we will not use it in this book. But if you are interested in exploring this feature further, please refer to the Microsoft docs at https://learn.microsoft.com/en-us/aspnet/core/test/http-files for more information.

We have introduced some tools to test APIs. Let's now learn how to debug APIs.

Debugging

VS Code has a built-in debugging feature that allows you to debug code. Unlike Visual Studio, it needs a launch.json configuration for debugging. When you open an ASP.NET Core project in VS Code, it will prompt you to add some assets. If you choose Yes, VS Code can generate a launch.json file in the .vscode folder.

If you missed it, you can add it manually from the Debug view:

Figure 2.13 – Creating a launch.json file from the Debug view

Figure 2.13 – Creating a launch.json file from the Debug view

If you cannot see the buttons in Figure 2.13, you can open the Command Palette by pressing Ctrl + Shift + P (on Windows) or Command + Shift + P (on macOS), then type .net and choose .NET: Generate Assets for Build and Debug. It will then generate a launch.json file in the .vscode folder:

Figure 2.14 – Generating a launch.json file from the Command Palette

Figure 2.14 – Generating a launch.json file from the Command Palette

The content of the default launch.json configuration is shown next:

{    "version": "0.2.0",
    "configurations": [
        {
            // Use IntelliSense to find out which attributes exist for C# debugging
            // Use hover for the description of the existing attributes
            // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md.
            "name": ".NET Core Launch (web)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            // If you have changed target frameworks, make sure to update the program path.
            "program": "${workspaceFolder}/bin/Debug/net8.0/MyFirstApi.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
            "serverReadyAction": {
                "action": "openExternally",
                "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
            },
            "env": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            },
            "sourceFileMap": {
                "/Views": "${workspaceFolder}/Views"
            }
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach"
        }
    ]
}

This file specifies the configuration for debugging. Some important attributes are described next:

  • The program attribute specifies the path to the executable file
  • The args attribute specifies the arguments to pass to the executable file
  • The cwd attribute specifies the working directory
  • The env attribute specifies the environment variables

We do not need to change anything in this file now.

Set up a breakpoint in the app. For example, we can set a breakpoint in the Get() method in the WeatherForecastController.cs file by clicking in the left margin of the code window. Once the breakpoint is set, you will see a red dot before the line number in the left margin:

Figure 2.15 – Setting up a breakpoint in VS Code

Figure 2.15 – Setting up a breakpoint in VS Code

To debug the app, open the Debug view by selecting the Debugging icon on the left-side menu. Make sure you choose the correct debugging configuration from the drop-down menu. For this case, please select .NET Core Launch (web). Then, select the green arrow at the top of the pane:

Figure 2.16 – Debugging the API in VS Code

Figure 2.16 – Debugging the API in VS Code

Send a request from any of the tools in the previous section, and you will see the program execution stops when it reaches the breakpoint, as shown next:

Figure 2.17 – Hitting the breakpoint in VS Code

Figure 2.17 – Hitting the breakpoint in VS Code

The Locals section of the VARIABLES window will display the values of variables that are defined in the current context.

You can also type a variable in the DEBUG CONSOLE window to check the value directly. To execute the next step, you can use the control toolbar at the top of the VS Code window. You can run the code line by line to monitor its execution. It is helpful if we need to know how the program works.

Now we have learned how to build, run, and test APIs, it is time to look at the code of APIs.

Understanding the MVC pattern

ASP.NET Core MVC is a rich framework for building web applications with the Model-View-Controller (MVC) design pattern. The MVC pattern enables web applications to separate the presentation from the business logic. An ASP.NET Core web API project follows the basic MVC pattern, but it does not have views, so it only has a Model layer and a Controller layer. Let’s look at this in a bit more detail:

  • Models: Models are classes that represent the data that is used in the application. Normally, the data is stored in a database.
  • Controllers: Controllers are classes that handle the business logic of the application. Based on the convention of ASP.NET Core, controllers are stored in the Controllers folder. Figure 2.18 shows an example of the MVC pattern in an web API project. However, the view layer is not included in the web API project. The request from the client will be mapped to the controller, and the controller will execute the business logic and return the response to the client.
Figure 2.18 – The MVC pattern

Figure 2.18 – The MVC pattern

Next, we will look at the code of the model and the controller in an ASP.NET Core web API project.

The model and the controller

In the ASP.NET Core template project, you can find a file named WeatherForecast.cs. This file is a model. It is a pure C# class that represents a data model.

The controller is the WeatherForecastController.cs file located in the Controllers folder. It contains the business logic.

It looks like this:

[ApiController][Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    // Some code is ignored
    private readonly ILogger<WeatherForecastController> _logger;
    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }
    [HttpGet(Name = "GetWeatherForecast")]
    public IEnumerable<WeatherForecast> Get()
    {
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

The constructor of the controller class has a parameter named ILogger<WeatherForecastController> logger. This parameter is used to log messages. It is injected with DI by the ASP.NET Core framework. We will talk about DI in the next section.

This class has an [ApiController] attribute that indicates that it is a web API controller. It also has a [Route("[controller]")] attribute that indicates the URL of the controller.

The Get() method has a [HttpGet(Name = "GetWeatherForecast")] attribute that indicates the name of the endpoint, and the Get() method is a GET operation. This method returns a list of weather forecasts as the response.

Note that the [Route("[controller]")] attribute is marked on the controller class. It means the path of the controller is /WeatherForecast. Currently, there is no [Route] attribute on the Get() method. We will learn more about routing in future sections.

We should now have a basic understanding of how ASP.NET Core web API works. The client sends the request to the web API, and the request will be mapped to the controller and the method. The controller will execute the business logic and return the response. We can use some methods to get, save, update, and delete data from the database in the controllers.

Next, let us create a new API endpoint by adding a new model and controller.

Creating a new model and controller

In Chapter 1, we showed an example REST API on https://jsonplaceholder.typicode.com/posts. It returns a list of posts, as shown next:

[  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  ...
]

Let us implement a similar API. First, we need to create a new model. Create a new folder named Models in the project. Then, create a new file named Post.cs in the Models folder:

namespace MyFirstApi.Models;public class Post
{
    public int UserId { get; set; }
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Body { get; set; } = string.Empty;
}

File-scoped namespace declaration

From C# 10, you can use a new form of namespace declaration, as shown in the previous code snippet, which is called a file-scoped namespace declaration. All the members in this file are in the same namespace. It saves space and reduces indentation.

Nullable reference types

You may be wondering why we assign an empty string to the Title and Body properties. This is because the properties are of type string. If we do not initialize the property, the compiler will complain:

Non-nullable property 'Title' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

By default, the ASP.NET Core web API project template enabled the nullable reference types annotation in the project properties. If you check the project file, you will find <Nullable>enable</Nullable> in the <PropertyGroup> section.

Nullable reference types were introduced in C# 8.0. They can minimize the likelihood of errors that cause the runtime to throw a System.NullReferenceException error. For example, if we forget to initialize the Title property, we may get a System.NullReferenceException error when we try to access a property of it, such as Title.Length.

With this feature enabled, any variable of a reference type is considered to be non-nullable. If you want to allow a variable to be nullable, you must append the type name with the ? operator to declare the variable as a nullable reference type; for example, public string Title? { get; set; }, which explicitly marks the property as nullable.

To learn more about this feature, see https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references.

Next, create a new file named PostController.cs in the Controllers folder. You can manually add it, or install the dotnet-aspnet-codegenerator tool to create it. To install the tool, run the following commands from the project folder:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Designdotnet tool install -g dotnet-aspnet-codegenerator

The preceding commands install a NuGet package required for scaffolding. The dotnet-aspnet-codegenerator tool is a scaffolding engine that is used to generate code.

Then, run the following command to generate a controller:

dotnet-aspnet-codegenerator controller -name PostsController -api -outDir Controllers

The preceding command generates an empty controller. The -name option specifies the name of the controller. The -api option indicates that the controller is an API controller. The -outDir option specifies the output directory. Update the content of the controller as follows:

using Microsoft.AspNetCore.Mvc;using MyFirstApi.Models;
namespace MyFirstApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class PostsController : ControllerBase
{
    [HttpGet]
    public ActionResult<List<Post>> GetPosts()
    {
        return new List<Post>
        {
            new() { Id = 1, UserId = 1, Title = "Post1", Body = "The first post." },
            new() { Id = 2, UserId = 1, Title = "Post2", Body = "The second post." },
            new() { Id = 3, UserId = 1, Title = "Post3", Body = "The third post." }
        };
    }
}

Target-typed new expressions

When we create a new List instance of a specific type, we will normally use code like this:

var list = new List<Post>

{

new Post() { Id = 1, UserId = 1, Title = "Post1", Body = "The first post." },

};

When the list is declared as List<Post>, the type is known, so it is not necessary to use new Post() here when adding new elements. The type specification can be omitted for constructors, such as new(). This feature was introduced in C# 9.0.

The controller is named PostsController. The convention is the resource name with the Controller suffix. It is marked with the ApiController attribute, which indicates that the controller is a web API controller. It also has a [Route("api/[controller]")] attribute that indicates the URL of the controller. [controller] is like a placeholder, which will be replaced with the name of the controller in the routing. So, the route of this controller is /api/posts.

In this controller, we have a method named GetPosts(). This method returns a list of posts as the response. The method is marked with the [HttpGet] attribute, which indicates that this method is a GET operation. It does not have any route template, because it will match /api/posts. For other methods, we can use the [Route("[action]")] attribute to specify the route template.

The return type of the GetPosts() method is ActionResult<IEnumerable<Post>>. ASP.NET Core can automatically convert the object to JSON and return it to the client in the response message. Also, it can return other HTTP status codes, such as NotFound, BadRequest, InternalServerError, and so on. We will see more examples later.

If you run dotnet run or dotnet watch, then navigate to Swagger UI, such as https://localhost:7291/swagger/index.html, you will see the new API listed. The API is accessible at /api/posts.

Currently, the /api/posts endpoint returns a hardcoded list of posts. Let us update the controller to return a list of posts from a service.

Creating a service

Create a Services folder in the project. Then, create a new file named PostService.cs in the Services folder, as shown next:

using MyFirstApi.Models;namespace MyFirstApi.Services;
public class PostsService
{
    private static readonly List<Post> AllPosts = new();
    public Task CreatePost(Post item)
    {
        AllPosts.Add(item);
        return Task.CompletedTask;
    }
    public Task<Post?> UpdatePost(int id, Post item)
    {
        var post = AllPosts.FirstOrDefault(x => x.Id == id);
        if (post != null)
        {
            post.Title = item.Title;
            post.Body = item.Body;
            post.UserId = item.UserId;
        }
        return Task.FromResult(post);
    }
    public Task<Post?> GetPost(int id)
    {
        return Task.FromResult(AllPosts.FirstOrDefault(x => x.Id == id));
    }
    public Task<List<Post>> GetAllPosts()
    {
        return Task.FromResult(AllPosts);
    }
    public Task DeletePost(int id)
    {
        var post = AllPosts.FirstOrDefault(x => x.Id == id);
        if (post != null)
        {
            AllPosts.Remove(post);
        }
        return Task.CompletedTask;
    }
}

The PostsService class is a simple demo service that manages the list of posts. It has methods to create, update, and delete posts. To simplify the implementation, it uses a static field to store the list of posts. This is just for demonstration purposes; please do not use this in production.

Next, we will follow the API design to implement CRUD operations. You can review the REST-based API design section of the previous chapter.

Implementing a GET operation

The design for the viewPost() operation is as follows:

Operation name

URL

HTTP method

Input

Response

Description

viewPost()

/posts/{postId}

GET

PostId

Post, 200

View a post detail

Table 2.1 – The design for the viewPost() operation

Update the PostController class as follows:

using Microsoft.AspNetCore.Mvc;using MyFirstApi.Models;
using MyFirstApi.Services;
namespace MyFirstApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class PostsController : ControllerBase
{
    private readonly PostsService _postsService;
    public PostsController()
    {
        _postsService = new PostsService();
    }
    [HttpGet("{id}")]
    public async Task<ActionResult<Post>> GetPost(int id)
    {
        var post = await _postsService.GetPost(id);
        if (post == null)
        {
            return NotFound();
        }
        return Ok(post);
    }
    // Omitted for brevity
}

In the constructor method of the controller, we initialize the _postsService field. Note that we use the new() constructor to create an instance of the service. That means the controller is coupled with the PostsService class. We will see how to decouple the controller and the service in the next chapter.

Then, create a GetPost() method that returns a post with the specified ID. It has a [HttpGet("{id}")] attribute to indicate the URL of the operation. The URL will be mapped to /api/posts/{id}. id is a placeholder, which will be replaced with the ID of the post. Then, id will be passed to the GetPost() method as a parameter.

If the post is not found, the method will return a NotFound response. ASP.NET Core provides a set of built-in response messages, such as NotFound, BadRequest, InternalServerError, and so on.

If you call the API now, it will return NotFound because we have not created a post.

Implementing a CREATE operation

The design for the createPost() operation is as follows:

Operation name

URL

HTTP method

Input

Response

Description

createPost()

/posts

POST

Post

Post, 201

Create a new post

Table 2.2 – The design for the createPost() operation

Create a new method named CreatePost() in the controller. As the controller has been mapped to api/posts, we do not need to specify the route of this method. The content of the method is as follows:

[HttpPost]public async Task<ActionResult<Post>> CreatePost(Post post)
{
    await _postsService.CreatePost(post);
    return CreatedAtAction(nameof(GetPost), new { id = post.Id }, post);
}

When we call this endpoint, the post object will be serialized in the JSON format that is attached to the POST request body. In this method, we can get the post from the request and then call the CreatePost() method in the service to create a new post. Then, we will return the built-in CreatedAtAction, which returns a response message with the specified action name, route values, and post. For this case, it will call the GetPost() action to return the newly created post.

Now, we can test the API. For example, we can send a POST request in Thunder Client.

Change the method to POST. Use the following JSON data as the body:

{  "userId": 1,
  "id": 1,
  "title": "Hello ASP.NET Core",
  "body": "ASP.NET Core is a cross-platform, high-performance, open-source framework for building modern, cloud-enabled, Internet-connected apps."
}

Click the Send button. Note that the status of the response is 201 Created:

Figure 2.19 – Sending a POST request

Figure 2.19 – Sending a POST request

Then, send a GET request to the api/posts/1 endpoint. We can get a response like this:

Figure 2.20 – Sending a GET request

Figure 2.20 – Sending a GET request

Please note that the post we created is stored in the memory of the service. Because we have not provided a database to store the data, if we restart the application, the post will be lost.

Next, let us see how to implement an update operation.

Implementing an UPDATE operation

The design for the updatePost() operation is as follows:

Operation name

URL

HTTP method

Input

Response

Description

updatePost()

/posts/{postId}

PUT

Post

Post, 200

Update a new post

Table 2.3 – The design for the updatePost() operation

Create a new UpdatePost() method in the controller, as shown next:

[HttpPut("{id}")]public async Task<ActionResult> UpdatePost(int id, Post post)
{
    if (id != post.Id)
    {
        return BadRequest();
    }
    var updatedPost = await _postsService.UpdatePost(id, post);
    if (updatedPost == null)
    {
        return NotFound();
    }
    return Ok(post);
}

This method has a [HttpPut("{id}")] attribute to indicate that it is a PUT operation. Similarly, id is a placeholder, which will be replaced with the ID of the post. In the PUT request, we should attach the serialized content of the post to the request body.

This time, let us test the API with HttpRepl. Run the following command to connect to the server:

httprepl https://localhost:7291/api/postsconnect https://localhost:7291/api/posts/1
put -h Content-Type=application/json -c "{"userId": 1,"id": 1,"title": "Hello ASP.NET Core 8","body": "ASP.NET Core is a cross-platform, high-performance, open-source framework for building modern, cloud-enabled, Internet-connected apps."}"

You will see this output:

HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8
Date: Thu, 18 Aug 2022 11:25:26 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
  "userId": 1,
  "id": 1,
  "title": "Hello ASP.NET Core 8",
  "body": "ASP.NET Core is a cross-platform, high-performance, open-source framework for building modern, cloud-enabled, Internet-connected apps."
}

Then, we can update the GetPosts() method as follows:

[HttpGet]public async Task<ActionResult<List<Post>>> GetPosts()
{
    var posts = await _postService.GetAllPosts();
    return Ok(posts);
}

We have implemented GET, POST, and PUT operations. Next, you can try to implement the DeletePost() method using the DELETE operation by yourself.

Dependency injection

In the preceding example of the controller, there is a _postsService field that is initialized in the constructor method of the controller by using the new() constructor:

private readonly PostsService _postsService;public PostsController()
{
    _postsService = new PostsService();
}

That says the PostsController class depends on the PostsService class, and the PostsService class is a dependency of the PostsController class. If we want to replace PostsService with a different implementation to save the data, we have to update the code of PostsController. If the PostsService class has its own dependencies, they must also be initialized by the PostsController class. When the project grows larger, the dependencies will become more complex. Also, this kind of implementation is not easy to test and maintain.

Dependency injection (DI) is one of the most well-known design patterns in the software development world. It helps decouple classes that depend on each other. You may find the following terms being used interchangeably: Dependency Inversion Principle (DIP), Inversion of Control (IoC), and DI. These terms are commonly confused even though they are related. You can find multiple articles and blog posts that explain them. Some say they are the same thing, but some say not. What are they?

Understanding DI

The Dependency Inversion Principle is one of the SOLID principles in object-oriented (OO) design. It was defined by Robert C. Martin in his book Agile Software Development: Principles, Patterns, and Practices, Pearson, in 2002. The principle states, “high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.”

In the preceding controller, we said PostsController depends on PostsService. The controller is the high-level module, and the service is the low-level module. When the service is changed, the controller must be changed as well. Keep in mind that the term inversion does not mean that the low-level module will depend on the high level. Instead, both of them should depend on abstractions that expose the behavior needed by high-level modules. If we invert this dependency relationship by creating an interface for the service, both the controller and the service will depend on the interface. The implementation of the service can change as long as it respects the interface.

IoC is a programming principle that inverts the flow of control in an application. In traditional programming, custom code is responsible for instantiating objects and controlling the execution of the main function. IoC inverts the flow of control as compared to traditional control flow. With IoC, the framework does the instantiation, calling custom or task-specific code.

It can be used to differentiate a framework from a class library. Normally, the framework calls the application code, and the application code calls the library. This kind of IoC is sometimes referred to as the Hollywood principle: “Don’t call us, we’ll call you.”

IoC is related to DIP, but it is not the same. DIP concerns decoupling dependencies between high-level modules and low-level modules through shared abstractions (interfaces). IoC is used to increase the modularity of the program and make it extensible. There are several technologies to implement IoC, such as Service Locator, DI, the template method design pattern, the strategy design pattern, and so on.

DI is a form of IoC. This term was coined by Martin Fowler in 2004. It separates the concerns of constructing objects and using them. When an object or a function (the client) needs a dependency, it does not know how to construct it. Instead, the client only needs to declare the interfaces of the dependency, and the dependency is injected into the client by external code (an injector). It makes it easier to change the implementation of the dependency. It is often similar to the strategy design pattern. The difference is that the strategy pattern can use different strategies to construct the dependency, while DI typically only uses a single instance of the dependency.

There are three main types of DI:

  • Constructor injection: The dependencies are provided as parameters of the client’s constructor
  • Setter injection: The client exposes a setter method to accept the dependency
  • Interface injection: The dependency’s interface provides an injector method that will inject the dependency into any client passed to it

As you can see, these three terms are related, but there are some differences. Simply put, DI is a technique for achieving IoC between classes and their dependencies. ASP.NET Core supports DI as a first-class citizen.

DI in ASP.NET Core

ASP.NET Core uses constructor injection to request dependencies. To use it, we need to do the following:

  1. Define interfaces and their implementations.
  2. Register the interfaces and the implementations to the service container.
  3. Add services as the constructor parameters to inject the dependencies.

You can download the example project named DependencyInjectionDemo from the folder samples/chapter2/ DependencyInjectionDemo/DependencyInjectionDemo in the chapter's GitHub repository.

Follow the steps below to use DI in ASP.NET Core:

  1. First, we will create an interface and its implementation. Copy the Post.cs file and the PostService.cs file from the previous MyFirstApi project to the DependencyInjectionDemo project. Create a new interface named IPostService in the Service folder, as shown next:
    public interface IPostService{    Task CreatePost(Post item);    Task<Post?> UpdatePost(int id, Post item);    Task<Post?> GetPost(int id);    Task<List<Post>> GetAllPosts();    Task DeletePost(int id);}

    Then, update the PostService class to implement the IPostService interface:

    public class PostsService : IPostService

    You may also need to update the namespace of the Post class and the PostService class.

  2. Next, we can register the IPostService interface and the PostService implementation to the service container. Open the Program.cs file, and you will find that an instance of WebApplicationBuilder named builder is created by calling the WebApplication.CreateBuilder() method. The CreateBuilder() method is the entry point of the application. We can configure the application by using the builder instance, and then call the builder.Build() method to build the WebApplication. Add the following code:
    builder.Services.AddScoped<IPostService, PostsService>();

    The preceding code utilizes the AddScoped() method, which indicates that the service is created once per client request and disposed of upon completion of the request.

  3. Copy the PostsController.cs file from the previous MyFirstApi project to the DependencyInjectionDemo project. Update the namespace and the using statements. Then, update the constructor method of the controller as follows:
    private readonly IPostService _postsService;public PostsController(IPostService postService){    _postsService = postService;}

    The preceding code uses the IPostService interface as the constructor parameter. The service container will inject the correct implementation into the controller.

DI has four roles: services, clients, interfaces, and injectors. In this example, IPostService is the interface, PostService is the service, PostsController is the client, and builder.Services is the injector, which is a collection of services for the application to compose. It is sometimes referred to as a DI container.

The PostsController class requests the instance of IPostService from its constructor. The controller, which is the client, does not know where the service is, nor how it is constructed. The controller only knows the interface. The service has been registered in the service container, which can inject the correct implementation into the controller. We do not need to use the new keyword to create an instance of the service. That says the client and the service are decoupled.

This DI feature is provided in a NuGet package called Microsoft.Extensions.DependencyInjection. When an ASP.NET Core project is created, this package is added automatically. If you create a console project, you may need to install it manually by using the following command:

dotnet add package Microsoft.Extensions.DependencyInjection

If we want to replace the IPostService with another implementation, we can do so by registering the new implementation to the service container. The code of the controller does not need to be changed. That is one of the benefits of DI.

Next, let us discuss the lifetime of services.

DI lifetimes

In the previous example, the service is registered using the AddScoped() method. In ASP.NET Core, there are three lifetimes when the service is registered:

  • Transient: A transient service is created each time it is requested and disposed of at the end of the request.
  • Scoped: In web applications, a scope means a request (connection). A scoped service is created once per client request and disposed of at the end of the request.
  • Singleton: A singleton service is created the first time it is requested or when providing the implementation instance to the service container. All subsequent requests will use the same instance.

To demonstrate the difference between these lifetimes, we will use a simple demo service:

Create a new interface named IDemoService and its implementation named DemoService in the Services folder, as shown next:

IDemoService.cs:

namespace DependencyInjectionDemo.Services;public interface IDemoService
{
    SayHello();
}

DemoService.cs:

namespace DependencyInjectionDemo.Services;public class DemoService : IDemoService
{
    private readonly Guid _serviceId;
    private readonly DateTime _createdAt;
    public DemoService()
    {
        _serviceId = Guid.NewGuid();
        _createdAt = DateTime.Now;
    }
    public string SayHello()
    {
        return $"Hello! My Id is {_serviceId}. I was created at {_createdAt:yyyy-MM-dd HH:mm:ss}.
";
    }
}

The implementation will generate an ID and a time when it was created, and output it when the SayHello() method is called.

  1. Then, we can register the interface and the implementation to the service container. Open the Program.cs file and add the code as follows:
    builder.Services.AddScoped<IDemoService, DemoService>();
  2. Create a controller named DemoController.cs. Now, we can add the service as constructor parameters to inject the dependency:
    [ApiController][Route("[controller]")]public class DemoController : ControllerBase{    private readonly IDemoService _demoService;    public DemoController(IDemoService demoService)    {        _demoService = demoService;    }    [HttpGet]    public ActionResult Get()    {        return Content(_demoService.SayHello());    }}

For this example, if you test the /demo endpoint, you will see the GUID value and the creation time in the output change every time:

http://localhost:5147/> get demoHTTP/1.1 200 OK
Content-Length: 91
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:06:46 GMT
Server: Kestrel
Hello! My Id is 6ca84d82-90cb-4dd6-9a34-5ea7573508ac. I was created at 2023-10-21 11:06:46.
http://localhost:5147/> get demo
HTTP/1.1 200 OK
Content-Length: 91
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:07:02 GMT
Server: Kestrel
Hello! My Id is 9bc5cf49-661d-45bb-b9ed-e0b3fe937827. I was created at 2023-10-21 11:07:02.

We can change the lifetime to AddSingleton(), as follows:

builder.Services.AddSingleton<IDemoService, DemoService>();

The GUID values and the creation time values will be the same for all requests:

http://localhost:5147/> get demoHTTP/1.1 200 OK
Content-Length: 91
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:08:57 GMT
Server: Kestrel
Hello! My Id is a1497ead-bff6-4020-b337-28f1d3af7b05. I was created at 2023-10-21 11:08:02.
http://localhost:5147/> get demo
HTTP/1.1 200 OK
Content-Length: 91
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:09:12 GMT
Server: Kestrel
Hello! My Id is a1497ead-bff6-4020-b337-28f1d3af7b05. I was created at 2023-10-21 11:08:02.

As the DemoController class only requests the IDemoService interface once for each request, we cannot differentiate the behavior between scoped and transient services. Let us look at a more complex example.

  1. You can find the example code in the DependencyInjectionDemo project. There are three interfaces along with their implementations:
    public interface IService{    string Name { get; }    string SayHello();}public interface ITransientService : IService{}public class TransientService : ITransientService{    private readonly Guid _serviceId;    private readonly DateTime _createdAt;    public TransientService()    {        _serviceId = Guid.NewGuid();        _createdAt = DateTime.Now;    }    public string Name => nameof(TransientService);    public string SayHello()    {        return $"Hello! I am {Name}. My Id is {_serviceId}. I was created at {_createdAt:yyyy-MM-dd HH:mm:ss}.";    }}public interface ISingletonService : IService{}public class SingletonService : ISingletonService{    private readonly Guid _serviceId;    private readonly DateTime _createdAt;    public SingletonService()    {        _serviceId = Guid.NewGuid();        _createdAt = DateTime.Now;    }    public string Name => nameof(SingletonService);    public string SayHello()    {        return $"Hello! I am {Name}. My Id is {_serviceId}. I was created at {_createdAt:yyyy-MM-dd HH:mm:ss}.";    }}public interface IScopedService : IService{}public class ScopedService : IScopedService{    private readonly Guid _serviceId;    private readonly DateTime _createdAt;    private readonly ITransientService _transientService;    private readonly ISingletonService _singletonService;    public ScopedService(ITransientService transientService, ISingletonService singletonService)    {        _transientService = transientService;        _singletonService = singletonService;        _serviceId = Guid.NewGuid();        _createdAt = DateTime.Now;    }    public string Name => nameof(ScopedService);    public string SayHello()    {        var scopedServiceMessage = $"Hello! I am {Name}. My Id is {_serviceId}. I was created at {_createdAt:yyyy-MM-dd HH:mm:ss}.";        var transientServiceMessage = $"{_transientService.SayHello()} I am from {Name}.";        var singletonServiceMessage = $"{_singletonService.SayHello()} I am from {Name}.";        return            $"{scopedServiceMessage}{Environment.NewLine}{transientServiceMessage}{Environment.NewLine}{singletonServiceMessage}";    }}
  2. In the Program.cs file, we can register them to the service container as follows:
    builder.Services.AddScoped<IScopedService, ScopedService>();builder.Services.AddTransient<ITransientService, TransientService>();builder.Services.AddSingleton<ISingletonService, SingletonService>();
  3. Then, create a controller named LifetimeController.cs. The code is shown next:
    [ApiController][Route("[controller]")]public class LifetimeController : ControllerBase{    private readonly IScopedService _scopedService;    private readonly ITransientService _transientService;    private readonly ISingletonService _singletonService;    public LifetimeController(IScopedService scopedService, ITransientService transientService,        ISingletonService singletonService)    {        _scopedService = scopedService;        _transientService = transientService;        _singletonService = singletonService;    }    [HttpGet]    public ActionResult Get()    {        var scopedServiceMessage = _scopedService.SayHello();        var transientServiceMessage = _transientService.SayHello();        var singletonServiceMessage = _singletonService.SayHello();        return Content(            $"{scopedServiceMessage}{Environment.NewLine}{transientServiceMessage}{Environment.NewLine}{singletonServiceMessage}");    }}

In this example, ScopedService has two dependencies: ITransientService and ISingletonService. So, when ScopedService is created, it will ask for the instances of these dependencies from the service container. On the other hand, the controller also has dependencies: IScopedService, ITransientService, and ISingletonService. When the controller is created, it will ask for these three dependencies. That means ITransientService and ISingletonService will be needed twice for each request. But let us check the output of the following requests:

http://localhost:5147/> get lifetimeHTTP/1.1 200 OK
Content-Length: 625
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:20:44 GMT
Server: Kestrel
Hello! I am ScopedService. My Id is df87d966-0e86-4f08-874f-ba6ce71de560. I was created at 2023-10-21 11:20:44.
Hello! I am TransientService. My Id is 77e29268-ad48-423c-94e5-de1d09bd3ba5. I was created at 2023-10-21 11:20:44. I am from ScopedService.
Hello! I am SingletonService. My Id is 95a44c5b-8678-48c6-a2f0-cc6b90423773. I was created at 2023-10-21 11:20:44. I am from ScopedService.
Hello! I am TransientService. My Id is e77564d1-e146-4d29-b74b-a07f8f6640c1. I was created at 2023-10-21 11:20:44.
Hello! I am SingletonService. My Id is 95a44c5b-8678-48c6-a2f0-cc6b90423773. I was created at 2023-10-21 11:20:44.
http://localhost:5147/> get lifetime
HTTP/1.1 200 OK
Content-Length: 625
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:20:57 GMT
Server: Kestrel
Hello! I am ScopedService. My Id is e5f802ed-5e4c-4abd-9213-8f13f97c1008. I was created at 2023-10-21 11:20:57.
Hello! I am TransientService. My Id is daccb91b-438f-4561-9c86-13b02ad8e358. I was created at 2023-10-21 11:20:57. I am from ScopedService.
Hello! I am SingletonService. My Id is 95a44c5b-8678-48c6-a2f0-cc6b90423773. I was created at 2023-10-21 11:20:44. I am from ScopedService.
Hello! I am TransientService. My Id is 94e9e6c1-729a-4033-8a27-550ea10ba5d0. I was created at 2023-10-21 11:20:57.
Hello! I am SingletonService. My Id is 95a44c5b-8678-48c6-a2f0-cc6b90423773. I was created at 2023-10-21 11:20:44.

We can see that in each request, ScopedService was created once, while ITransientService was created twice. In both requests, SingletonService was created only once.

Group registration

As the project grows, we may have more and more services. If we register all services in Program.cs, this file will be very large. For this case, we can use group registration to register multiple services at once. For example, we can create a service group named LifetimeServicesCollectionExtensions.cs:

public static class LifetimeServicesCollectionExtensions{
    public static IServiceCollection AddLifetimeServices(this IServiceCollection services)
    {
        services.AddScoped<IScopedService, ScopedService>();
        services.AddTransient<ITransientService, TransientService>();
        services.AddSingleton<ISingletonService, SingletonService>();
        return services;
    }
}

This is an extension method for the IServiceCollection interface. It is used to register all services at once in the Program.cs file:

// Group registrationbuilder.Services.AddLifetimeServices();

In this way, the Program.cs file will be smaller and easier to read.

Action injection

Sometimes, one controller may need many services but may not need all of them for all actions. If we inject all the dependencies from the constructor, the constructor method will be large. For this case, we can use action injection to inject dependencies only when needed. See the following example:

[HttpGet]public ActionResult Get([FromServices] ITransientService transientService)
{
  ...
}

The [FromServices] attribute enables the service container to inject dependencies when needed without using constructor injection. However, if you find that a service needs a lot of dependencies, it may indicate that the class has too many responsibilities. Based on the Single Responsibility Principle (SRP), consider refactoring the class to split the responsibilities into smaller classes.

Keep in mind that this kind of action injection only works for actions in the controller. It does not support normal classes. Additionally, since ASP.NET Core 7.0, the [FromServices] attribute can be omitted as the framework will automatically attempt to resolve any complex type parameters registered in the DI container.

Keyed services

ASP.NET Core 8.0 introduces a new feature known as keyed services, or named services. This feature allows developers to register services with a key, allowing them to access the service with that key. This makes it easier to manage multiple services that implement the same interface within an application, as the key can be used to identify and access the service.

For example, we have a service interface named IDataService:

public interface IDataService{
    string GetData();
}

This IDataService interface has two implementations: SqlDatabaseService and CosmosDatabaseService:

public class SqlDatabaseService : IDataService{
    public string GetData()
    {
        return "Data from SQL Database";
    }
}
public class CosmosDatabaseService : IDataService
{
    public string GetData()
    {
        return "Data from Cosmos Database";
    }
}

We can register them to the service container using different keys:

builder.Services.AddKeyedScoped<IDataService, SqlDatabaseService>("sqlDatabaseService");builder.Services.AddKeyedScoped<IDataService, CosmosDatabaseService>("cosmosDatabaseService");

Then, we can inject the service by using the FromKeyedServices attribute:

[ApiController][Route("[controller]")]
public class KeyedServicesController : ControllerBase
{
    [HttpGet("sql")]
    public ActionResult GetSqlData([FromKeyedServices("sqlDatabaseService")] IDataService dataService) =>
        Content(dataService.GetData());
    [HttpGet("cosmos")]
    public ActionResult GetCosmosData([FromKeyedServices("cosmosDatabaseService")] IDataService dataService) =>
        Content(dataService.GetData());
}

The FromKeyedServices attribute is used to inject the service by using the specified key. Test the API with HttpRepl, and you will see the output as follows:

http://localhost:5147/> get keyedServices/sqlHTTP/1.1 200 OK
Content-Length: 22
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:48:49 GMT
Server: Kestrel
Data from SQL Database
http://localhost:5147/> get keyedServices/cosmos
HTTP/1.1 200 OK
Content-Length: 25
Content-Type: text/plain; charset=utf-8
Date: Fri, 20 Oct 2023 22:48:54 GMT
Server: Kestrel
Data from Cosmos Database

The keyed services can be used to register singleton or transient services as well. Just use the AddKeyedSingleton() or AddKeyedTransient() method respectively; for example:

builder.Services.AddKeyedSingleton<IDataService, SqlDatabaseService>("sqlDatabaseService");builder.Services.AddKeyedTransient<IDataService, CosmosDatabaseService>("cosmosDatabaseService");

It is important to note that if an empty string is passed as the key, a default implementation for the service must be registered with a key of an empty string, otherwise the service container will throw an exception.

Microsoft releases new versions of .NET SDKs frequently. If you encounter a different version number, that is acceptable.

The preceding command will list all the available SDKs on your machine. For example, it may show the following output if have multiple .NET SDKs installed.

Important note

Every Microsoft product has a lifecycle. .NET and .NET Core provides Long-term support (LTS) releases that get 3 years of patches and free support. When this book was written, .NET 7 is still supported, until May 2024. Based on Microsoft’s policy, even numbered releases are LTS releases. So .NET 8 is the latest LTS release. The code samples in this book are written with .NET 8.0.

When you use VS Code to open the project, the C# Dev Kit extension can create a solution file for you. This feature makes VS Code more friendly to C# developers. You can see the following structure in the Explorer view:

It uses the Swashbuckle.AspNetCore NuGet package, which provides the Swagger UI to document and test the APIs.

Follow the steps below to use DI in ASP.NET Core:

We can see that in each request, ScopedService was created once, while ITransientService was created twice. In both requests, SingletonService was created only once.

Using primary constructors to inject dependencies

Beginning with .NET 8 and C# 12, we can use the primary constructor to inject dependencies. A primary constructor allows us to declare the constructor parameters directly in the class declaration, instead of using a separate constructor method. For example, we can update the PostsController class as follows:

```csharppublic class PostsController(IPostService postService) : ControllerBase
{
    // No need to define a private field to store the service 
    // No need to define a constructor method
}
```

You can find a sample named PrimaryConstructorController.cs in the Controller folder of the DependencyInjectionDemo project.

When using the primary constructor in a class, note that the parameters passed to the class declaration cannot be used as properties or members. For example, if a class declares a parameter named postService in the class declaration, it cannot be accessed as a class member using this.postService or from external code. To learn more about the primary constructor, please refer to the documentation at https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors.

Primary constructors can save us from writing fields and constructor methods. So, we’ll use them in the following examples.

Do not use new to create service B, otherwise, service A will be tightly coupled with service B.

Resolving a service when the app starts

If we need a service in the Program.cs file, we cannot use constructor injection. For this situation, we can resolve a scoped service for a limited duration at app startup, as follows:

var app = builder.Build();using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;
    var demoService = services.GetRequiredService<IDemoService>();
    var message = demoService.SayHello();
    Console.WriteLine(message);
}

The preceding code creates a scope and resolves the IDemoService service from the service container. Then, it can use the service to do something. After the scope is disposed of, the service will be disposed of as well.

DI tips

ASP.NET Core uses DI heavily. The following are some tips to help you use DI:

  • When designing your services, make the services as stateless as possible. Do not use static classes and members unless you have to do so. If you need to use a global state, consider using a singleton service instead.
  • Carefully design dependency relationships between services. Do not create a cyclic dependency.
  • Do not use new to create a service instance in another service. For example, if service A depends on service B, the instance of service B should be injected into service A with DI. Do not use new to create service B, otherwise, service A will be tightly coupled with service B.
  • Use a DI container to manage the lifetime of services. If a service implements the IDisposable interface, the DI container will dispose of the service when the scope is disposed of. Do not manually dispose of it.
  • When registering a service, do not use new to create an instance of the service. For example, services.AddSingleton(new ExampleService()); registers a service instance that is not managed by the service container. So, the DI framework will not be able to dispose of the service automatically.
  • Avoid using the service locator pattern. If DI can be used, do not use the GetService() method to obtain a service instance.

You can learn more about the DI guidelines at https://docs.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection-guidelines.

Why there is no configuration method for the logger in the template project?

ASP.NET Core provides a built-in DI implementation for the logger. When the project was created, logging was registered by the ASP.NET Core framework. Therefore, there is no configuration method for the logger in the template project. Actually, there are more than 250 services that are automatically registered by the ASP.NET Core framework.

Can I use third-party DI containers?

It is highly recommended that you use the built-in DI implementation in ASP.NET Core. But if you need any specific features that it does not support, such as property injection, Func<T> support for lazy initialization, and so on, you can use third-party DI containers, such as Autofac (https://autofac.org/).

Introduction to minimal APIs

In the previous section, Creating a simple web API project, we created a simple web API project using the dotnet new webapi -n MyFirstApi -controllers command. The -controllers option (or --use-controllers) indicates that the project will use controller-based routing. Alternatively, the -minimal or --use-minimal-apis option can be used to create a project that uses minimal APIs. In this section, we will introduce minimal APIs.

Minimal APIs is a new feature introduced in ASP.NET Core 6.0. It is a new way to create APIs without using controllers. Minimal APIs are designed to be simple and lightweight with minimal dependencies. They are a good choice for small projects or prototypes, and also for projects that do not need the full features of controllers.

To create a minimal API project, we can use the following command:

dotnet new webapi -n MinimalApiDemo -minimal

There is no Controllers folder in the project. Instead, you can find the following code in the Program.cs file:

app.MapGet("/weatherforecast", () =>{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();

The preceding code uses the MapGet() method to map the GET request to the /weatherforecast endpoint. The MapGet() method is an extension method of the IEndpointRouteBuilder interface. This interface is used to configure the endpoints in the application. Its extension method, MapGet(), returns an IEndpointConventionBuilder interface that allows us to use fluent APIs to configure the endpoint by using other extension methods, such as WithName() and WithOpenApi(). The WithName() method is used to set the name of the endpoint. The WithOpenApi() method is used to generate an OpenAPI document for the endpoint.

Creating a simple endpoint

Let us create a new /posts endpoint that supports CRUD operations. First, add the following code to the end of the Program.cs file to define a Post class:

public class Post{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Content { get; set; } = string.Empty;
}

Add the following code to the Program.cs file:

var list = new List<Post>(){
    new() { Id = 1, Title = "First Post", Content = "Hello World" },
    new() { Id = 2, Title = "Second Post", Content = "Hello Again" },
    new() { Id = 3, Title = "Third Post", Content = "Goodbye World" },
};
app.MapGet("/posts",
    () => list).WithName("GetPosts").WithOpenApi().WithTags("Posts");
app.MapPost("/posts",
    (Post post) =>
    {
        list.Add(post);
        return Results.Created($"/posts/{post.Id}", post);
    }).WithName("CreatePost").WithOpenApi().WithTags("Posts");
app.MapGet("/posts/{id}", (int id) =>
{
    var post = list.FirstOrDefault(p => p.Id == id);
    return post == null ? Results.NotFound() : Results.Ok(post);
}).WithName("GetPost").WithOpenApi().WithTags("Posts");
app.MapPut("/posts/{id}", (int id, Post post) =>
{
    var index = list.FindIndex(p => p.Id == id);
    if (index == -1)
    {
        return Results.NotFound();
    }
    list[index] = post;
    return Results.Ok(post);
}).WithName("UpdatePost").WithOpenApi().WithTags("Posts");
app.MapDelete("/posts/{id}", (int id) =>
{
    var post = list.FirstOrDefault(p => p.Id == id);
    if (post == null)
    {
        return Results.NotFound();
    }
    list.Remove(post);
    return Results.Ok();
}).WithName("DeletePost").WithOpenApi().WithTags("Posts");

The preceding code defines five endpoints:

  • GET /posts: Get all posts
  • POST /posts: Create a new post
  • GET /posts/{id}: Get a post by ID
  • PUT /posts/{id}: Update a post by ID
  • DELETE /posts/{id}: Delete a post by ID

We use the WithTags extension method to group these endpoints into a tag named Posts. In this example, a list is used to store the posts. In a real-world application, we should use a database to store the data.

Using DI in minimal APIs

Minimal APIs support DI as well. You can find the IPostService interface and its PostService implementation in the Services folder. Here is an example of using DI in minimal APIs:

app.MapGet("/posts", async (IPostService postService) =>{
    var posts = await postService.GetPostsAsync();
    return posts;
}).WithName("GetPosts").WithOpenApi().WithTags("Posts");
app.MapGet("/posts/{id}", async (IPostService postService, int id) =>
{
    var post = await postService.GetPostAsync(id);
    return post == null ? Results.NotFound() : Results.Ok(post);
}).WithName("GetPost").WithOpenApi().WithTags("Posts");
app.MapPost("/posts", async (IPostService postService, Post post) =>
{
    var createdPost = await postService.CreatePostAsync(post);
    return Results.Created($"/posts/{createdPost.Id}", createdPost);
}).WithName("CreatePost").WithOpenApi().WithTags("Posts");
app.MapPut("/posts/{id}", async (IPostService postService, int id, Post post) =>
{
    try
    {
        var updatedPost = await postService.UpdatePostAsync(id, post);
        return Results.Ok(updatedPost);
    }
    catch (KeyNotFoundException)
    {
        return Results.NotFound();
    }
}).WithName("UpdatePost").WithOpenApi().WithTags("Posts");
app.MapDelete("/posts/{id}", async (IPostService postService, int id) =>
{
    try
    {
        await postService.DeletePostAsync(id);
        return Results.NoContent();
    }
    catch (KeyNotFoundException)
    {
        return Results.NotFound();
    }
}).WithName("DeletePost").WithOpenApi().WithTags("Posts");

In the preceding code, the IPostService interface is used as a parameter of the action method. The DI container will inject the correct implementation into the action method. You can run the project and test the endpoints. It should have the same behavior as the controller-based project.

What is the difference between minimal APIs and controller-based APIs?

Minimal APIs are simpler than controller-based APIs, allowing us to map endpoints to methods directly. This makes minimal APIs a good choice for quickly creating simple APIs or demo projects. However, minimal APIs do not support the full range of features that controllers provide, such as model binding, model validation, and so on. These features may be added in the future. Therefore, we will mainly use controller-based APIs and not discuss minimal APIs in detail in this book. If you want to learn more about minimal APIs, please refer to the official documentation at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis.

Summary

In this chapter, we created a simple web API project and introduced how to run the project locally and call the APIs with different clients. We implemented basic CRUD operations using an in-memory list. Also, we explained how to use DI in ASP.NET Core. We explored the lifetime of services and learned some tips. In addition, we introduced minimal APIs. In the next chapter, we will delve further into the built-in components of ASP.NET Core.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Gain proficiency in building modern ASP.NET Core web API applications
  • Develop the skills to effectively test, debug, and secure your web API applications
  • Streamline development workflows and boost productivity with cloud computing platforms and industry-standard CI/CD tools
  • Purchase of the print or Kindle book includes a free PDF eBook

Description

Web API applications have become increasingly significant in recent years, fueled by the ever-accelerating pace of technological advancements. However, with this rapid evolution comes a pressing challenge: the need to create web API applications that are not only functional but also adaptable, maintainable, and scalable to meet the demands of users and businesses alike. This book will help you address this challenge head-on, equipping you with the knowledge and skills required to develop web API applications from scratch. By providing a deeper understanding of the various protocols implemented by ASP.NET Core, including RESTful, SignalR (WebSocket), gRPC, and GraphQL, supplemented by practical examples and optimization techniques, such as using middleware, testing, caching, and logging, this book offers invaluable insights for both newcomers as well as seasoned developers to meet modern web development requirements. Additionally, you’ll discover how to use cloud platforms such as Azure and Azure DevOps to enhance the development and operational aspects of your application. By the end of the book, you’ll be fully prepared to undertake enterprise-grade web API projects with confidence, harnessing the latest advancements in ASP.NET Core 8 to drive innovation.

What you will learn

Build a strong foundation in web API fundamentals Explore the ASP.NET Core 8 framework and other industry-standard libraries and tools for high-performance, scalable web APIs Apply essential software design patterns such as MVC, dependency injection, and the repository pattern Use Entity Framework Core for database operations and complex query creation Implement robust security measures to protect against malicious attacks and data breaches Deploy your application to the cloud using Azure and leverage Azure DevOps to implement CI/CD

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Apr 5, 2024
Length 804 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781804610954
Vendor :
Microsoft
Category :
Concepts :

Table of Contents

20 Chapters
Preface Chevron down icon Chevron up icon
Chapter 1: Fundamentals of Web APIs Chevron down icon Chevron up icon
Chapter 2: Getting Started with ASP.NET Core Web APIs Chevron down icon Chevron up icon
Chapter 3: ASP.NET Core Fundamentals (Part 1) Chevron down icon Chevron up icon
Chapter 4: ASP.NET Core Fundamentals (Part 2) Chevron down icon Chevron up icon
Chapter 5: Data Access in ASP.NET Core (Part 1: Entity Framework Core Fundamentals) Chevron down icon Chevron up icon
Chapter 6: Data Access in ASP.NET Core (Part 2 – Entity Relationships) Chevron down icon Chevron up icon
Chapter 7: Data Access in ASP.NET Core (Part 3: Tips) Chevron down icon Chevron up icon
Chapter 8: Security and Identity in ASP.NET Core Chevron down icon Chevron up icon
Chapter 9: Testing in ASP.NET Core (Part 1 – Unit Testing) Chevron down icon Chevron up icon
Chapter 10: Testing in ASP.NET Core (Part 2 – Integration Testing) Chevron down icon Chevron up icon
Chapter 11: Getting Started with gRPC Chevron down icon Chevron up icon
Chapter 12: Getting Started with GraphQL Chevron down icon Chevron up icon
Chapter 13: Getting Started with SignalR Chevron down icon Chevron up icon
Chapter 14: CI/CD for ASP.NET Core Using Azure Pipelines and GitHub Actions Chevron down icon Chevron up icon
Chapter 15: ASP.NET Core Web API Common Practices Chevron down icon Chevron up icon
Chapter 16: Error Handling, Monitoring, and Observability Chevron down icon Chevron up icon
Chapter 17: Cloud-Native Patterns Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Filter icon Filter
Top Reviews
Rating distribution
Empty star icon Empty star icon Empty star icon Empty star icon Empty star icon 0
(0 Ratings)
5 star 0%
4 star 0%
3 star 0%
2 star 0%
1 star 0%

Filter reviews by


No reviews found
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.