20 KiB
title | author | description | monikerRange | ms.author | ms.date | uid |
---|---|---|---|---|---|---|
Part 4, add a model to an ASP.NET Core MVC app | wadepickett | Part 4 of tutorial series on ASP.NET Core MVC. | >= aspnetcore-3.1 | wpickett | 07/24/2024 | tutorials/first-mvc-app/adding-model |
Part 4, add a model to an ASP.NET Core MVC app
By Rick Anderson and Jon P Smith.
:::moniker range=">= aspnetcore-9.0"
In this tutorial, classes are added for managing movies in a database. These classes are the "Model" part of the MVC app.
These model classes are used with Entity Framework Core (EF Core) to work with a database. EF Core is an object-relational mapping (ORM) framework that simplifies the data access code that you have to write.
The model classes created are known as POCO classes, from Plain Old CLR Objects. POCO classes don't have any dependency on EF Core. They only define the properties of the data to be stored in the database.
In this tutorial, model classes are created first, and EF Core creates the database.
Add a data model class
Visual Studio
Right-click the Models folder > Add > Class. Name the file Movie.cs
.
Visual Studio Code
Add a file named Movie.cs
to the Models folder.
Update the Models/Movie.cs
file with the following code:
The Movie
class contains an Id
field, which is required by the database for the primary key.
The xref:System.ComponentModel.DataAnnotations.DataType attribute on ReleaseDate
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.
The question mark after string
indicates that the property is nullable. For more information, see Nullable reference types.
Add NuGet packages
Visual Studio
Visual Studio automatically installs the required packages.
Build the project as a check for compiler errors.
Visual Studio Code
In Visual Studio Code, press Ctrl+F5 (Windows) or ⌘+F5 (macOS) to run the app without debugging.
In the Panel below the editor region, select the PROBLEMS tab, or from the View menu, select Problems if it is not currently in view. Verify there are no compilation errors.
Scaffold movie pages
Use the scaffolding tool to produce Create
, Read
, Update
, and Delete
(CRUD) pages for the movie model.
Visual Studio
In Solution Explorer, right-click the Controllers folder and select Add > New Scaffolded Item.
In the Add New Scaffolded Item dialog:
- In the left pane, select Installed > Common > MVC.
- Select MVC Controller with views, using Entity Framework.
- Select Add.
Complete the Add MVC Controller with views, using Entity Framework dialog:
- In the Model class drop down, select Movie (MvcMovie.Models).
- In the Data context class row, select the + (plus) sign.
- In the Add Data Context dialog, the class name MvcMovie.Data.MvcMovieContext is generated.
- Select Add.
- In the Database provider drop down, select SQL Server.
- Views and Controller name: Keep the default.
- Select Add.
If you get an error message, select Add a second time to try it again.
Scaffolding adds the following packages:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design
Scaffolding creates the following:
- A movies controller:
Controllers/MoviesController.cs
- Razor view files for Create, Delete, Details, Edit, and Index pages:
Views/Movies/*.cshtml
- A database context class:
Data/MvcMovieContext.cs
Scaffolding updates the following:
- Inserts required package references in the
MvcMovie.csproj
project file. - Registers the database context in the
Program.cs
file. - Adds a database connection string to the
appsettings.json
file.
The automatic creation of these files and file updates is known as scaffolding.
The scaffolded pages can't be used yet because the database doesn't exist. Running the app and selecting the Movie App link results in a Cannot open database or no such table: Movie error message.
Build the app to verify that there are no errors.
Visual Studio Code
Open a command window in the project directory. The project directory is the directory that contains the Program.cs
and .csproj
files.
On macOS and Linux, export the scaffold tool path:
export PATH=$HOME/.dotnet/tools:$PATH
Run the following command:
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovie.Data.MvcMovieContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries --databaseProvider sqlite
[!INCLUDE explains scaffold generated params]
Scaffolding creates the following:
- A movies controller:
Controllers/MoviesController.cs
- Razor view files for Create, Delete, Details, Edit, and Index pages:
Views/Movies/*.cshtml
- A database context class:
Data/MvcMovieContext.cs
Scaffolding updates the following:
- Registers the database context in the
Program.cs
file - Adds a database connection string to the
appsettings.json
file.
The automatic creation of these files and file updates is known as scaffolding.
The scaffolded pages can't be used yet because the database doesn't exist. Running the app and selecting the Movie App link results in a Cannot open database or no such table: Movie error message.
Build the app to verify that there are no errors.
Use SQLite for development, SQL Server for production
The following highlighted code in Program.cs
shows how to use SQLite in development and SQL Server in production.
Initial migration
Use the EF Core Migrations feature to create the database. Migrations is a set of tools that create and update a database to match the data model.
Visual Studio
From the Tools menu, select NuGet Package Manager > Package Manager Console .
In the Package Manager Console (PMC), enter the following command:
Add-Migration InitialCreate
Add-Migration InitialCreate
: Generates aMigrations/{timestamp}_InitialCreate.cs
migration file. TheInitialCreate
argument is the migration name. Any name can be used, but by convention, a name is selected that describes the migration. Because this is the first migration, the generated class contains code to create the database schema. The database schema is based on the model specified in theMvcMovieContext
class.
The following warning is displayed, which is addressed in a later step:
No store type was specified for the decimal property '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 in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasPrecision', or configure a value converter using 'HasConversion'.
In the PMC, enter the following command:
Update-Database
Update-Database
: Updates the database to the latest migration, which the previous command created. This command runs theUp
method in theMigrations/{time-stamp}_InitialCreate.cs
file, which creates the database.
[!INCLUDE more information on the PMC tools for EF Core]
Visual Studio Code
Run the following .NET CLI command:
dotnet ef migrations add InitialCreate
ef migrations add InitialCreate
: Generates aMigrations/{timestamp}_InitialCreate.cs
migration file. TheInitialCreate
argument is the migration name. Any name can be used, but by convention, a name is selected that describes the migration. This is the first migration, so the generated class contains code to create the database schema. The database schema is based on the model specified in theMvcMovieContext
class, in theData/MvcMovieContext.cs
file.
The following warning is displayed, which is addressed in a later step:
No store type was specified for the decimal property '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 in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasPrecision', or configure a value converter using 'HasConversion'.
Run the following .NET CLI command:
dotnet ef database update
ef database update
: Updates the database to the latest migration, which the previous command created. This command runs theUp
method in theMigrations/{time-stamp}_InitialCreate.cs
file, which creates the database.
For more information on maintaining multiple providers such as Microsoft SQL Server and SQLite, see Migrations with Multiple Providers.
Test the app
Visual Studio
Run the app and select the Movie App link.
If you get an exception similar to the following, you may have missed the Update-Database
command in the migrations step:
SqlException: Cannot open database "MvcMovieContext-1" requested by the login. The login failed.
Visual Studio Code
Run the app and select the Movie App link.
If you get an exception similar to the following, you may have missed the dotnet ef database update
command in the migrations step:
SqliteException: SQLite Error 1: 'no such table: Movie'.
[!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.
Examine the generated database context class and registration
With EF Core, data access is performed using a model. A model is made up of entity classes and a context object that represents a session with the database. The context object allows querying and saving data. The database context is derived from Microsoft.EntityFrameworkCore.DbContext and specifies the entities to include in the data model.
Scaffolding creates the Data/MvcMovieContext.cs
database context class:
The preceding code creates a DbSet<Movie> property that represents the movies in the database.
Dependency injection
ASP.NET Core is built with dependency injection (DI). Services, such as the database context, are registered with DI in Program.cs
. These services are provided to components that require them via constructor parameters.
In the Controllers/MoviesController.cs
file, the constructor uses Dependency Injection to inject the MvcMovieContext
database context into the controller. The database context is used in each of the CRUD methods in the controller.
Scaffolding generated the following highlighted code in Program.cs
:
Visual Studio
Visual Studio Code
The ASP.NET Core configuration system reads the "MvcMovieContext" database connection string.
Examine the generated database connection string
Scaffolding added a connection string to the appsettings.json
file:
Visual Studio
Visual Studio Code
For local development, the ASP.NET Core configuration system reads the ConnectionString
key from the appsettings.json
file.
The InitialCreate
class
Examine the Migrations/{timestamp}_InitialCreate.cs
migration file:
In the preceding code:
InitialCreate.Up
creates the Movie table and configuresId
as the primary key.InitialCreate.Down
reverts the schema changes made by theUp
migration.
Dependency injection in the controller
Open the Controllers/MoviesController.cs
file and examine the constructor:
The constructor uses Dependency Injection to inject the database context (MvcMovieContext
) into the controller. The database context is used in each of the CRUD methods in the controller.
Test the Create page. Enter and submit data.
Test the Edit, Details, and Delete pages.
Strongly typed models and the @model
directive
Earlier in this tutorial, you saw how a controller can pass data or objects to a view using the ViewData
dictionary. The ViewData
dictionary is a dynamic object that provides a convenient late-bound way to pass information to a view.
MVC provides the ability to pass strongly typed model objects to a view. This strongly typed approach enables compile time code checking. The scaffolding mechanism passed a strongly typed model in the MoviesController
class and views.
Examine the generated Details
method in the Controllers/MoviesController.cs
file:
The id
parameter is generally passed as route data. For example, https://localhost:5001/movies/details/1
sets:
- The controller to the
movies
controller, the first URL segment. - The action to
details
, the second URL segment. - The
id
to 1, the last URL segment.
The id
can be passed in with a query string, as in the following example:
https://localhost:5001/movies/details?id=1
The id
parameter is defined as a nullable type (int?
) in cases when the id
value isn't provided.
A lambda expression is passed in to the xref:System.Data.Entity.QueryableExtensions.FirstOrDefaultAsync%2A method to select movie entities that match the route data or query string value.
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
If a movie is found, an instance of the Movie
model is passed to the Details
view:
return View(movie);
Examine the contents of the Views/Movies/Details.cshtml
file:
The @model
statement at the top of the view file specifies the type of object that the view expects. When the movie controller was created, the following @model
statement was included:
@model MvcMovie.Models.Movie
This @model
directive allows access to the movie that the controller passed to the view. The Model
object is strongly typed. For example, in the Details.cshtml
view, the code passes each movie field to the DisplayNameFor
and DisplayFor
HTML Helpers with the strongly typed Model
object. The Create
and Edit
methods and views also pass a Movie
model object.
Examine the Index.cshtml
view and the Index
method in the Movies controller. Notice how the code creates a List
object when it calls the View
method. The code passes this Movies
list from the Index
action method to the view:
The code returns problem details if the Movie
property of the data context is null.
When the movies controller was created, scaffolding included the following @model
statement at the top of the Index.cshtml
file:
The @model
directive allows access to the list of movies that the controller passed to the view by using a Model
object that's strongly typed. For example, in the Index.cshtml
view, the code loops through the movies with a foreach
statement over the strongly typed Model
object:
Because the Model
object is strongly typed as an IEnumerable<Movie>
object, each item in the loop is typed as Movie
. Among other benefits, the compiler validates the types used in the code.
Additional resources
[!div class="step-by-step"] Previous: Adding a View Next: Working with SQL
:::moniker-end