Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
By Rick Anderson and Jon P Smith.
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.
Right-click the Models folder > Add > Class. Name the file Movie.cs
.
Update the Models/Movie.cs
file with the following code:
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.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 an Id
field, which is required by the database for the primary key.
The DataType attribute on ReleaseDate
specifies the type of the data (Date
). With this attribute:
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.
Visual Studio automatically installs the required packages.
Build the project as a check for compiler errors.
Use the scaffolding tool to produce Create
, Read
, Update
, and Delete
(CRUD) pages for the movie model.
In Solution Explorer, right-click the Controllers folder and select Add > New Scaffolded Item.
In the Add New Scaffolded Item dialog:
Complete the Add MVC Controller with views, using Entity Framework dialog:
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:
Controllers/MoviesController.cs
Views/Movies/*.cshtml
Data/MvcMovieContext.cs
Scaffolding updates the following:
MvcMovie.csproj
project file.Program.cs
file.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 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.
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 a Migrations/{timestamp}_InitialCreate.cs
migration file. The InitialCreate
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 the MvcMovieContext
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 the Up
method in the Migrations/{time-stamp}_InitialCreate.cs
file, which creates the database.For more information on the PMC tools for EF Core, see EF Core tools reference - PMC in 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.
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.
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:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
public DbSet<MvcMovie.Models.Movie> Movie { get; set; } = default!;
}
}
The preceding code creates a DbSet<Movie> property that represents the movies in the database.
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
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovieContext") ?? throw new InvalidOperationException("Connection string 'MvcMovieContext' not found.")));
The ASP.NET Core configuration system reads the "MvcMovieContext" database connection string.
Scaffolding added a connection string to the appsettings.json
file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovieContext-4ebefa10-de29-4dea-b2ad-8a8dc6bcf374;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
For local development, the ASP.NET Core configuration system reads the ConnectionString
key from the appsettings.json
file.
Examine the Migrations/{timestamp}_InitialCreate.cs
migration file:
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MvcMovie.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Movie");
}
}
}
In the preceding code:
InitialCreate.Up
creates the Movie table and configures Id
as the primary key.InitialCreate.Down
reverts the schema changes made by the Up
migration.Open the Controllers/MoviesController.cs
file and examine the constructor:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
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.
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:
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
The id
parameter is generally passed as route data. For example, https://localhost:5001/movies/details/1
sets:
movies
controller, the first URL segment.details
, the second URL segment.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 FirstOrDefaultAsync 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:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
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:
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
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:
@model IEnumerable<MvcMovie.Models.Movie>
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:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
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.
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.
Right-click the Models folder > Add > Class. Name the file Movie.cs
.
Update the Models/Movie.cs
file with the following code:
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.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 an Id
field, which is required by the database for the primary key.
The DataType attribute on ReleaseDate
specifies the type of the data (Date
). With this attribute:
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.
Visual Studio automatically installs the required packages.
Build the project as a check for compiler errors.
Use the scaffolding tool to produce Create
, Read
, Update
, and Delete
(CRUD) pages for the movie model.
In Solution Explorer, right-click the Controllers folder and select Add > New Scaffolded Item.
In the Add New Scaffolded Item dialog:
Complete the Add MVC Controller with views, using Entity Framework dialog:
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:
Controllers/MoviesController.cs
Views/Movies/*.cshtml
Data/MvcMovieContext.cs
Scaffolding updates the following:
MvcMovie.csproj
project file.Program.cs
file.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 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.
From the Tools menu, select NuGet Package Manager > Package Manager Console .
In the Package Manager Console (PMC), enter the following commands:
Add-Migration InitialCreate
Update-Database
Add-Migration InitialCreate
: Generates a Migrations/{timestamp}_InitialCreate.cs
migration file. The InitialCreate
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 the MvcMovieContext
class.
Update-Database
: Updates the database to the latest migration, which the previous command created. This command runs the Up
method in the Migrations/{time-stamp}_InitialCreate.cs
file, which creates the database.
The Update-Database
command generates the following warning:
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'.
Ignore the preceding warning, it's fixed in a later tutorial.
For more information on the PMC tools for EF Core, see EF Core tools reference - PMC in 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.
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.
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:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
public DbSet<MvcMovie.Models.Movie> Movie { get; set; }
}
}
The preceding code creates a DbSet<Movie> property that represents the movies in the database.
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
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovieContext")));
The ASP.NET Core configuration system reads the "MvcMovieContext" database connection string.
Scaffolding added a connection string to the appsettings.json
file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-bd1c-3f753a804ce1.db"
}
}
For local development, the ASP.NET Core configuration system reads the ConnectionString
key from the appsettings.json
file.
Examine the Migrations/{timestamp}_InitialCreate.cs
migration file:
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Movie");
}
}
}
In the preceding code:
InitialCreate.Up
creates the Movie table and configures Id
as the primary key.InitialCreate.Down
reverts the schema changes made by the Up
migration.Open the Controllers/MoviesController.cs
file and examine the constructor:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
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.
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:
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
The id
parameter is generally passed as route data. For example, https://localhost:5001/movies/details/1
sets:
movies
controller, the first URL segment.details
, the second URL segment.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 FirstOrDefaultAsync 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:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
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:
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
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:
@model IEnumerable<MvcMovie.Models.Movie>
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:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
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.
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.
Right-click the Models folder > Add > Class. Name the file Movie.cs
.
Update the Models/Movie.cs
file with the following code:
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.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 an Id
field, which is required by the database for the primary key.
The DataType attribute on ReleaseDate
specifies the type of the data (Date
). With this attribute:
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.
Visual Studio automatically installs the required packages.
Build the project as a check for compiler errors.
Use the scaffolding tool to produce Create
, Read
, Update
, and Delete
(CRUD) pages for the movie model.
In Solution Explorer, right-click the Controllers folder and select Add > New Scaffolded Item.
In the Add New Scaffolded Item dialog:
Complete the Add MVC Controller with views, using Entity Framework dialog:
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:
Controllers/MoviesController.cs
Views/Movies/*.cshtml
Data/MvcMovieContext.cs
Scaffolding updates the following:
MvcMovie.csproj
project file.Program.cs
file.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 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.
From the Tools menu, select NuGet Package Manager > Package Manager Console .
In the Package Manager Console (PMC), enter the following commands:
Add-Migration InitialCreate
Update-Database
Add-Migration InitialCreate
: Generates a Migrations/{timestamp}_InitialCreate.cs
migration file. The InitialCreate
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 the MvcMovieContext
class.
Update-Database
: Updates the database to the latest migration, which the previous command created. This command runs the Up
method in the Migrations/{time-stamp}_InitialCreate.cs
file, which creates the database.
The Update-Database
command generates 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 preceding warning, it's fixed in a later tutorial.
For more information on the PMC tools for EF Core, see EF Core tools reference - PMC in 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.
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.
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:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
public DbSet<MvcMovie.Models.Movie> Movie { get; set; }
}
}
The preceding code creates a DbSet<Movie> property that represents the movies in the database.
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
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovieContext")));
The ASP.NET Core configuration system reads the "MvcMovieContext" database connection string.
Scaffolding added a connection string to the appsettings.json
file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-bd1c-3f753a804ce1.db"
}
}
For local development, the ASP.NET Core configuration system reads the ConnectionString
key from the appsettings.json
file.
Examine the Migrations/{timestamp}_InitialCreate.cs
migration file:
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Movie");
}
}
}
In the preceding code:
InitialCreate.Up
creates the Movie table and configures Id
as the primary key.InitialCreate.Down
reverts the schema changes made by the Up
migration.Open the Controllers/MoviesController.cs
file and examine the constructor:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
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.
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:
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
The id
parameter is generally passed as route data. For example, https://localhost:5001/movies/details/1
sets:
movies
controller, the first URL segment.details
, the second URL segment.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 FirstOrDefaultAsync 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:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
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:
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
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:
@model IEnumerable<MvcMovie.Models.Movie>
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:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
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.
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.
Right-click the Models folder > Add > Class. Name the file Movie.cs
.
Update the Models/Movie.cs
file with the following code:
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.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 an Id
field, which is required by the database for the primary key.
The DataType attribute on ReleaseDate
specifies the type of the data (Date
). With this attribute:
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.
From the Tools menu, select NuGet Package Manager > Package Manager Console (PMC).
In the PMC, run the following command:
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer
The preceding commands add:
Build the project as a check for compiler errors.
Use the scaffolding tool to produce Create
, Read
, Update
, and Delete
(CRUD) pages for the movie model.
In Solution Explorer, right-click the Controllers folder and select Add > New Scaffolded Item.
In the Add Scaffold dialog, select MVC Controller with views, using Entity Framework > Add.
Complete the Add MVC Controller with views, using Entity Framework dialog:
If you get an error message, select Add a second time to try it again.
Scaffolding updates the following:
MvcMovie.csproj
project file.Program.cs
file.appsettings.json
file.Scaffolding creates the following:
Controllers/MoviesController.cs
Views/Movies/*.cshtml
Data/MvcMovieContext.cs
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. The compiler generates several warnings about how null
values are handled. See this GitHub issue and Nullable reference types for more information.
To eliminate the warnings from nullable reference types, remove the following line from the MvcMovie.csproj
file:
<Nullable>enable</Nullable>
We hope to fix this issue in the next release.
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.
From the Tools menu, select NuGet Package Manager > Package Manager Console .
In the Package Manager Console (PMC), enter the following commands:
Add-Migration InitialCreate
Update-Database
Add-Migration InitialCreate
: Generates a Migrations/{timestamp}_InitialCreate.cs
migration file. The InitialCreate
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 the MvcMovieContext
class.
Update-Database
: Updates the database to the latest migration, which the previous command created. This command runs the Up
method in the Migrations/{time-stamp}_InitialCreate.cs
file, which creates the database.
The Update-Database
command generates 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 preceding warning, it's fixed in a later tutorial.
For more information on the PMC tools for EF Core, see EF Core tools reference - PMC in 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 migrations step:
SqlException: Cannot open database "MvcMovieContext-1" requested by the login. The login failed.
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.
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:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
public DbSet<MvcMovie.Models.Movie> Movie { get; set; }
}
}
The preceding code creates a DbSet<Movie> property that represents the movies in the database.
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
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovieContext")));
The ASP.NET Core configuration system reads the "MvcMovieContext" database connection string.
Scaffolding added a connection string to the appsettings.json
file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovieContext-7dc5;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
For local development, the ASP.NET Core configuration system reads the ConnectionString
key from the appsettings.json
file.
Examine the Migrations/{timestamp}_InitialCreate.cs
migration file:
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Movie");
}
}
}
In the preceding code:
InitialCreate.Up
creates the Movie table and configures Id
as the primary key.InitialCreate.Down
reverts the schema changes made by the Up
migration.Open the Controllers/MoviesController.cs
file and examine the constructor:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
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.
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:
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
The id
parameter is generally passed as route data. For example, https://localhost:5001/movies/details/1
sets:
movies
controller, the first URL segment.details
, the second URL segment.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 FirstOrDefaultAsync 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:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
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:
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
When the movies controller was created, scaffolding included the following @model
statement at the top of the Index.cshtml
file:
@model IEnumerable<MvcMovie.Models.Movie>
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:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
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.
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.
Right-click the Models folder > Add > Class. Name the file Movie.cs
.
Update the Models/Movie.cs
file with the following code:
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.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 an Id
field, which is required by the database for the primary key.
The DataType attribute on ReleaseDate
specifies the type of the data (Date
). With this attribute:
DataAnnotations are covered in a later tutorial.
From the Tools menu, select NuGet Package Manager > Package Manager Console (PMC).
In the PMC, run the following command:
Install-Package Microsoft.EntityFrameworkCore.Design
The preceding commands add:
Build the project as a check for compiler errors.
Use the scaffolding tool to produce Create
, Read
, Update
, and Delete
(CRUD) pages for the movie model.
In Solution Explorer, right-click the Controllers folder and select Add > New Scaffolded Item.
In the Add Scaffold dialog, select MVC Controller with views, using Entity Framework > Add.
Complete the Add MVC Controller with views, using Entity Framework dialog:
Scaffolding updates the following:
MvcMovie.csproj
project file.Startup.ConfigureServices
of the Startup.cs
file.appsettings.json
file.Scaffolding creates the following:
Controllers/MoviesController.cs
Views/Movies/*.cshtml
Data/MvcMovieContext.cs
The automatic creation of these files and file updates are 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.
Use the EF Core Migrations feature to create the database. Migrations are a set of tools that create and update a database to match the data model.
From the Tools menu, select NuGet Package Manager > Package Manager Console .
In the Package Manager Console (PMC), enter the following commands:
Add-Migration InitialCreate
Update-Database
Add-Migration InitialCreate
: Generates a Migrations/{timestamp}_InitialCreate.cs
migration file. The InitialCreate
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 the MvcMovieContext
class.
Update-Database
: Updates the database to the latest migration, which the previous command created. This command runs the Up
method in the Migrations/{time-stamp}_InitialCreate.cs
file, which creates the database.
The Update-Database
command generates 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 preceding warning, it's fixed in a later tutorial.
For more information on the PMC tools for EF Core, see EF Core tools reference - PMC in 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 migrations step:
SqlException: Cannot open database "MvcMovieContext-1" requested by the login. The login failed.
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.
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:
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
public DbSet<Movie> Movie { get; set; }
}
}
The preceding code creates a DbSet<Movie> property that represents the movies in the database.
ASP.NET Core is built with dependency injection (DI). Services, such as the database context, must be registered with DI in Startup
. Components that require these services are provided 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 Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}
The ASP.NET Core configuration system reads the "MvcMovieContext" database connection string.
Scaffolding added a connection string to the appsettings.json
file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovieContext-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
For local development, the ASP.NET Core configuration system reads the ConnectionString
key from the appsettings.json
file.
Examine the Migrations/{timestamp}_InitialCreate.cs
migration file:
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Movie");
}
}
In the preceding code:
InitialCreate.Up
creates the Movie table and configures Id
as the primary key.InitialCreate.Down
reverts the schema changes made by the Up
migration.Open the Controllers/MoviesController.cs
file and examine the constructor:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
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.
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:
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
The id
parameter is generally passed as route data. For example, https://localhost:5001/movies/details/1
sets:
movies
controller, the first URL segment.details
, the second URL segment.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 FirstOrDefaultAsync 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:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
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:
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
When the movies controller was created, scaffolding included the following @model
statement at the top of the Index.cshtml
file:
@model IEnumerable<MvcMovie.Models.Movie>
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:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
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.
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.
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.
Right-click the Models folder > Add > Class. Name the file Movie.cs
.
Update the Movie.cs
file with the following code:
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.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 an Id
field, which is required by the database for the primary key.
The DataType attribute on ReleaseDate
specifies the type of the data (Date
). With this attribute:
DataAnnotations are covered in a later tutorial.
From the Tools menu, select NuGet Package Manager > Package Manager Console (PMC).
In the PMC, run the following command:
Install-Package Microsoft.EntityFrameworkCore.SqlServer
The preceding command adds the EF Core SQL Server provider. The provider package installs the EF Core package as a dependency. Additional packages are installed automatically in the scaffolding step later in the tutorial.
A database context class is needed to coordinate EF Core functionality (Create, Read, Update, Delete) for the Movie
model. The database context is derived from Microsoft.EntityFrameworkCore.DbContext and specifies the entities to include in the data model.
Create a Data folder.
Add a Data/MvcMovieContext.cs
file with the following code:
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
public DbSet<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.
ASP.NET Core is built with dependency injection (DI). Services (such as the EF Core DB context) must be registered with DI during application startup. Components that require these services (such as Razor Pages) are provided via constructor parameters. The constructor code that gets a DB context instance is shown later in the tutorial. In this section, you register the database context with the DI container.
Add the following using
statements at the top of Startup.cs
:
using MvcMovie.Data;
using Microsoft.EntityFrameworkCore;
Add the following highlighted code in Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}
The name of the connection string is passed in to the context by calling a method on a DbContextOptions object. For local development, the ASP.NET Core configuration system reads the connection string from the appsettings.json
file.
Add a connection string to the appsettings.json
file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovieContext-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Build the project as a check for compiler errors.
Use the scaffolding tool to produce Create, Read, Update, and Delete (CRUD) pages for the movie model.
In Solution Explorer, right-click the Controllers folder > Add > New Scaffolded Item.
In the Add Scaffold dialog, select MVC Controller with views, using Entity Framework > Add.
Complete the Add Controller dialog:
Visual Studio creates:
Controllers/MoviesController.cs
)The automatic creation of these files is known as scaffolding.
You can't use the scaffolded pages yet because the database doesn't exist. If you run the app and click on the Movie App link, you get a Cannot open database or no such table: Movie error message.
Use the EF Core Migrations feature to create the database. Migrations is a set of tools that let you create and update a database to match your data model.
From the Tools menu, select NuGet Package Manager > Package Manager Console (PMC).
In the PMC, enter the following commands:
Add-Migration InitialCreate
Update-Database
Add-Migration InitialCreate
: Generates a Migrations/{timestamp}_InitialCreate.cs
migration file. The InitialCreate
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 the MvcMovieContext
class.
Update-Database
: Updates the database to the latest migration, which the previous command created. This command runs the Up
method in the Migrations/{time-stamp}_InitialCreate.cs
file, which creates the database.
The database update command generates 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()'.
You can ignore that warning, it will be fixed in a later tutorial.
For more information on the PMC tools for EF Core, see EF Core tools reference - PMC in Visual Studio.
Examine the Migrations/{timestamp}_InitialCreate.cs
migration file:
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
Title = table.Column<string>(nullable: true),
ReleaseDate = table.Column<DateTime>(nullable: false),
Genre = table.Column<string>(nullable: true),
Price = table.Column<decimal>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Movie");
}
}
The Up
method creates the Movie table and configures Id
as the primary key. The Down
method reverts the schema changes made by the Up
migration.
Run the app and click the Movie App link.
If you get an exception similar to one of the following:
SqlException: Cannot open database "MvcMovieContext-1" requested by the login. The login failed.
You probably missed the migrations step.
Test the Create page. Enter and submit data.
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 pages.
Open the Controllers/MoviesController.cs
file and examine the constructor:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
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.
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 also provides the ability to pass strongly typed model objects to a view. This strongly typed approach enables compile time code checking. The scaffolding mechanism used this approach (that is, passing a strongly typed model) with the MoviesController
class and views.
Examine the generated Details
method in the Controllers/MoviesController.cs
file:
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
The id
parameter is generally passed as route data. For example https://localhost:5001/movies/details/1
sets:
movies
controller (the first URL segment).details
(the second URL segment).You can also pass in the id
with a query string as follows:
https://localhost:5001/movies/details?id=1
The id
parameter is defined as a nullable type (int?
) in case an ID value isn't provided.
A lambda expression is passed in to FirstOrDefaultAsync
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:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
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:
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
When the movies controller was created, scaffolding included the following @model
statement at the top of the Index.cshtml
file:
@model IEnumerable<MvcMovie.Models.Movie>
The @model
directive allows you to access 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:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Because the Model
object is strongly typed (as an IEnumerable<Movie>
object), each item in the loop is typed as Movie
. Among other benefits, this means that you get compile time checking of the code.
ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreTraining
Module
Create a web UI with ASP.NET Core - Training
Learn how to create web pages using Razor with ASP.NET Core.
Documentation
Part 5, work with a database in an ASP.NET Core MVC app
Part 5 of tutorial series on ASP.NET Core MVC.
Part 3, add a view to an ASP.NET Core MVC app
Part 3 of tutorial series on ASP.NET Core MVC.
Part 6, controller methods and views in ASP.NET Core
Part 6, add a model to an ASP.NET Core MVC app