© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
P. HimschootMicrosoft Blazorhttps://doi.org/10.1007/978-1-4842-7845-1_6

6. Data Storage and Microservices

Peter Himschoot1  
(1)
Melle, Belgium
 

In general, client-side browser applications need to store some of their data. In some cases, such as games, the application can store its data in the browser itself, using browser local storage. But in most cases, storage will happen on the server, which has access to database engines such as SQL Server. In this chapter, you will learn the basics of storing data using Entity Framework Core and exposing that data using REST and microservices built on top of ASP.NET Core.

What Is REST?

Storing data on the Web is ubiquitous. But how can applications communicate with one another? Representational State Transfer (REST) is a protocol built on top of the HTTP protocol for invoking functionality on servers, such as retrieving and storing data from/in a database.

Understanding HTTP

Before talking about REST, you should have a good understanding of the Hypertext Transfer Protocol, better known as HTTP. HTTP was created by Tim Berners-Lee at CERN in 1989. CERN is a center for elementary physics research, and what do researchers do when they have completed their research? They publish papers with their research findings. Before the Internet, publishing a paper was done literally on paper (hence the name), and it took a lot of time between writing the paper and getting it published in a research magazine. Instead, Tim Berners-Lee devised a way to put papers on a server and allow users to read these papers using a program, now known as a browser.

Also, scientific papers contain a lot of references, and when you want to read a paper like this, it helps to be able to access the referenced papers. The Internet facilitates reading papers through the use of HyperText Markup Language (HTML). Hypertext is an electronic document format that can contain links to other documents. You simply click the link to read the other paper, and you can go back to the first paper simply by clicking the back button in your browser.

Universal Resource Identifiers and Methods

Browsers are applications that know how to talk HTTP, and the first thing you do after opening a browser is you type in a Universal Resource Identifier (URI). A URI allows a browser to talk to a server, but more is needed. As the name suggests, a URI identifies a resource universally, but you also need to use an HTTP method to instruct the server to do something with the URI. The most common method is GET. As Figure 6-1 shows, when you type in a URI in the browser, it will do a GET on the server.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig1_HTML.jpg
Figure 6-1

The Browser Uses the GET Method to Retrieve a Document

Each time you click a hyperlink in the HTML document, the browser repeats this process with another URI.

But there are other methods. If you want to publish a new paper, you can use the POST method to send the paper to the server, supplying it with a URI. In this case, the server will store the paper at the requested URI. If you want to make a change to your paper, for example, to correct a spelling mistake, you can use the PUT method. Now the server will overwrite the contents identified by the URI. And finally, you can delete the paper using the DELETE method and its URI.

Note

Using the GET, POST, PUT, and DELETE methods like this is a convention. Nothing states that you have to do things like this, and there are REST services out there that use different methods and status codes.

HTTP Status Codes

What happens when you ask a server about something it doesn’t have? What should the server return? Servers not only return HTML, but they also return a status code about the result. When the server can process the request successfully, it will in general return status code 200 (other successful status codes exist – you can find the full list at https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). When the server can’t find the resource, it will return a status code 404. Status code 404 simply means “Not Found”. The client will receive this status code and can react appropriately. When the browser receives a status code 200 (“OK”), it displays the HTML; when it receives a 404, it displays a not found screen; etc.

Invoking Server Functionality Using REST

Think about these methods we just talked about. With POST, you can CREATE something on a server; with GET, you can READ it back; with PUT, you can UPDATE something on the server; and with DELETE, you can DELETE things on the server. They are also known as CRUD operations (CREATE-READ-UPDATE-DELETE). Roy Fielding, the inventor of REST, realized that using the HTTP protocol you can also use HTTP to work with data stored in a database. For example, if you use the GET method with a URI http://someserver/categories, the server can execute some code to retrieve data from the categories relational table and return it. Of course, the server would use a format more appropriate for transferring data, such as XML or JSON. Because there are many different formats for data, the server also needs a way to convey which format it is sending. (At the beginning of the Web, only HTML was used as the format.) This is done through HTTP headers.

HTTP Headers

HTTP headers are instructions exchanged between the client and the server. Headers are key/value pairs, where the client and server agree on the key. Many standard HTTP headers exist which you can find at https://en.wikipedia.org/wiki/List_of_HTTP_header_fields. For example, a server can use the Content-Type header to tell the client to expect a specific format. Another header is the Accept header, which is sent by the client to the server to politely ask the server to send the content in that format; this is also known as content negotiation . Currently, the most popular format is JavaScript Object Notation (JSON). And this is the exchange format you will use with Blazor.

JavaScript Object Notation

JSON is a compact format for transferring data. Look at the example in Listing 6-1.
{ "book" : {
  "title" : "Microsoft Blazor",
  "chapters" : [ "Your first Blazor project", "Data Binding"]
  }
}
Listing 6-1

An Example of JSON

This JSON format describes a book, which can easily be transformed into an object in memory. The simplest JSON object is a string, for example, “Hello world!”, but we can also create complex objects and arrays of JSON objects.

Objects are denoted using curly braces, and inside the curly braces, you will see a comma-separated list of properties. Each property uses a key : value notation. Listing 6-1 contains a single book object with as value another nested JSON object. This nested JSON object contains two properties: title and chapters. The title is a string "Microsoft Blazor". Note that the property name is also transferred as a string. And finally, the chapters property is an array of strings, where you use square brackets to indicate an array.

The JSON format is used for transferring data between two machines but today is also heavily used for configuring tools, such as ASP.NET Core (just look at appsettings.json in an ASP.NET server project). JSON today is way more popular on the Web than XML, probably because of its simplicity.

Some Examples of REST Calls

You need a list of pizzas from a server, and the server exposes the pizzas at URI http://someserver/pizza. To get a list of pizzas, you use the GET method, and you use the Accept header with value application/json to request the JSON format. Look at Figure 6-2 for this example.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig2_HTML.jpg
Figure 6-2

Using REST to Retrieve a List of Pizzas

Maybe your client wants to display the details of a pizza with id number 5. In this case, it can append the id to the URI and perform a GET. Should the server not have any pizza with that id, it can return a status code 404, as illustrated in Figure 6-3.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig3_HTML.png
Figure 6-3

Using REST to Retrieve a Specific Pizza Through Its Unique Id

As the last example, let’s send some data from the client to the server. Imagine that the customer has filled in all the details for the order and clicks the Order button. You then send the order as JSON to the server using the POST method (remember POST means insert). The server can then process the order in any way it likes; for instance, it can insert the order into its database and return a 201: Created status code, as in Figure 6-4. REST recommends returning a status code 201 with the Location header set to the URI for the newly created resource.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig4_HTML.jpg
Figure 6-4

POSTing an Order to the Server

Building a Simple Microservice Using ASP.NET Core

So, how do you build a REST service? Your (hosted) Blazor project uses ASP.NET Core for hosting the Blazor client, and adding a REST service to your server project is easy. But first, let’s do a little intro to microservices.

Services and Single Responsibility

A service is something (here, it will be a piece of software) that listens for requests; when it receives a request, the service handles the request and returns with a response. In Chapter 5, we built a menu service which can return a list of pizzas. In real life, you also encounter services, and they are very similar. Consider a bank. You step into a bank, and you give the teller your account number, some ID, and request $100. The teller will check your account; if you have enough money in your account, the teller will deduct the money and give you the cash. Should your account be too low, the teller will refuse. In both cases, you got a response.

Services should also adhere to the principle of single responsibility. They should do one thing very well, and that’s it. For example, the pizza service will allow clients to retrieve, add, update, and delete pizzas. That’s it. A single responsibility, in this case, PIZZAS.

You can have other services too, each with their own responsibility. Services that take care of one thing are known as microservices.

The Pizza Service

Open the PizzaPlace solution you worked on in previous chapters. In this chapter, you will focus on the PizzaPlace.Server project. The only role this project currently has is to host your Blazor client application, but now you will enhance this role by adding some microservices.

Note

With .NET 6, you can also choose the minimal API approach, which uses a terse C# syntax to accomplish the same thing. In this case, you will need to look inside the Program.cs file instead of Startup.cs.

Open Startup.cs and look at the Configure method , as in Listing 6-2.
public void Configure(IApplicationBuilder app,
                      IWebHostEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
    app.UseWebAssemblyDebugging();
  }
  else
  {
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. ...
    app.UseHsts();
  }
  app.UseHttpsRedirection();
  app.UseBlazorFrameworkFiles();
  app.UseStaticFiles();
  app.UseRouting();
  app.UseEndpoints(endpoints =>
  {
    endpoints.MapRazorPages();
    endpoints.MapControllers();
    endpoints.MapFallbackToFile("index.html");
  });
}
Listing 6-2

The Startup Class’s Configure Method

The last line with the endpoints.MapFallbackToFile("index.html") method takes care of your Blazor client project. But right before it, you see the endpoints.MapControllers() method that is used for hosting your services.

How the MapControllers method works is not the topic of this book, but I will cover what you need to know. If you want to learn more about ASP.NET Core, there are many good books about this topic, such as Pro ASP.NET Core MVC by Adam Freeman (www.apress.com/gp/book/9781430265290).

In a nutshell, ASP.NET MVC will give the HTTP request to a controller class which should inherit the ControllerBase class, and then the controller will execute one of its methods. How does it decide? The MapControllers method will take the request’s URL, for example, /pizzas, and use the first segment of the URL to search for a controller with a matching name, for example, PizzasController. Because we are using REST, the chosen controller will pick the method that matches the verb, for example, GET. If your method is called Get(), it will be invoked, but you can also use the HttpGet attribute on a method. In that case, the method’s name is not important. With the HttpGet attribute, you can specify what the URL should look like, which allows you to pass arguments in the URL to the method. We will look at an example shortly.

Next in line is the Controllers folder of the server project. Initially, this folder is empty, and the idea is that you put your service classes here. In ASP.NET, service classes are known as controllers, hence the name of the folder.

If you are using Visual Studio, right-click this folder and select Add ➤ Controller. Select API Controller - Empty from Figure 6-5 and click Add.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig5_HTML.jpg
Figure 6-5

Adding a New Controller

Type PizzasController and click Add again.

If you are using Code, simply right-click the Controllers folder and select Add File. Name it PizzasController.cs. Now complete the class as in Listing 6-3.

This will add a new class called PizzasController, inheriting from ControllerBase, which you can see in Listing 6-3. This class also has two attributes on it. The [ApiController] attribute tells the ASP.NET runtime that this is a controller for a REST service. The [Route] attribute tells the ASP.NET runtime that the URI where it will expose itself is “api/pizzas”. The “[controller]” part of the route is a placeholder for the name of the controller (Pizzas), but without the “Controller” part.
using Microsoft.AspNetCore.Mvc;
namespace PizzaPlace.Server.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class PizzasController : ControllerBase
  {
  }
}
Listing 6-3

The Empty PizzasController

Let’s add a GET method to retrieve a list of pizzas. For the moment, you will hard-code the list, but in the next section, you will retrieve it from a database. Modify the PizzasController as shown in Listing 6-4.
using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class PizzasController : ControllerBase
  {
    private static readonly List<Pizza> pizzas = new List<Pizza>
    {
      new Pizza(1, "Pepperoni", 8.99M, Spiciness.Spicy ),
      new Pizza(2, "Margarita", 7.99M, Spiciness.None ),
      new Pizza(3, "Diabolo", 9.99M, Spiciness.Hot )
    };
    [HttpGet("/pizzas")]
    public IQueryable<Pizza> GetPizzas()
      => pizzas.AsQueryable();
  }
}
Listing 6-4

Adding a Method to the PizzaController to Retrieve a List of Pizzas

Let’s walk through this implementation. First, you declare a hard-coded static list of pizzas. Next is the GetPizzas method , which has attribute HttpGet("/pizzas"). This attribute says that when you perform a GET HTTP method on the server with the /pizzas URI, the server should call the GetPizzas method. This attribute overrides the Route attribute on the class, so the default api/pizzas will not invoke the GetPizzas method.

The GetPizzas method returns an IQueryable<Pizza>, and ASP.NET Core will send this result back to the client as a list of pizzas. The IQueryable<Pizza> interface is used in .NET to represent data that can be queried, such as database data, and is returned by LINQ queries. Why IQueryable<Pizza>? Because later in this chapter, we will return data from a database which is exposed as this type.

Note that the GetPizzas method contains nothing about HOW the data will be transferred to the client. This is all taken care of for you by ASP.NET Core! By default, your implementation in ASP.NET Core will use JSON, which is what you want. ASP.NET Core allows you to pick other formats, including your custom format. The client can request a certain format, such as XML or JSON using the Accept header in the request. Here, we will be using the default JSON format.

Time to see if it works. First, ensure that the PizzaPlace.Server project is the startup project (with Visual Studio, right-click the PizzaPlace.Server project and select Set as Startup Project from the drop-down menu. The PizzaPlace.Server project should be shown as bold).

Now run your project and wait for the browser to open because you will perform a GET; you can use the browser for the GET method, but for other methods, you will use a nice tool called Postman.

Change the URI in the browser to http://localhost:xxxx/pizzas where xxxx is the original port number in your browser (the port number gets selected by the host and might be different than mine). You should see the result shown in Figure 6-6.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig6_HTML.jpg
Figure 6-6

The Results of Getting a List of Pizzas from the Pizza Service

A JSON-encoded list of pizzas! It works! So here we see an array of objects, and each object has the properties of our Pizza class (except the properties use lowercase which is the convention with JSON).

Now you are ready to retrieve the data from a real database using Entity Framework Core.

What Is Entity Framework Core?

Entity Framework Core is the framework Microsoft recommends for working with databases. Entity Framework Core (EF) is an Object-Relational Mapper which allows you to write classes as normal C# classes and then store and retrieve .NET objects from a database without having to be an SQL expert. It will take care of querying, inserting, updating, and deleting objects in the database for you. This is also known as persistence ignorance , where your code does not need to know how and where data gets stored! Entity Framework Core has support for SQL Server, SQLite, and more.

Using the Code-First Approach

But of course, you need to explain to Entity Framework Core what kind of data you want to store. Entity Framework Core uses a technique called Code First , where you write code to describe the data and how it should be stored in the database. Then, you can use this to generate the database, the tables, and the constraints. If you want to make changes to the database, you can update the database schema with code-first migrations .

If you already have a database, you can also generate the code from the database, also known as EF Database First , but this is not the target of this book.

With code-first approach, you describe the classes (also known as entities ) that will map to database tables. You already have the Pizza class (which you can find in the PizzaPlace.Shared project) to describe the Pizza table in the database. But you need to do more.

In this part, you will be using SQL Server , or SQLite if you don’t have access to SQL Server. If you installed Visual Studio on your Windows machine, SQL Server was installed too.

You can check if SQL Server was installed as follows: start Visual Studio and select View ➤ SQL Server Object Explorer from the menu. Now click Add SQL Server. Expand the local node. If you have SQL Server, locally it should be listed.

If you don’t have SQL Server on your machine, you can install a free version of SQL Server or use a SQL Server instance in the cloud, for example, SQL Server on Azure (https://azure.microsoft.com/get-started). You can even install SQL Server on Linux and OSX! There are some nice articles on the Web (e.g., https://database.guide/how-to-install-sql-server-on-a-mac/) that explain how. And if you don’t want to bother installing SQL Server, you can also use SQLite , which is available out of box with .NET Core, so you don’t need to install anything for SQLite.

Let us start by adding Entity Framework Core to the PizzaPlace.Server project. If you are using Visual Studio, right-click the server project and select Manage NuGet Packages. The NuGet window will open in Visual Studio. NuGet is a very practical way for installing dependencies such as Entity Framework Core to your project. It will not only install the Microsoft.EntityFrameworkCore.SqlServer library but also all its dependencies.

Select the Browse tab and type Microsoft.EntityFrameworkCore.SqlServer in the search box (or Microsoft.EntityFrameworkCore.Sqlite if you are using that). You should see this library as the top search result (if not, look at the top right corner of the NuGet window where you will see Package Source; select nuget.org as the source). Select it, then select the Latest stable version from the Version drop-down, and click the Install button.

With Code, you open the command prompt with the current folder set to where the PizzaPlace.Server project is, and type in the following command:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
If you opted to use SQLite, use this command:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

Add a new class called PizzaPlaceDbContext to the PizzaPlace.Server project, as shown in Listing 6-5. This class represents the database, and you do need to give a couple of hints about how you want your data to be stored in SQL Server (or some other database engine; this uses the same code).

Note

The whole idea of using Entity Framework is to abstract away the underlying database. In general, to switch to a different database engine, you install a different NuGet package which will take care of communicating with the database. The code stays the same and EF is really efficient!

using Microsoft.EntityFrameworkCore;
using PizzaPlace.Shared;
namespace PizzaPlace.Server
{
  public class PizzaPlaceDbContext : DbContext
  {
    public PizzaPlaceDbContext(DbContextOptions<PizzaPlaceDbContext> options)
      : base(options) { }
    public DbSet<Pizza> Pizzas { get; set; } = default!;
    protected override void OnModelCreating(
      ModelBuilder modelBuilder)
    {
      base.OnModelCreating(modelBuilder);
      var pizzaEntity = modelBuilder.Entity<Pizza>();
      pizzaEntity.HasKey(pizza => pizza.Id);
      pizzaEntity.Property(pizza => pizza.Name)
        .HasMaxLength(80);
      pizzaEntity.Property(pizza => pizza.Price)
        .HasColumnType("money");
      pizzaEntity.Property(pizza => pizza.Spiciness)
        .HasConversion<string>();
    }
  }
}
Listing 6-5

The PizzaPlaceDbContext Class

First, you need to create a constructor for the PizzaPlaceDbContext class taking a DbContextOptions<PizzaPlaceDbContext> argument. This is used to pass some options and the connection to the database server, which you will do later in this section.

Next, you add a table to the database to represent your pizzas using a public property of type DbSet<Pizza>. DbSet<T> is the collection class used by Entity Framework Core to represent a table in the database, but you can think of it as a List<T> (one of the cool things with Entity Framework Core is that you work with collections instead of using SQL to talk to the database). Entity Framework Core will use the DbSet<T> to interact with a database table, in this case, the Pizzas table.

Finally, you override the OnModelCreating method , which takes a modelBuilder argument. In the OnModelCreating method, you describe how each DbSet<T> should be mapped to the database; for example, you can tell it which table to use, how each column should be called, which type to use in the database, etc. In this case, you tell the modelBuilder that the Pizza table should have a primary key, the Id property of the Pizza class. We tell it to make the Name maximum 80 characters and how the Price property should be mapped to a SQL type. You will use the MONEY type for that. Finally, we tell EF that the Spiciness enumeration should be mapped to a string using the HasConversion<string>() method . This way, we end up with nice readable entries for spiciness, instead of a number. For the moment, this is enough for your current implementation. You don’t have to explain everything about every property because there are a lot of defaults available. For example, the string type from .NET will be mapped to a type used for strings in the database.

Preparing Your Project for Code-First Migrations

Now you are ready to tell the PizzaPlaze.Server project to use SQL Server (or SQLite) as the database. You do this with dependency injection. In ASP.NET Core, you configure dependency injection in the Startup class’s ConfigureServices method . Let’s have a look at this method which is shown in Listing 6-6.
public void ConfigureServices(IServiceCollection services)
{
  services.AddControllersWithViews();
  services.AddRazorPages();
}
Listing 6-6

The Startup.ConfigureServices Method

Remember IServiceCollection from Chapter 5? Here, dependencies for ASP.NET Core are added, such as dependencies for Controllers and razor pages, which are required for your service.

The Startup class also comes with a constructor as in Listing 6-7.
public class Startup
{
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
  }
  public IConfiguration Configuration { get; }
Listing 6-7

The Startup Class’s Constructor

You need this constructor to have access to the project's configuration file. The configuration will contain the connection string for the database to talk to.

Now we will provide the PizzaPlaceDbContext class as a dependency in the ConfigureServices method . If you are using SQL Server, add the following code from Listing 6-8 at the end of the ConfigureServices method .
public void ConfigureServices(IServiceCollection services)
{
  services.AddControllersWithViews();
  services.AddRazorPages();
  services.AddDbContext<PizzaPlaceDbContext>(options =>
    options.UseSqlServer(
      Configuration.GetConnectionString("PizzaPlaceDb")));
}
Listing 6-8

Adding Entity Framework Dependencies

This single statement tells ASP.NET Core that you will be using the PizzaPlaceDbContext and that you will be storing it in SQL Server. This code also looks up the connection string for the database in configuration, which you still need to add.

Should you opt for SQLite, you need to add the code from Listing 6-9.
public void ConfigureServices(IServiceCollection services)
{
  services.AddControllersWithViews();
  services.AddRazorPages();
  services.AddDbContext<PizzaPlaceDbContext>(options =>
   options.UseSqlite(
     Configuration.GetConnectionString("PizzaPlaceDbLite"))
    );
}
Listing 6-9

Using SQLite As the Database

ASP.NET Core allows you to place your configuration settings in many different places, such as a JSON configuration file, environment variables, etc. Our server project already has a configuration file called appsettings.json, so open it.

You need to add a connection string that will allow access to the database. A database connection string tells your code where to find the database server, which database to use, and which credentials should be used to log in. Update the appsettings.json configuration file as in Listing 6-10. This actually contains two connection strings, one for SQL Server and one for SQLite. The SQL Server connection string uses the (localdb)\\MSSQLLocalDB server, which is the server installed with Visual Studio. Of course, if you are using another database server, you will also have to change the server name. There are a lot of examples on www.connectionstrings.com/, or read on to find out how to get the connection string with Visual Studio. The SQLite connection string is a lot simpler, containing the name of the file where we will store the data using SQLite.
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "PizzaPlaceDb": "Server=(localdb)\\MSSQLLocalDB;Database=PizzaPlaceDb;Trusted_Connection=True;MultipleActiveResultSets=true",
    "PizzaPlaceDbLite": "Data Source=PizzaPlace.db"
  }
}
Listing 6-10

The appsettings.json Configuration File

Finding Your Database Server’s Connection String

If you are not sure which connection string to use, you can find the connection string for SQL Server in Visual Studio by selecting View ➤ SQL Server Object Explorer.

You can connect to a database by clicking the server icon with the little green + sign, shown in Figure 6-7.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig7_HTML.jpg
Figure 6-7

SQL Server Object Explorer

You can look for available database servers by expanding the Local, Network, or Azure expanders as in Figure 6-8. I recommend that you try to find the MSSQLLocalDB database server. If you use another database server, you might need to change how to log in to your database. When you’re ready, click Connect.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig8_HTML.jpg
Figure 6-8

Finding the Connection String for a Database

Next, expand SQL Server in SQL Server Object Explorer from Figure 6-9 and select your server. Right-click it and select Properties. Now copy the connection string from the properties window, paste it in appsettings.json, and change the database name to PizzaPlaceDb.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig9_HTML.jpg
Figure 6-9

Getting the Database’s Properties

Creating Your First Code-First Migration

You are almost ready to generate the database from the code. Start by adding the Microsoft.EntityFrameworkCore.Design NuGet package to the PizzaPlace.Server project. You need this package to perform code-first migrations.

Now you need to create a code-first migration . A migration is a C# class that contains the changes that need to be made to the database to bring it up (or down) to the database schema your application needs. This is done through a tool called dotnet-ef .

Start by selecting from the Visual Studio menu View ➤ Other Windows ➤ Package Manager Console. Or use the command line (cmd.exe) if you prefer. If you are using Code, use the integrated terminal or open a command prompt.

You must run the next command in the PizzaPlace.Server directory, so make sure you are in the correct directory (the one with the PizzaPlace.Server.csproj file).

You might need to install the global dotnet-ef command-line tool as well. This is the tool you use to generate the migration from your code and to update the database once you are happy with the generated migration. Run the following command to install the migration tool. You only need to install this tool once.

dotnet tool install --global dotnet-ef

Now execute the following command to create the migration:
dotnet-ef migrations add CreatingPizzaPlaceDb
Here, you use the dotnet-ef tool to add a new migration called CreatingPizzaPlaceDb. You can pick any name you want for the migration; do pick one that makes sense. You should see the following output:
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'

Should you get an error or warnings, please review the code for the Pizza and the PizzaPlaceDbContext classes (and maybe compare with the provided sources for the book), ensure that all the Entity Framework packages are using the same version, and try again.

This tool created a new Migrations folder in the PizzaPlace.Server project with two files similar to Figure 6-10 but with a different timestamp.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig10_HTML.jpg
Figure 6-10

The Result of Adding the First Migration

Open the CreatingPizzaDb.cs file from Listing 6-11 and look at what the tool did. If you have been using SQLite, consult Listing 6-12.
using Microsoft.EntityFrameworkCore.Migrations;
namespace PizzaPlace.Server.Migrations
{
  public partial class CreatingPizzaPlaceDb : Migration
  {
    protected override void Up(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.CreateTable(
          name: "Pizzas",
          columns: table => new
          {
            Id = table.Column<int>(type: "int", nullable: false)
                  .Annotation("SqlServer:Identity", "1, 1"),
            Name = table.Column<string>(type: "nvarchar(80)",
              maxLength: 80, nullable: false),
            Price = table.Column<decimal>(type: "money",
              nullable: false),
            Spiciness = table.Column<string>(type:
              "nvarchar(max)", nullable: false)
          },
          constraints: table =>
          {
            table.PrimaryKey("PK_Pizzas", x => x.Id);
          });
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.DropTable(
          name: "Pizzas");
    }
  }
}
Listing 6-11

The CreatingPizzaDb.cs File for SQL Server

A migration class has two methods: Up and Down. The Up method will upgrade the database schema. In this case, it will create a new table called Pizzas with Id, Name, Price, and Spiciness columns.

The Down method downgrades the database schema, in this case, by dropping the column. As you are developing, you will make small changes to the database schema; each change becomes a migration. We can then use these migrations to update the database or go back to a previous schema of the database. You can also apply a whole series of changes when you want to update your production database to match the development database’s schema.
using Microsoft.EntityFrameworkCore.Migrations;
namespace PizzaPlace.Server.Migrations
{
  public partial class Created : Migration
  {
    protected override void Up(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.CreateTable(
        name: "Pizzas",
        columns: table => new
        {
          Id = table.Column<int>(type: "INTEGER",
                                 nullable: false)
                    .Annotation("Sqlite:Autoincrement", true),
            Name = table.Column<string>(type: "TEXT",
                                        maxLength: 80,
                                        nullable: false),
            Price = table.Column<decimal>(type: "money",
                                          nullable: false),
            Spiciness = table.Column<string>(type: "TEXT",
                                             nullable: false)
        },
        constraints: table =>
        {
          table.PrimaryKey("PK_Pizzas", x => x.Id);
        });
    }
    protected override void Down(
      MigrationBuilder migrationBuilder)
    {
      migrationBuilder.DropTable(
          name: "Pizzas");
    }
  }
}
Listing 6-12

The Migration Class for SQLite

Generating the Database

Now you are ready to generate the database from your migration. With Visual Studio, go back to the Command Line or Package Manager Console window (View ➤ Other Windows ➤ Package Manager Console), or with Code, open the integrated terminal (View ➤ Terminal). Ensure you are in the folder that contains the PizzaPlace.Server project and type the following command:
dotnet-ef database update

This just created the database for you! Let’s have a look at the database. First, let’s look at SQL Server.

From Visual Studio, open View ➤ SQL Server Object Explorer and expand the tree for the PizzaPlaceDb database as in Figure 6-11 (you might need to refresh the database: right-click Databases and select Refresh).
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig11_HTML.jpg
Figure 6-11

SQL Server Object Explorer Showing the PizzaPlaceDb Database

If you don’t have Visual Studio, you can download Azure Data Studio from https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio. After installation ends, start Azure Data Studio and create a new connection. Enter your server name and select PizzaPlaceDb from the drop-down list, as shown in Figure 6-12.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig12_HTML.jpg
Figure 6-12

Connection with SQL Operations Studio

To look at the SQLite database, you will need to download and install the SQLite database browser from https://sqlitebrowser.org/.

Now run DB Browser, and open the PizzaPlace.db. You can now use this tool to explore the database as in Figure 6-13.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig13_HTML.jpg
Figure 6-13

Using DB Browser for SQLite

Enhancing the Pizza Microservice

Let’s add some functionality to the Pizza microservice so it uses the database instead of hard-coded data and add a method to insert a pizza in your database.

Open the PizzaController class , which sits in the Controllers folder of the PizzaPlace.Server project. Start by adding a constructor that takes the PizzaPlaceDbContext as an argument, as in Listing 6-13.
using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class PizzasController : ControllerBase
  {
    private readonly PizzaPlaceDbContext db;
    public PizzasController(PizzaPlaceDbContext db)
    {
      this.db = db;
    }
    ...
  }
}
Listing 6-13

Injecting a PizzaPlaceDbContext Instance into the Controller

To talk to the database, the PizzasController needs a PizzaPlaceDbContext instance, and as you learned in Chapter 5, you can use a constructor to do this. The constructor only needs to save the reference in a local field (for now).

You don’t need the hard-coded list of pizzas, so remove the static field, and update the GetPizza method to use the PizzaPlaceDbContext instead, as in Listing 6-14. To get all the pizzas, you can simply use the Pizzas property of the PizzaPlaceDbContext. The Entity Framework will access the database when it accesses the Pizzas property and return all the rows in the Pizza table. Also remove the Route attribute, since the GetPizzas method specifies its URL.
using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
  [ApiController]
  public class PizzasController : ControllerBase
  {
    private readonly PizzaPlaceDbContext db;
    public PizzasController(PizzaPlaceDbContext db)
    {
      this.db = db;
    }
    [HttpGet("/pizzas")]
    public IQueryable<Pizza> GetPizzas()
      => db.Pizzas;
  }
}
Listing 6-14

Retrieving the Pizzas from the Database

Now let’s add a method to insert a new pizza in the database. Add the InsertPizza method from Listing 6-15 to the PizzasController class. This method will receive a pizza instance from the client as part of the POST request body, so you add the HttpPost attribute with the URI that you should post to. The pizza object will be posted in the request body, and this is why the InsertPizza method’s pizza argument has the FromBody attribute to tell ASP.NET MVC Core to convert the body of the request to a pizza instance. The method adds the pizza to the PizzaPlaceDbContext Pizzas table and then saves it to the database using the SaveChanges method. The InsertPizza method then returns a 201 Created status code with the URI of the pizza as the response, as is the convention with REST. There are many possible HTTP status codes that you could return from a controller’s method. But the most common of them have special helper methods that make it easy to return a certain status code, for instance, Ok(), NotFound(). In this case, you return a 201 – Created status code. You will examine this response with Postman in the next part of this chapter.
using Microsoft.AspNetCore.Mvc;
using PizzaPlace.Shared;
using System.Collections.Generic;
using System.Linq;
namespace PizzaPlace.Server.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class PizzasController : ControllerBase
  {
    private readonly PizzaPlaceDbContext db;
    public PizzasController(PizzaPlaceDbContext db)
    {
      this.db = db;
    }
    [HttpGet("/pizzas")]
    public IQueryable<Pizza> GetPizzas()
      => db.Pizzas;
    [HttpPost("/pizzas")]
    public IActionResult InsertPizza([FromBody] Pizza pizza)
    {
      db.Pizzas.Add(pizza);
      db.SaveChanges();
      return Created($"pizzas/{pizza.Id}", pizza);
    }
  }
}
Listing 6-15

The InsertPizza Method

This is an introduction to REST services. Building real services with all the different approaches and best practices can take up a whole book. The idea of this chapter is to get you up and running.

Testing Your Microservice Using Postman

So now you have your first microservice. But how do you test it? Previously, you used the browser to test the GetPizzas method , which uses the GET method. For other methods, such as POST, PUT, and DELETE, you need a better tool. Here, you will use Postman, which is a tool specifically for testing REST services.

Open your favorite browser and go to www.getpostman.com. Download the application and install it. By the time you read this book, the installation procedure may have changed a bit, so please follow the instructions from the installer.

After it has installed, run Postman.

Postman will open, and it will ask you what you want to do. Select Create New, as shown in Figure 6-14.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig14_HTML.jpg
Figure 6-14

Select Create New to Get Started with Postman

Now select Create a request as in Figure 6-15.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig15_HTML.jpg
Figure 6-15

Create a Request with Postman

Now run the PizzaPlace solution and copy the URI from the browser. Paste it in Postman’s URL field and append /pizzas as in Figure 6-16. Don’t forget that you most likely will have a different port number!
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig16_HTML.jpg
Figure 6-16

Making a GET Request with Postman

Before you click Send, let’s add the Accept header. Click the Headers tab and enter Accept as the key and application/json as the value. Please refer to Figure 6-17 for reference.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig17_HTML.jpg
Figure 6-17

Adding Headers to the Request in Postman

Now you can click Send. You should receive an empty list as in Figure 6-18. Also note the 200 OK status code, meaning the method was executed successfully! We simply don’t have any pizzas in our database yet.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig18_HTML.jpg
Figure 6-18

Receiving an Empty List of Pizzas from the Server

Let’s add a couple of pizzas to the database. At the top of Postman, you will find a tab with a plus sign. Click it to add another tab. Select POST as the method and copy the URI from the previous tab, as shown in Figure 6-19.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig19_HTML.jpg
Figure 6-19

Starting with the POST Request

Now select the Headers section and add a new header with key Content-Type and value application/json like in Figure 6-20.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig20_HTML.jpg
Figure 6-20

Adding the Content-Type Header for the POST Request

Now select the Body section, select the raw format using the drop-down, and enter a pizza object using JSON. Please refer to Figure 6-21. Note that this raw string contains the pizza’s properties serialized as JSON and that you don’t need to send the Id property because the server will generate the id when it gets inserted into the database.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig21_HTML.jpg
Figure 6-21

Entering a Pizza Using JSON

Ensure your PizzaPlace application is still running, and then click the Send button. If all is well, you should receive a positive 201 Created response as in Figure 6-22. In the response area of Postman, select the Headers tab. Look for the Location header. It will show the new URI given to this pizza. This Location header is returned by the Created method you called as the last line of Listing 6-15.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig22_HTML.jpg
Figure 6-22

The POST Response in Postman

Click the first tab where you created the GET request and click Send again. Now you should have a list of pizzas (a list of one). Try creating a couple of other pizzas. Figure 6-23 is my result after adding three pizzas.
../images/469993_3_En_6_Chapter/469993_3_En_6_Fig23_HTML.png
Figure 6-23

A List of Pizzas Stored in the Database

Summary

In this chapter, you had a look at how to store data on the server using Entity Framework Core and how to expose that data using Web API, REST, and microservices. You added a pizza service to the PizzaPlace application and then went on testing it with Postman.

In the next chapter, you will learn how to talk to your service(s) from Blazor.