Part 2, add a model to a Razor Pages app in ASP.NET Core

By Rick Anderson

In this tutorial, classes are added for managing movies in a database. The app's model classes use Entity Framework Core (EF Core) to work with the database. EF Core is an object-relational mapper (O/RM) that simplifies data access. You write the model classes first, and EF Core creates the database.

The model classes are known as POCO classes (from "Plain-Old CLR Objects") because they don't have a dependency on EF Core. They define the properties of the data that are stored in the database.

Add a data model

  1. In Solution Explorer, right-click the RazorPagesMovie project > Add > New Folder. Name the folder Models.

  2. Right-click the Models folder. Select Add > Class. Name the class Movie.

  3. Add the following properties to the Movie class:

    using System.ComponentModel.DataAnnotations;
    
    namespace RazorPagesMovie.Models
    {
        public class Movie
        {
            public int ID { get; set; }
            public string Title { get; set; } = string.Empty;
    
            [DataType(DataType.Date)]
            public DateTime ReleaseDate { get; set; }
            public string Genre { get; set; } = string.Empty;
            public decimal Price { get; set; }
        }
    }
    

The Movie class contains:

  • The ID field is required by the database for the primary key.

  • A [DataType] attribute that specifies the type of data in the ReleaseDate property. With this attribute:

    • The user isn't required to enter time information in the date field.
    • Only the date is displayed, not time information.

DataAnnotations are covered in a later tutorial.

Build the project to verify there are no compilation errors.

Scaffold the movie model

In this section, the movie model is scaffolded. That is, the scaffolding tool produces pages for Create, Read, Update, and Delete (CRUD) operations for the movie model.

  1. Add the NuGet package Microsoft.EntityFrameworkCore.Design, which is required for the scaffolding tool.

    1. From the Tools menu, select NuGet Package Manager > Manage NuGet Packages for Solution NuGet Package Manager - manage
    2. Select the Browse tab.
    3. Check Include prerelease
    4. Enter Microsoft.EntityFrameworkCore.Design and select it from the list.
    5. Check Project and then Select Install
    6. Select I Accept in the License Acceptance dialog. NuGet Package Manager - add package
  2. Create the Pages/Movies folder:

    1. Right-click on the Pages folder > Add > New Folder.
    2. Name the folder Movies.
  3. Right-click on the Pages/Movies folder > Add > New Scaffolded Item.

    New Scaffolded Item

  4. In the Add New Scaffold dialog, select Razor Pages using Entity Framework (CRUD) > Add.

    Add Scaffold

  5. Complete the Add Razor Pages using Entity Framework (CRUD) dialog:

    1. In the Model class drop down, select Movie (RazorPagesMovie.Models).
    2. In the Data context class row, select the + (plus) sign.
      1. In the Add Data Context dialog, the class name RazorPagesMovie.Data.RazorPagesMovieContext is generated.
    3. Select Add.

    Add Razor Pages

    If you get an error message that says you need to install the Microsoft.EntityFrameworkCore.SqlServer package, repeat the steps starting with Add > New Scaffolded Item.

The appsettings.json file is updated with the connection string used to connect to a local database.

Files created and updated

The scaffold process creates the following files:

  • Pages/Movies: Create, Delete, Details, Edit, and Index.
  • Data/RazorPagesMovieContext.cs

The created files are explained in the next tutorial.

The scaffold process adds the following highlighted code to the Program.cs file:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

builder.Services.AddDbContext<RazorPagesMovieContext>(options => 
       options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPagesMovieContext")));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production
    // scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

The Program.cs changes are explained later in this tutorial.

Create the initial database schema using EF's migration feature

The migrations feature in Entity Framework Core provides a way to:

  • Create the initial database schema.
  • Incrementally update the database schema to keep it in sync with the app's data model. Existing data in the database is preserved.

In this section, the Package Manager Console (PMC) window is used to:

  • Add an initial migration.
  • Update the database with the initial migration.
  1. From the Tools menu, select NuGet Package Manager > Package Manager Console.

    PMC menu

  2. In the PMC, enter the following commands:

    Add-Migration InitialCreate
    Update-Database
    
    

The preceding commands install the Entity Framework Core tools and run the migrations command to generate code that creates the initial database schema.

The following warning is displayed, which is addressed in a later step:

No type was specified for the decimal column 'Price' on entity type 'Movie'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'HasColumnType()'.

The database schema is based on the model specified in DbContext. The InitialCreate argument is used to name the migration. Any name can be used, but by convention a name is selected that describes the migration.

The migrations command generates code to create the initial database schema. The schema is based on the model specified in DbContext. The InitialCreate argument is used to name the migrations. Any name can be used, but by convention a name is selected that describes the migration.

The update command runs the Up method in migrations that have not been applied. In this case, update runs the Up method in the Migrations/<time-stamp>_InitialCreate.cs file, which creates the database.

Examine the context registered with dependency injection

ASP.NET Core is built with dependency injection. Services, such as the EF Core database context, are registered with dependency injection during application startup. Components that require these services (such as Razor Pages) are provided via constructor parameters. The constructor code that gets a database context instance is shown later in the tutorial.

The scaffolding tool automatically created a database context and registered it with the dependency injection container. The following highlighted code is added to the Program.cs file by the scaffolder:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

builder.Services.AddDbContext<RazorPagesMovieContext>(options => 
       options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPagesMovieContext")));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production
    // scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

The data context RazorPagesMovieContext:

  • Derives from Microsoft.EntityFrameworkCore.DbContext.
  • Specifies which entities are included in the data model.
  • Coordinates EF Core functionality, such as Create, Read, Update and Delete, for the Movie model.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Data
{
    public class RazorPagesMovieContext : DbContext
    {
        public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
            : base(options)
        {
        }

        public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; }
    }
}

The preceding code creates a DbSet<Movie> property for the entity set. In Entity Framework terminology, an entity set typically corresponds to a database table. An entity corresponds to a row in the table.

The name of the connection string is passed in to the context by calling a method on a DbContextOptions object. For local development, the Configuration system reads the connection string from the appsettings.json file.

Build the app

Build the app. The compiler generates several nullable warnings. See this GitHub issue, Nullable reference types (NRTs) and .NET compiler null-state static analysis, and Nullable reference types for more information.

Fix the warning messages

In this section, you can either disable nullable warnings or fix the scaffolded code. To eliminate the warnings from nullable reference types, remove the following line from the RazorPagesMovie .csproj file:

<Nullable>enable</Nullable>

Alternatively, fix the scaffolded code. The RazorPagesMovieContext generates the following warning:

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

To fix the warning, apply the suggestion and declare the Movie property nullable.

        public DbSet<RazorPagesMovie.Models.Movie>? Movie { get; set; }
    }
}

The ? declares the property nullable.

An alternative approach is to disable the CS8618 warning with pragma statements:

    public class RazorPagesMovieContext : DbContext
    {
#pragma warning disable CS8618
        public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
#pragma warning restore CS8618 
            : base(options)
        {
        }

        public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; }
    }
}

For the warnings in the Razor Pages C# code behind files, use the pragma C# preprocessor directive to disable warnings. For example, use the following highlighted code in the Pages/Movies/Index.cshtml.cs file:

namespace RazorPagesMovie.Pages.Movies
{
#pragma warning disable CS8618
#pragma warning disable CS8604
    public class IndexModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

        public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
        {
            _context = context;
        }

        public IList<Movie> Movie { get; set; }

        public async Task OnGetAsync()
        {
            Movie = await _context.Movie.ToListAsync();
        }
    }
#pragma warning restore CS8618
#pragma warning restore CS8604
}

The Pages/Movies/Delete.cshtml.cs file requires the following pragma statements:

#pragma warning disable CS8618
#pragma warning disable CS8601
#pragma warning disable CS8602
#pragma warning disable CS8604
// Class
#pragma warning restore CS8618
#pragma warning restore CS8601
#pragma warning restore CS8602
#pragma warning restore CS8604

Ignore NU1603 package mismatch warnings, they will be fixed when .NET 6 is released.

Test the app

  1. Run the app and append /Movies to the URL in the browser (http://localhost:port/movies).

    If you receive the following error:

    SqlException: Cannot open database "RazorPagesMovieContext-GUID" requested by the login. The login failed.
    Login failed for user 'User-name'.
    

    You missed the migrations step.

  2. Test the Create link.

    Create page

    Note

    You may not be able to enter decimal commas in the Price field. To support jQuery validation for non-English locales that use a comma (",") for a decimal point and for non US-English date formats, the app must be globalized. For globalization instructions, see this GitHub issue.

  3. Test the Edit, Details, and Delete links.

The next tutorial explains the files created by scaffolding.

Troubleshooting with the completed sample

If you run into a problem you can't resolve, compare your code to the completed project. View or download completed project (how to download).

Additional resources

In this section, classes are added for managing movies in a database. The app's model classes use Entity Framework Core (EF Core) to work with the database. EF Core is an object-relational mapper (O/RM) that simplifies data access. You write the model classes first, and EF Core creates the database.

The model classes are known as POCO classes (from "Plain-Old CLR Objects") because they don't have a dependency on EF Core. They define the properties of the data that are stored in the database.

View or download sample code (how to download).

Add a data model

  1. In Solution Explorer, right-click the RazorPagesMovie project > Add > New Folder. Name the folder Models.

  2. Right-click the Models folder. Select Add > Class. Name the class Movie.

  3. Add the following properties to the Movie class:

    using System;
    using System.ComponentModel.DataAnnotations;
    
    namespace RazorPagesMovie.Models
    {
        public class Movie
        {
            public int ID { get; set; }
            public string Title { get; set; }
    
            [DataType(DataType.Date)]
            public DateTime ReleaseDate { get; set; }
            public string Genre { get; set; }
            public decimal Price { get; set; }
        }
    }
    

The Movie class contains:

  • The ID field is required by the database for the primary key.

  • [DataType(DataType.Date)]: The [DataType] attribute specifies the type of the data (Date). With this attribute:

    • The user isn't required to enter time information in the date field.
    • Only the date is displayed, not time information.

DataAnnotations are covered in a later tutorial.

Build the project to verify there are no compilation errors.

Scaffold the movie model

In this section, the movie model is scaffolded. That is, the scaffolding tool produces pages for Create, Read, Update, and Delete (CRUD) operations for the movie model.

  1. Create a Pages/Movies folder:

    1. Right-click on the Pages folder > Add > New Folder.
    2. Name the folder Movies.
  2. Right-click on the Pages/Movies folder > Add > New Scaffolded Item.

    New Scaffolded Item

  3. In the Add Scaffold dialog, select Razor Pages using Entity Framework (CRUD) > Add.

    Add Scaffold

  4. Complete the Add Razor Pages using Entity Framework (CRUD) dialog:

    1. In the Model class drop down, select Movie (RazorPagesMovie.Models).
    2. In the Data context class row, select the + (plus) sign.
      1. In the Add Data Context dialog, the class name RazorPagesMovie.Data.RazorPagesMovieContext is generated.
    3. Select Add.

    Add Razor Pages

The appsettings.json file is updated with the connection string used to connect to a local database.

Files created and updated

The scaffold process creates the following files:

  • Pages/Movies: Create, Delete, Details, Edit, and Index.
  • Data/RazorPagesMovieContext.cs

Updated files

  • Startup.cs

The created and updated files are explained in the next section.

Create the initial database schema using EF's migration feature

The migrations feature in Entity Framework Core provides a way to:

  • Create the initial database schema.
  • Incrementally update the database schema to keep it in sync with the application's data model. Existing data in the database is preserved.

In this section, the Package Manager Console (PMC) window is used to:

  • Add an initial migration.
  • Update the database with the initial migration.
  1. From the Tools menu, select NuGet Package Manager > Package Manager Console.

    PMC menu

  2. In the PMC, enter the following commands:

    Add-Migration InitialCreate
    Update-Database
    

For SQL Server, the preceding commands generate the following warning: "No type was specified for the decimal column 'Price' on entity type 'Movie'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'HasColumnType()'."

Ignore the warning, as it will be addressed in a later step.

The migrations command generates code to create the initial database schema. The schema is based on the model specified in DbContext. The InitialCreate argument is used to name the migrations. Any name can be used, but by convention a name is selected that describes the migration.

The update command runs the Up method in migrations that have not been applied. In this case, update runs the Up method in the Migrations/<time-stamp>_InitialCreate.cs file, which creates the database.

Examine the context registered with dependency injection

ASP.NET Core is built with dependency injection. Services, such as the EF Core database context, are registered with dependency injection during application startup. Components that require these services (such as Razor Pages) are provided via constructor parameters. The constructor code that gets a database context instance is shown later in the tutorial.

The scaffolding tool automatically created a database context and registered it with the dependency injection container.

Examine the Startup.ConfigureServices method. The highlighted line was added by the scaffolder:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<RazorPagesMovieContext>(options =>
      options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));
}

The RazorPagesMovieContext coordinates EF Core functionality, such as Create, Read, Update and Delete, for the Movie model. The data context (RazorPagesMovieContext) is derived from Microsoft.EntityFrameworkCore.DbContext. The data context specifies which entities are included in the data model.

using Microsoft.EntityFrameworkCore;

namespace RazorPagesMovie.Data
{
    public class RazorPagesMovieContext : DbContext
    {
        public RazorPagesMovieContext (
            DbContextOptions<RazorPagesMovieContext> options)
            : base(options)
        {
        }

        public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; }
    }
}

The preceding code creates a DbSet<Movie> property for the entity set. In Entity Framework terminology, an entity set typically corresponds to a database table. An entity corresponds to a row in the table.

The name of the connection string is passed in to the context by calling a method on a DbContextOptions object. For local development, the Configuration system reads the connection string from the appsettings.json file.

Test the app

  1. Run the app and append /Movies to the URL in the browser (http://localhost:port/movies).

    If you receive the following error:

    SqlException: Cannot open database "RazorPagesMovieContext-GUID" requested by the login. The login failed.
    Login failed for user 'User-name'.
    

    You missed the migrations step.

  2. Test the Create link.

    Create page

    Note

    You may not be able to enter decimal commas in the Price field. To support jQuery validation for non-English locales that use a comma (",") for a decimal point and for non US-English date formats, the app must be globalized. For globalization instructions, see this GitHub issue.

  3. Test the Edit, Details, and Delete links.

SQL Logging of Entity Framework Core

Logging configuration is commonly provided by the Logging section of appsettings.{Environment}.json files. To log SQL statements, add "Microsoft.EntityFrameworkCore.Database.Command": "Information" to the appsettings.Development.json file:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
     ,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  "AllowedHosts": "*"
}

With the preceding JSON, SQL statements are displayed on the command line and in the Visual Studio output window.

For more information, see Logging in .NET Core and ASP.NET Core and this GitHub issue.

The next tutorial explains the files created by scaffolding.

Additional resources

In this section, classes are added for managing movies. The app's model classes use Entity Framework Core (EF Core) to work with the database. EF Core is an object-relational mapper (O/RM) that simplifies data access.

The model classes are known as POCO classes (from "plain-old CLR objects") because they don't have any dependency on EF Core. They define the properties of the data that are stored in the database.

View or download sample code (how to download).

Add a data model

Right-click the RazorPagesMovie project > Add > New Folder. Name the folder Models.

Right-click the Models folder. Select Add > Class. Name the class Movie.

Add the following properties to the Movie class:

using System;
using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

The Movie class contains:

  • The ID field is required by the database for the primary key.

  • [DataType(DataType.Date)]: The DataType attribute specifies the type of the data (Date). With this attribute:

    • The user is not required to enter time information in the date field.
    • Only the date is displayed, not time information.

DataAnnotations are covered in a later tutorial.

DataAnnotations are covered in a later tutorial.

Build the project to verify there are no compilation errors.

Scaffold the movie model

In this section, the movie model is scaffolded. That is, the scaffolding tool produces pages for Create, Read, Update, and Delete (CRUD) operations for the movie model.

Create a Pages/Movies folder:

  • Right-click on the Pages folder > Add > New Folder.
  • Name the folder Movies.

Right-click on the Pages/Movies folder > Add > New Scaffolded Item.

New Scaffolded Item on VScode

In the Add Scaffold dialog, select Razor Pages using Entity Framework (CRUD) > Add.

Add Scaffold on VScode

Complete the Add Razor Pages using Entity Framework (CRUD) dialog:

  • In the Model class drop down, select Movie (RazorPagesMovie.Models).
  • In the Data context class row, select the + (plus) sign and change the generated name from RazorPagesMovie.Models.RazorPagesMovieContext to RazorPagesMovie.Data.RazorPagesMovieContext. This change is not required. It creates the database context class with the correct namespace.
  • Select Add.

Add Razor Pages on VScode

The appsettings.json file is updated with the connection string used to connect to a local database.

Files created

The scaffold process creates and updates the following files:

  • Pages/Movies: Create, Delete, Details, Edit, and Index.
  • Data/RazorPagesMovieContext.cs

Updated

  • Startup.cs

The created and updated files are explained in the next section.

Initial migration

In this section, the Package Manager Console (PMC) is used to:

  • Add an initial migration.
  • Update the database with the initial migration.

From the Tools menu, select NuGet Package Manager > Package Manager Console.

PMC menu

In the PMC, enter the following commands:

Add-Migration InitialCreate
Update-Database

The preceding commands generate the following warning: "No type was specified for the decimal column 'Price' on entity type 'Movie'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'HasColumnType()'."

Ignore the warning, as it will be addressed in a later step.

The migrations command generates code to create the initial database schema. The schema is based on the model specified in DbContext. The InitialCreate argument is used to name the migrations. Any name can be used, but by convention a name is selected that describes the migration.

The update command runs the Up method in migrations that have not been applied. In this case, update runs the Up method in Migrations/<time-stamp>_InitialCreate.cs file, which creates the database.

Examine the context registered with dependency injection

ASP.NET Core is built with dependency injection. Services, such as the EF Core database context context, are registered with dependency injection during application startup. Components that require these services, such as Razor Pages, are provided via constructor parameters. The constructor code that gets a database context context instance is shown later in the tutorial.

The scaffolding tool automatically created a database context context and registered it with the dependency injection container.

Examine the Startup.ConfigureServices method. The highlighted line was added by the scaffolder:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<RazorPagesMovieContext>(options =>
      options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));
}

The RazorPagesMovieContext coordinates EF Core functionality, such as Create, Read, Update and Delete, for the Movie model. The data context (RazorPagesMovieContext) is derived from Microsoft.EntityFrameworkCore.DbContext. The data context specifies which entities are included in the data model.

using Microsoft.EntityFrameworkCore;

namespace RazorPagesMovie.Data
{
    public class RazorPagesMovieContext : DbContext
    {
        public RazorPagesMovieContext (
            DbContextOptions<RazorPagesMovieContext> options)
            : base(options)
        {
        }

        public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; }
    }
}

The preceding code creates a DbSet<Movie> property for the entity set. In Entity Framework terminology, an entity set typically corresponds to a database table. An entity corresponds to a row in the table.

The name of the connection string is passed in to the context by calling a method on a DbContextOptions object. For local development, the Configuration system reads the connection string from the appsettings.json file.

Test the app

  • Run the app and append /Movies to the URL in the browser (http://localhost:port/movies).

If you get the error:

SqlException: Cannot open database "RazorPagesMovieContext-GUID" requested by the login. The login failed.
Login failed for user 'User-name'.

You missed the migrations step.

  • Test the Create link.

    Create page

    Note

    You may not be able to enter decimal commas in the Price field. To support jQuery validation for non-English locales that use a comma (",") for a decimal point and for non US-English date formats, the app must be globalized. For globalization instructions, see this GitHub issue.

  • Test the Edit, Details, and Delete links.

The next tutorial explains the files created by scaffolding.

Additional resources