diff --git a/aspnetcore/data/ef-mvc/advanced.md b/aspnetcore/data/ef-mvc/advanced.md
index 5af9aede68d7..8944e9651b90 100644
--- a/aspnetcore/data/ef-mvc/advanced.md
+++ b/aspnetcore/data/ef-mvc/advanced.md
@@ -34,7 +34,7 @@ In this tutorial, you:
One of the advantages of using the Entity Framework is that it avoids tying your code too closely to a particular method of storing data. It does this by generating SQL queries and commands for you, which also frees you from having to write them yourself. But there are exceptional scenarios when you need to run specific SQL queries that you have manually created. For these scenarios, the Entity Framework Code First API includes methods that enable you to pass SQL commands directly to the database. You have the following options in EF Core 1.0:
-* Use the `DbSet.FromSql` method for queries that return entity types. The returned objects must be of the type expected by the `DbSet` object, and they're automatically tracked by the database context unless you [turn tracking off](crud.md#no-tracking-queries).
+* Use the `DbSet.FromSql` method for queries that return entity types. The returned objects must be of the type expected by the `DbSet` object, and they're automatically tracked by the database context unless you [turn tracking off](crud.md#disable-tracking-of-entity-objects-no-tracking-queries).
* Use the `Database.ExecuteSqlCommand` for non-query commands.
diff --git a/aspnetcore/data/ef-mvc/crud.md b/aspnetcore/data/ef-mvc/crud.md
index 7708a3fd9dc6..14433f6189ae 100644
--- a/aspnetcore/data/ef-mvc/crud.md
+++ b/aspnetcore/data/ef-mvc/crud.md
@@ -1,127 +1,131 @@
---
-title: "Tutorial: Implement CRUD Functionality - ASP.NET MVC with EF Core"
-description: "In this tutorial, you'll review and customize the CRUD (create, read, update, delete) code that the MVC scaffolding automatically creates for you in controllers and views."
+title: Implement CRUD - ASP.NET MVC with EF Core
+description: Review and customize the autogenerated CRUD (create, read, update, delete) code produced by the MVC scaffolding in your controllers and views. This article is part of a tutorial series.
author: tdykstra
ms.author: tdykstra
ms.custom: mvc
-ms.date: 02/04/2019
+ms.date: 04/16/2026
ms.topic: tutorial
uid: data/ef-mvc/crud
+
+# customer intent: As an ASP.NET developer, I want to make changes to the auto-generated CRUD code, so I can customize my controllers and views.
---
-# Tutorial: Implement CRUD Functionality - ASP.NET MVC with EF Core
+# Tutorial: Implement basic CRUD functionality - ASP.NET MVC with EF Core
-In the previous tutorial, you created an MVC application that stores and displays data using the Entity Framework and SQL Server LocalDB. In this tutorial, you'll review and customize the CRUD (create, read, update, delete) code that the MVC scaffolding automatically creates for you in controllers and views.
+In the previous tutorial, you created an MVC application that stores and displays data by using the [Entity Framework (EF) Core](/ef/core/) and a SQL Server local database. In this exercise, you review and customize the CRUD (create, read, update, delete) code that the MVC scaffolding automatically creates for you in controllers and views.
> [!NOTE]
-> It's a common practice to implement the repository pattern in order to create an abstraction layer between your controller and the data access layer. To keep these tutorials simple and focused on teaching how to use the Entity Framework itself, they don't use repositories. For information about repositories with EF, see [the last tutorial in this series](advanced.md).
+> It's a common practice to implement the repository pattern in order to create an abstraction layer between your controller and the data access layer. To keep the examples simple and focused on demonstrating how to use the Entity Framework itself, the tutorials don't use repositories. For information about repositories with Entity Framework, see [the last tutorial in this series](advanced.md).
In this tutorial, you:
> [!div class="checklist"]
-> * Customize the Details page
-> * Update the Create page
-> * Update the Edit page
-> * Update the Delete page
+> * Customize pages: **Details**, **Create**, **Edit**, and **Delete**
+> * Explore how to protect against overposting
+> * Use different approaches for HttpPost `Edit` code and `Delete`
+> * Work with no-tracking queries
> * Close database connections
## Prerequisites
-* [Get started with EF Core and ASP.NET Core MVC](intro.md)
+* Complete the previous tutorial, [Get started with EF Core in an ASP.NET MVC web app](intro.md).
## Customize the Details page
-The scaffolded code for the Students Index page left out the `Enrollments` property, because that property holds a collection. In the **Details** page, you'll display the contents of the collection in an HTML table.
+The scaffolded code for the **Students Index** page left out the `Enrollments` property because the property holds a collection. The **Details** page displays the contents of the collection as an HTML table.
-In `Controllers/StudentsController.cs`, the action method for the Details view uses the `FirstOrDefaultAsync` method to retrieve a single `Student` entity. Add code that calls `Include`. `ThenInclude`, and `AsNoTracking` methods, as shown in the following highlighted code.
+In the _Controllers/StudentsController.cs_ file, the action method for the Details view uses the `FirstOrDefaultAsync` method to retrieve a single `Student` entity. You need to add code that calls the `Include`, `ThenInclude`, and `AsNoTracking` methods, as shown in the following highlighted code.
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_Details&highlight=8-12)]
-The `Include` and `ThenInclude` methods cause the context to load the `Student.Enrollments` navigation property, and within each enrollment the `Enrollment.Course` navigation property. You'll learn more about these methods in the [read related data](read-related-data.md) tutorial.
+The `Include` and `ThenInclude` methods cause the context to load the `Student.Enrollments` navigation property and the `Enrollment.Course` navigation property within each enrollment. You learn more about these methods in the [read related data](read-related-data.md) tutorial.
-The `AsNoTracking` method improves performance in scenarios where the entities returned won't be updated in the current context's lifetime. You'll learn more about `AsNoTracking` at the end of this tutorial.
+The `AsNoTracking` method improves performance in scenarios where the returned entities aren't updated in the current context's lifetime. You learn more about the `AsNoTracking` method at the end of this tutorial.
-### Route data
+### Configure the route data
-The key value that's passed to the `Details` method comes from *route data*. Route data is data that the model binder found in a segment of the URL. For example, the default route specifies controller, action, and id segments:
+The key value passed to the `Details` method comes from the *route data*. Route data is data that the model binder finds in a segment of the URL. For example, the default route specifies `controller`, `action`, and `id` (identifier, ID) segments:
[!code-csharp[](intro/samples/5cu/Startup.cs?name=snippet2)]
-In the following URL, the default route maps Instructor as the controller, Index as the action, and 1 as the id; these are route data values.
+In the following URL, the default route maps _Instructor_ as the `controller`, _Index_ as the `action`, and 1 as the `id`. These values compose the route data.
-```
+```url
http://localhost:1230/Instructor/Index/1?courseID=2021
```
-The last part of the URL ("?courseID=2021") is a query string value. The model binder will also pass the ID value to the `Index` method `id` parameter if you pass it as a query string value:
+The last part of the URL (`?courseID=2021`) is a query string value. The model binder also passes the ID value to the `Index` method `id` parameter, if you pass it as a query string value:
-```
+```url
http://localhost:1230/Instructor/Index?id=1&CourseID=2021
```
-In the Index page, hyperlink URLs are created by tag helper statements in the Razor view. In the following Razor code, the `id` parameter matches the default route, so `id` is added to the route data.
+In the **Index** page, hyperlink URLs are created by tag helper statements in the Razor view. In the following Razor code, the `id` parameter matches the default route, so the `id` value is added to the route data.
```html
Edit
```
-This generates the following HTML when `item.ID` is 6:
+This change generates the following HTML when the `item.ID` value is 6:
```html
Edit
```
-In the following Razor code, `studentID` doesn't match a parameter in the default route, so it's added as a query string.
+In the following Razor code, the `studentID` text doesn't match a parameter in the default route, so the text is added as a query string.
```html
Edit
```
-This generates the following HTML when `item.ID` is 6:
+This change generates the following HTML when the `item.ID` value is 6:
```html
Edit
```
-For more information about tag helpers, see .
+For more information about tag helpers, see [Tag Helpers in ASP.NET Core](../../mvc/views/tag-helpers/intro.md).
### Add enrollments to the Details view
-Open `Views/Students/Details.cshtml`. Each field is displayed using `DisplayNameFor` and `DisplayFor` helpers, as shown in the following example:
+Open the _Views/Students/Details.cshtml_ file. Each field is displayed by using the `DisplayNameFor` and `DisplayFor` helpers, as shown in the following example.
[!code-cshtml[](intro/samples/cu/Views/Students/Details.cshtml?range=13-18&highlight=2,5)]
-After the last field and immediately before the closing `` tag, add the following code to display a list of enrollments:
+After the last field, and immediately before the closing `` tag, add the following code to display a list of enrollments:
[!code-cshtml[](intro/samples/cu/Views/Students/Details.cshtml?range=31-52)]
-If code indentation is wrong after you paste the code, press CTRL-K-D to correct it.
+If the code indentation is misaligned after you paste the snippet, use the **CTRL**+**K**+**D** keyboard shortcut to correct it.
-This code loops through the entities in the `Enrollments` navigation property. For each enrollment, it displays the course title and the grade. The course title is retrieved from the Course entity that's stored in the `Course` navigation property of the Enrollments entity.
+The new code loops through the entities in the `Enrollments` navigation property. For each enrollment, the code displays the course title and the grade. The course title is retrieved from the `Course` entity stored in the `Course` navigation property of the `Enrollments` entity.
-Run the app, select the **Students** tab, and click the **Details** link for a student. You see the list of courses and grades for the selected student:
+Run the app, select the **Students** tab, and select the **Details** link for a student. You see the list of courses and grades for the selected student:
-
+:::image type="content" source="crud/_static/student-details.png" border="false" alt-text="Screenshot of the Student Details page.":::
## Update the Create page
-In `StudentsController.cs`, modify the HttpPost `Create` method by adding a try-catch block and removing ID from the `Bind` attribute.
+In the _StudentsController.cs_ file, modify the HttpPost `Create` method by adding a `try-catch` block and removing the `ID` property from the `Bind` attribute.
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_Create&highlight=4,6-7,14-21)]
-This code adds the Student entity created by the ASP.NET Core MVC model binder to the Students entity set and then saves the changes to the database. (Model binder refers to the ASP.NET Core MVC functionality that makes it easier for you to work with data submitted by a form; a model binder converts posted form values to CLR types and passes them to the action method in parameters. In this case, the model binder instantiates a Student entity for you using property values from the Form collection.)
+This code adds the `Student` entity created by the ASP.NET Core MVC model binder to the `Students` entity set, and then saves the changes to the database.
-You removed `ID` from the `Bind` attribute because ID is the primary key value which SQL Server will set automatically when the row is inserted. Input from the user doesn't set the ID value.
+_Model binder_ refers to the ASP.NET Core MVC functionality that makes it easier for you to work with data submitted by a form. A model binder converts posted form values to CLR types and passes them to the action method in parameters. In this case, the model binder instantiates a `Student` entity for you by using property values from the `Form` collection.
-Other than the `Bind` attribute, the try-catch block is the only change you've made to the scaffolded code. If an exception that derives from `DbUpdateException` is caught while the changes are being saved, a generic error message is displayed. `DbUpdateException` exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Although not implemented in this sample, a production quality application would log the exception. For more information, see the **Log for insight** section in [Monitoring and Telemetry (Building Real-World Cloud Apps with Azure)](/aspnet/aspnet/overview/developing-apps-with-windows-azure/building-real-world-cloud-apps-with-windows-azure/monitoring-and-telemetry).
+You removed the `ID` property from the `Bind` attribute because the ID is the primary key value that SQL Server sets automatically when it inserts the row. Input from the user doesn't set the ID value.
-The `ValidateAntiForgeryToken` attribute helps prevent cross-site request forgery (CSRF) attacks. The token is automatically injected into the view by the [FormTagHelper](xref:mvc/views/working-with-forms#the-form-tag-helper) and is included when the form is submitted by the user. The token is validated by the `ValidateAntiForgeryToken` attribute. For more information, see .
+Other than the `Bind` attribute, the `try-catch` block is the only change made to the scaffolded code. If an exception that derives from the `DbUpdateException` handler is caught while the changes are being saved, a generic error message is displayed. The cause for a `DbUpdateException` exception can be external to the application rather than a programming error, so the user is advised to try again. A production quality application logs such exception, but that functionality isn't implemented in this sample. For more information, see [Logging in .NET and ASP.NET Core](../../fundamentals/logging/index.md).
+
+The `ValidateAntiForgeryToken` attribute helps prevent cross-site request forgery (CSRF) attacks. The [Form Tag Helper](../../mvc/views/working-with-forms.md#the-form-tag-helper) method automatically injects the token into the view. The token is also included when the user submits the form. The `ValidateAntiForgeryToken` attribute checks the token. For more information, see t[Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core](../../security/anti-request-forgery.md) and [FormTagHelper Class](/dotnet/api/microsoft.aspnetcore.mvc.taghelpers.formtaghelper) reference.
-### Security note about overposting
+### Protect against overposting
-The `Bind` attribute that the scaffolded code includes on the `Create` method is one way to protect against overposting in create scenarios. For example, suppose the Student entity includes a `Secret` property that you don't want this web page to set.
+The `Bind` attribute that the scaffolded code includes on the `Create` method is one way to protect against overposting in create scenarios. For example, suppose the `Student` entity includes a `Secret` property that you don't want this web page to set.
```csharp
public class Student
@@ -134,174 +138,163 @@ public class Student
}
```
-Even if you don't have a `Secret` field on the web page, a hacker could use a tool such as Fiddler, or write some JavaScript, to post a `Secret` form value. Without the `Bind` attribute limiting the fields that the model binder uses when it creates a Student instance, the model binder would pick up that `Secret` form value and use it to create the Student entity instance. Then whatever value the hacker specified for the `Secret` form field would be updated in your database. The following image shows the Fiddler tool adding the `Secret` field (with the value "OverPost") to the posted form values.
+Even if you don't have a `Secret` field on the web page, a hacker might use a tool such as Fiddler, or write some JavaScript, to post a `Secret` form value. Without the `Bind` attribute limiting the fields that the model binder uses when it creates a `Student` instance, the model binder can pick up that `Secret` form value and use it to create the `Student` entity instance. Then, whatever value the hacker specified for the `Secret` form field is updated in your database. The following image shows the Fiddler tool adding the `Secret` field (with the value "OverPost") to the posted form values.
-
+:::image type="content" source="crud/_static/fiddler.png" border="false" alt-text="Screenshot of the Composer view in Fiddler showing the Secret field added in the request body.":::
-The value "OverPost" would then be successfully added to the `Secret` property of the inserted row, although you never intended that the web page be able to set that property.
+The value "OverPost" is then successfully added to the `Secret` property of the inserted row, although you never intended that the web page set the property.
-You can prevent overposting in edit scenarios by reading the entity from the database first and then calling `TryUpdateModel`, passing in an explicit allowed properties list. That's the method used in these tutorials.
+You can prevent overposting in edit scenarios by reading the entity from the database first and then calling the `TryUpdateModel` method. Pass in an explicit allowed properties list, which is how the method is used in these tutorials.
-An alternative way to prevent overposting that's preferred by many developers is to use view models rather than entity classes with model binding. Include only the properties you want to update in the view model. Once the MVC model binder has finished, copy the view model properties to the entity instance, optionally using a tool such as AutoMapper. Use `_context.Entry` on the entity instance to set its state to `Unchanged`, and then set `Property("PropertyName").IsModified` to true on each entity property that's included in the view model. This method works in both edit and create scenarios.
+An alternate way to prevent overposting preferred by many developers is to use view models rather than entity classes with model binding. Include only the properties you want to update in the view model. After the MVC model binder completes, copy the view model properties to the entity instance, optionally by using a tool such as AutoMapper. Use the `_context.Entry` setting on the entity instance to set its state to `Unchanged`, and then set the `Property("PropertyName").IsModified` setting to true on each entity property included in the view model. This method works in both edit and create scenarios.
### Test the Create page
-The code in `Views/Students/Create.cshtml` uses `label`, `input`, and `span` (for validation messages) tag helpers for each field.
+The code in the _Views/Students/Create.cshtml_ file uses the `label`, `input`, and `span` (for validation messages) tag helpers for each field.
-Run the app, select the **Students** tab, and click **Create New**.
+Run the app, select the **Students** tab, and then select **Create New**.
-Enter names and a date. Try entering an invalid date if your browser lets you do that. (Some browsers force you to use a date picker.) Then click **Create** to see the error message.
+Enter names and a date. Try entering an invalid date, as your browser allows. (Some browsers force you to use a date picker.) Select **Create** to see the error message.
-
+:::image type="content" source="crud/_static/date-error.png" border="false" alt-text="Screenshot of the Create page showing the validation error message for the invalid Date field value.":::
-This is server-side validation that you get by default; in a later tutorial you'll see how to add attributes that will generate code for client-side validation also. The following highlighted code shows the model validation check in the `Create` method.
+This scenario demonstrates the default server-side validation. In a later tutorial, you see how to add attributes that generate code for client-side validation. The following highlighted code shows the model validation check in the `Create` method.
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_Create&highlight=8)]
-Change the date to a valid value and click **Create** to see the new student appear in the **Index** page.
+Change the date to a valid value, and select **Create** to see the new student appear in the **Index** page.
## Update the Edit page
-In `StudentController.cs`, the HttpGet `Edit` method (the one without the `HttpPost` attribute) uses the `FirstOrDefaultAsync` method to retrieve the selected Student entity, as you saw in the `Details` method. You don't need to change this method.
+In the _StudentController.cs_ file, the HttpGet `Edit` method (the one without the `HttpPost` attribute) uses the `FirstOrDefaultAsync` method to retrieve the selected `Student` entity, as you reviewed earlier in the `Details` method. You don't need to change this method.
-### Recommended HttpPost Edit code: Read and update
+### Use recommended HttpPost Edit code: Read and update
-Replace the HttpPost Edit action method with the following code.
+Replace the HttpPost `Edit` method with the following code.
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_ReadFirst)]
-These changes implement a security best practice to prevent overposting. The scaffolder generated a `Bind` attribute and added the entity created by the model binder to the entity set with a `Modified` flag. That code isn't recommended for many scenarios because the `Bind` attribute clears out any pre-existing data in fields not listed in the `Include` parameter.
+These changes implement a security best practice to prevent overposting. The scaffolder generated a `Bind` attribute and added the entity created by the model binder to the entity set with a `Modified` flag. That code isn't recommended for many scenarios because the `Bind` attribute clears out any preexisting data in fields not listed in the `Include` parameter.
-The new code reads the existing entity and calls `TryUpdateModel` to update fields in the retrieved entity [based on user input in the posted form data](xref:mvc/models/model-binding). The Entity Framework's automatic change tracking sets the `Modified` flag on the fields that are changed by form input. When the `SaveChanges` method is called, the Entity Framework creates SQL statements to update the database row. Concurrency conflicts are ignored, and only the table columns that were updated by the user are updated in the database. (A later tutorial shows how to handle concurrency conflicts.)
+The new code reads the existing entity and calls the `TryUpdateModel` method to update fields in the retrieved entity based on user input in the posted form data. (For more information, see [Model Binding in ASP.NET Core](../../mvc/models/model-binding.md).) The Entity Framework's automatic change tracking sets the `Modified` flag on fields that the form input updates. When the `SaveChanges` method is called, the Entity Framework creates SQL statements to update the database row. Concurrency conflicts are ignored, and only the table columns updated by the user are also updated in the database. (A later tutorial shows how to handle concurrency conflicts.)
-As a best practice to prevent overposting, the fields that you want to be updateable by the **Edit** page are declared in the `TryUpdateModel` parameters. (The empty string preceding the list of fields in the parameter list is for a prefix to use with the form fields names.) Currently there are no extra fields that you're protecting, but listing the fields that you want the model binder to bind ensures that if you add fields to the data model in the future, they're automatically protected until you explicitly add them here.
+As a best practice to prevent overposting, the fields that you want to be updateable by the **Edit** page are declared in the `TryUpdateModel` parameters. (The empty string preceding the list of fields in the parameter list is for a prefix to use with the form fields names.) Currently, there are no extra fields that you're protecting. If you list the fields you want the model binder to bind, you can prevent overposting for fields added in the future. The fields in the list are automatically protected until you explicitly add them.
-As a result of these changes, the method signature of the HttpPost `Edit` method is the same as the HttpGet `Edit` method; therefore you've renamed the method `EditPost`.
+As a result of these changes, the method signature of the HttpPost `Edit` method is the same as the HttpGet `Edit` method. The changes basically rename the method to `EditPost`.
-### Alternative HttpPost Edit code: Create and attach
+### Use alternate HttpPost Edit code: Create and attach
-The recommended HttpPost edit code ensures that only changed columns get updated and preserves data in properties that you don't want included for model binding. However, the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts. An alternative is to attach an entity created by the model binder to the EF context and mark it as modified. (Don't update your project with this code, it's only shown to illustrate an optional approach.)
+The recommended HttpPost `Edit` code ensures that only changed columns are updated and preserves data in properties that you don't want included for model binding. However, the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts. An alternative is to attach an entity created by the model binder to the Entity Framework context and mark it as modified. (Don't update your project with the following code. The code illustrates an optional approach.)
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_CreateAndAttach)]
You can use this approach when the web page UI includes all of the fields in the entity and can update any of them.
-The scaffolded code uses the create-and-attach approach but only catches `DbUpdateConcurrencyException` exceptions and returns 404 error codes. The example shown catches any database update exception and displays an error message.
+The scaffolded code uses the _create-and-attach_ approach but only catches `DbUpdateConcurrencyException` exceptions and returns 404 error codes. The example shown catches any database update exception and displays an error message.
-### Entity States
+### Understand entity states
-The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database, and this information determines what happens when you call the `SaveChanges` method. For example, when you pass a new entity to the `Add` method, that entity's state is set to `Added`. Then when you call the `SaveChanges` method, the database context issues a SQL INSERT command.
+The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database. This information determines what happens when you call the `SaveChanges` method. For example, when you pass a new entity to the `Add` method, that entity's state is set to `Added`. When you call the `SaveChanges` method, the database context issues a SQL INSERT command.
-An entity may be in one of the following states:
+An entity can be in one of the following states:
-* `Added`. The entity doesn't yet exist in the database. The `SaveChanges` method issues an INSERT statement.
+* `Added`: The entity doesn't yet exist in the database. The `SaveChanges` method issues an `INSERT` statement.
-* `Unchanged`. Nothing needs to be done with this entity by the `SaveChanges` method. When you read an entity from the database, the entity starts out with this status.
+* `Unchanged`: Nothing needs to be done with this entity by the `SaveChanges` method. When you read an entity from the database, the entity starts out with this status.
-* `Modified`. Some or all of the entity's property values have been modified. The `SaveChanges` method issues an UPDATE statement.
+* `Modified`: Some or all of the entity's property values are modified. The `SaveChanges` method issues an `UPDATE` statement.
-* `Deleted`. The entity has been marked for deletion. The `SaveChanges` method issues a DELETE statement.
+* `Deleted`: The entity is marked for deletion. The `SaveChanges` method issues a `DELETE` statement.
-* `Detached`. The entity isn't being tracked by the database context.
+* `Detached`: The database context doesn't track the entity.
-In a desktop application, state changes are typically set automatically. You read an entity and make changes to some of its property values. This causes its entity state to automatically be changed to `Modified`. Then when you call `SaveChanges`, the Entity Framework generates a SQL UPDATE statement that updates only the actual properties that you changed.
+In a desktop application, state changes are typically set automatically. You read an entity and make changes to some of its property values. This behavior causes its entity state to automatically change to `Modified`. When you call the `SaveChanges` method, the Entity Framework generates a SQL `UPDATE` statement that updates only the actual properties that you changed.
-In a web app, the `DbContext` that initially reads an entity and displays its data to be edited is disposed after a page is rendered. When the HttpPost `Edit` action method is called, a new web request is made and you have a new instance of the `DbContext`. If you re-read the entity in that new context, you simulate desktop processing.
+In a web app, the `DbContext` object that initially reads an entity and displays its data to be edited is disposed after a page is rendered. When the HttpPost `Edit` action method is called, a new web request is made and you have a new instance of the `DbContext` class. If you re-read the entity in that new context, you simulate desktop processing.
-But if you don't want to do the extra read operation, you have to use the entity object created by the model binder. The simplest way to do this is to set the entity state to Modified as is done in the alternative HttpPost Edit code shown earlier. Then when you call `SaveChanges`, the Entity Framework updates all columns of the database row, because the context has no way to know which properties you changed.
+If you don't want to do the extra read operation, you have to use the entity object created by the model binder. The easiest approach is to set the entity state to `Modified` as is done in the alternative HttpPost `Edit` code shown earlier. When you call the `SaveChanges` method, the Entity Framework updates all columns of the database row because the context has no way to know which properties you changed.
-If you want to avoid the read-first approach, but you also want the SQL UPDATE statement to update only the fields that the user actually changed, the code is more complex. You have to save the original values in some way (such as by using hidden fields) so that they're available when the HttpPost `Edit` method is called. Then you can create a Student entity using the original values, call the `Attach` method with that original version of the entity, update the entity's values to the new values, and then call `SaveChanges`.
+If you want to avoid the _read-first_ approach, but you also want the SQL `UPDATE` statement to update only the fields the user changes, the code is more complex. You have to save the original values in some way, such as by using hidden fields. The values must be available when the HttpPost `Edit` method is called. You can create a `Student` entity by using the original values, call the `Attach` method with the original version of the entity, update the entity's values to the new values, and then call the `SaveChanges` method.
### Test the Edit page
-Run the app, select the **Students** tab, then click an **Edit** hyperlink.
+Run the app, select the **Students** tab, then select an **Edit** hyperlink.
-
+:::image type="content" source="crud/_static/student-edit.png" border="false" alt-text="Screenshot of the Edit page for a Student.":::
-Change some of the data and click **Save**. The **Index** page opens and you see the changed data.
+Change some of the data and select **Save**. The **Index** page opens and you see the changed data.
## Update the Delete page
-In `StudentController.cs`, the template code for the HttpGet `Delete` method uses the `FirstOrDefaultAsync` method to retrieve the selected Student entity, as you saw in the Details and Edit methods. However, to implement a custom error message when the call to `SaveChanges` fails, you'll add some functionality to this method and its corresponding view.
+In the _StudentController.cs_ file, the template code for the HttpGet `Delete` method uses the `FirstOrDefaultAsync` method to retrieve the selected `Student` entity, as you saw in the `Details` and `Edit` methods. However, to implement a custom error message when the call to the `SaveChanges` method fails, you need to add some functionality to this method and its corresponding view.
-As you saw for update and create operations, delete operations require two action methods. The method that's called in response to a GET request displays a view that gives the user a chance to approve or cancel the delete operation. If the user approves it, a POST request is created. When that happens, the HttpPost `Delete` method is called and then that method actually performs the delete operation.
+As you saw for the update and create operations, delete operations require two action methods. The method called in response to a GET request displays a view that gives the user a chance to approve or cancel the delete operation. If the user approves, a POST request is created. The HttpPost `Delete` method is then called and this method actually performs the delete operation.
-You'll add a try-catch block to the HttpPost `Delete` method to handle any errors that might occur when the database is updated. If an error occurs, the HttpPost Delete method calls the HttpGet Delete method, passing it a parameter that indicates that an error has occurred. The HttpGet Delete method then redisplays the confirmation page along with the error message, giving the user an opportunity to cancel or try again.
+You need to add a try-catch block to the HttpPost `Delete` method to handle any errors that might occur when the database is updated. If an error occurs, the HttpPost `Delete` method calls the HttpGet `Delete` method, passing a parameter that indicates an error. The HttpGet `Delete` method redisplays the confirmation page along with the error message, giving the user an opportunity to cancel or try again.
Replace the HttpGet `Delete` action method with the following code, which manages error reporting.
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_DeleteGet&highlight=1,9,16-21)]
-This code accepts an optional parameter that indicates whether the method was called after a failure to save changes. This parameter is false when the HttpGet `Delete` method is called without a previous failure. When it's called by the HttpPost `Delete` method in response to a database update error, the parameter is true and an error message is passed to the view.
+This code accepts an optional parameter that indicates whether the method call is after a failure to save changes. This parameter is false when the HttpGet `Delete` method is called without a previous failure. When the method is called by the HttpPost `Delete` method in response to a database update error, the parameter is true and an error message is passed to the view.
-### The read-first approach to HttpPost Delete
+### Use read-first approach to HttpPost Delete
-Replace the HttpPost `Delete` action method (named `DeleteConfirmed`) with the following code, which performs the actual delete operation and catches any database update errors.
+Replace the HttpPost `Delete` action method (named `DeleteConfirmed`) with the following code. This code performs the actual delete operation and catches any database update errors.
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_DeleteWithReadFirst&highlight=6-9,11-12,16-21)]
-This code retrieves the selected entity, then calls the `Remove` method to set the entity's status to `Deleted`. When `SaveChanges` is called, a SQL DELETE command is generated.
+The code retrieves the selected entity, and then calls the `Remove` method to set the entity's status to `Deleted`. When the `SaveChanges` method is called, a SQL DELETE command is generated.
-### The create-and-attach approach to HttpPost Delete
+### Use create-and-attach approach to HttpPost Delete
-If improving performance in a high-volume application is a priority, you could avoid an unnecessary SQL query by instantiating a Student entity using only the primary key value and then setting the entity state to `Deleted`. That's all that the Entity Framework needs in order to delete the entity. (Don't put this code in your project; it's here just to illustrate an alternative.)
+If improving performance in a high-volume application is a priority, you might avoid an unnecessary SQL query by instantiating a `Student` entity by using only the primary key value and setting the entity state to `Deleted`. This information is all the Entity Framework needs to delete the entity. (Don't update your project with the following code. The code illustrates an alternate approach.)
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_DeleteWithoutReadFirst&highlight=7-8)]
-If the entity has related data that should also be deleted, make sure that cascade delete is configured in the database. With this approach to entity deletion, EF might not realize there are related entities to be deleted.
+If the entity also contains related data to delete, make sure that cascade delete is configured in the database. With this approach to entity deletion, Entity Framework might not realize there are related entities to delete.
### Update the Delete view
-In `Views/Student/Delete.cshtml`, add an error message between the h2 heading and the h3 heading, as shown in the following example:
+In the _Views/Student/Delete.cshtml_ file, add an error message between the h2 heading and the h3 heading, as shown in the following example:
[!code-cshtml[](intro/samples/cu/Views/Students/Delete.cshtml?range=7-9&highlight=2)]
-Run the app, select the **Students** tab, and click a **Delete** hyperlink:
+Run the app, select the **Students** tab, and select a **Delete** hyperlink:
-
+:::image type="content" source="crud/_static/student-delete.png" border="false" alt-text="Screenshot of the Confirmation page for the Delete action.":::
-Click **Delete**. The Index page is displayed without the deleted student. (You'll see an example of the error handling code in action in the concurrency tutorial.)
+Select **Delete**. The **Index** page displays without the deleted student. (You see an example of the error handling code in action in the concurrency tutorial.)
## Close database connections
-To free up the resources that a database connection holds, the context instance must be disposed as soon as possible when you are done with it. The ASP.NET Core built-in [dependency injection](../../fundamentals/dependency-injection.md) takes care of that task for you.
+To free up the resources that a database connection holds, the context instance must be disposed as soon as possible when you're finished. The ASP.NET Core built-in [dependency injection](../../fundamentals/dependency-injection.md) takes care of the clean-up task.
-In `Startup.cs`, you call the [AddDbContext extension method](https://github.com/aspnet/EntityFrameworkCore/blob/03bcb5122e3f577a84498545fcf130ba79a3d987/src/Microsoft.EntityFrameworkCore/EntityFrameworkServiceCollectionExtensions.cs) to provision the `DbContext` class in the ASP.NET Core DI container. That method sets the service lifetime to `Scoped` by default. `Scoped` means the context object lifetime coincides with the web request life time, and the `Dispose` method will be called automatically at the end of the web request.
+In the _Startup.cs_ file, you call the [AddDbContext extension method](https://github.com/dotnet/efcore/blob/03bcb5122e3f577a84498545fcf130ba79a3d987/src/Microsoft.EntityFrameworkCore/EntityFrameworkServiceCollectionExtensions.cs) to create the `DbContext` class in the ASP.NET Core DI container. The method sets the service lifetime to `Scoped` by default. `Scoped` means the context object lifetime coincides with the web request life time, and the `Dispose` method is called automatically at the end of the web request.
## Handle transactions
-By default the Entity Framework implicitly implements transactions. In scenarios where you make changes to multiple rows or tables and then call `SaveChanges`, the Entity Framework automatically makes sure that either all of your changes succeed or they all fail. If some changes are done first and then an error happens, those changes are automatically rolled back. For scenarios where you need more control -- for example, if you want to include operations done outside of Entity Framework in a transaction -- see [Transactions](/ef/core/saving/transactions).
+By default, the Entity Framework implicitly implements transactions. In scenarios where you make changes to multiple rows or tables and then call the `SaveChanges` method, the Entity Framework automatically makes sure that either all of your changes succeed or they all fail. If some changes are finished first and then an error occurs, the completed changes are automatically rolled back. In scenarios where you need more control, see [Transactions](/ef/core/saving/transactions). The examples cover how to include operations completed outside of Entity Framework in a transaction.
-## No-tracking queries
+## Disable tracking of entity objects (no-tracking queries)
-When a database context retrieves table rows and creates entity objects that represent them, by default it keeps track of whether the entities in memory are in sync with what's in the database. The data in memory acts as a cache and is used when you update an entity. This caching is often unnecessary in a web application because context instances are typically short-lived (a new one is created and disposed for each request) and the context that reads an entity is typically disposed before that entity is used again.
+When a database context retrieves table rows and creates entity objects that represent them, it tracks whether the entities in memory are in sync with the database, by default. The data in memory acts as a cache and is used when you update an entity. This caching is often unnecessary in a web application because context instances are typically short-lived (a new one is created and disposed for each request). The context that reads an entity is typically disposed before that entity is used again.
-You can disable tracking of entity objects in memory by calling the `AsNoTracking` method. Typical scenarios in which you might want to do that include the following:
+You can disable tracking of entity objects in memory by calling the `AsNoTracking` method. Here are some of the common scenarios for this action:
-* During the context lifetime you don't need to update any entities, and you don't need EF to [automatically load navigation properties with entities retrieved by separate queries](read-related-data.md). Frequently these conditions are met in a controller's HttpGet action methods.
+* During the context lifetime, you don't need to update any entities, and you don't need Entity Framework to [automatically load navigation properties with entities retrieved by separate queries](read-related-data.md). These conditions are frequently met in a controller's HttpGet `Action` methods.
-* You are running a query that retrieves a large volume of data, and only a small portion of the returned data will be updated. It may be more efficient to turn off tracking for the large query, and run a query later for the few entities that need to be updated.
+* You're running a query that retrieves a large volume of data, and only a small portion of the returned data gets updated. It can be more efficient to turn off tracking for the large query, and run a query later for the few entities that need updates.
-* You want to attach an entity in order to update it, but earlier you retrieved the same entity for a different purpose. Because the entity is already being tracked by the database context, you can't attach the entity that you want to change. One way to handle this situation is to call `AsNoTracking` on the earlier query.
+* You want to attach an entity to make updates, but earlier you retrieved the same entity for a different purpose. Because the database context already tracks the entity, you can't attach the entity you want to change. One way to handle this situation is to call the `AsNoTracking` method on the earlier query.
-For more information, see [Tracking vs. No-Tracking](/ef/core/querying/tracking).
+For more information, see [Tracking versus no-tracking queries](/ef/core/querying/tracking).
## Get the code
-[Download or view the completed application.](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/cu-final)
-
-## Next steps
-
-In this tutorial, you:
-
-> [!div class="checklist"]
-> * Customized the Details page
-> * Updated the Create page
-> * Updated the Edit page
-> * Updated the Delete page
-> * Closed database connections
+[Download or view the completed application](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/cu-final),
-Advance to the next tutorial to learn how to expand the functionality of the **Index** page by adding sorting, filtering, and paging.
+## Next step
> [!div class="nextstepaction"]
-> [Next: Sorting, filtering, and paging](sort-filter-page.md)
+> [Tutorial: Add sorting, filtering, and paging - ASP.NET MVC with EF Core](sort-filter-page.md)
\ No newline at end of file
diff --git a/aspnetcore/data/ef-mvc/sort-filter-page.md b/aspnetcore/data/ef-mvc/sort-filter-page.md
index 95b87a4ab3fa..6685c28dd56b 100644
--- a/aspnetcore/data/ef-mvc/sort-filter-page.md
+++ b/aspnetcore/data/ef-mvc/sort-filter-page.md
@@ -1,106 +1,110 @@
---
-title: "Tutorial: Add sorting, filtering, and paging - ASP.NET MVC with EF Core"
-description: "In this tutorial you'll add sorting, filtering, and paging functionality to the Students Index page. You'll also create a page that does simple grouping."
+title: Add Sort, Filter, Paging - ASP.NET MVC with EF Core
+description: Add sorting, filtering, and paging functionality to the Students Index page, and create a page for simple grouping. This article is part of a tutorial series.
author: tdykstra
ms.author: tdykstra
-ms.date: 03/27/2019
+ms.date: 04/17/2026
ms.topic: tutorial
uid: data/ef-mvc/sort-filter-page
+
+# customer intent: As an ASP.NET developer, I want to add sorting, filtering, and paging functionality in ASP.NET MVC with EF Core, so I can customize my site pages and behavior.
---
# Tutorial: Add sorting, filtering, and paging - ASP.NET MVC with EF Core
-In the previous tutorial, you implemented a set of web pages for basic CRUD operations for Student entities. In this tutorial you'll add sorting, filtering, and paging functionality to the Students Index page. You'll also create a page that does simple grouping.
+In the previous tutorial, you implemented a set of web pages for basic CRUD operations for Student entities. You created an MVC application that stores and displays data by using the [Entity Framework (EF) Core](/ef/core/)
-The following illustration shows what the page will look like when you're done. The column headings are links that the user can click to sort by that column. Clicking a column heading repeatedly toggles between ascending and descending sort order.
+In this exercise, you add sorting, filtering, and paging functionality to the Students Index page. You also create a page that does simple grouping. The following illustration shows what the page looks like when you're done. The column headings are links the user can select to sort by that column. Selecting a column heading repeatedly causes the sort order to toggle between ascending and descending.
-
+:::image type="content" source="sort-filter-page/_static/paging.png" border="false" alt-text="Screenshot of the Students index page that includes sorting, filtering, and paging functionality.":::
In this tutorial, you:
> [!div class="checklist"]
-> * Add column sort links
-> * Add a Search box
-> * Add paging to Students Index
-> * Add paging to Index method
-> * Add paging links
-> * Create an About page
+> * Add links to support column sorting
+> * Add a Search box to support searches
+> * Add paging to the Students Index
+> * Add links to support the paging action
+> * Create an About page for the site
## Prerequisites
-* [Implement CRUD Functionality](crud.md)
+* Complete the previous tutorial, [Implement basic CRUD functionality - ASP.NET MVC with EF Core](crud.md)
## Add column sort links
-To add sorting to the Student Index page, you'll change the `Index` method of the Students controller and add code to the Student Index view.
+To add sorting to the **Students Index** page, you change the `Index` method of the Students controller and add code to the Student Index view.
-### Add sorting Functionality to the Index method
+### Add sorting functionality to the Index method
-In `StudentsController.cs`, replace the `Index` method with the following code:
+In the _StudentsController.cs_ file, replace the `Index` method with the following code:
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_SortOnly)]
-This code receives a `sortOrder` parameter from the query string in the URL. The query string value is provided by ASP.NET Core MVC as a parameter to the action method. The parameter will be a string that's either "Name" or "Date", optionally followed by an underscore and the string "desc" to specify descending order. The default sort order is ascending.
+This code receives a `sortOrder` parameter from the query string in the URL. The query string value is provided by ASP.NET Core MVC as a parameter to the action method. The parameter is a string value: "Name" or "Date." The value is optionally followed by an underscore and the string "desc" to specify descending order. The default sort order is ascending.
-The first time the Index page is requested, there's no query string. The students are displayed in ascending order by last name, which is the default as established by the fall-through case in the `switch` statement. When the user clicks a column heading hyperlink, the appropriate `sortOrder` value is provided in the query string.
+The first time the **Index** page is requested, there's no query string. The students are displayed in ascending order by last name, which is the default as established by the fall-through case in the `switch` statement. When the user selects a column heading hyperlink, the appropriate `sortOrder` value is provided in the query string.
-The two `ViewData` elements (NameSortParm and DateSortParm) are used by the view to configure the column heading hyperlinks with the appropriate query string values.
+The two `ViewData` elements (`NameSortParm` and `DateSortParm`) are used by the view to configure the column heading hyperlinks with the appropriate query string values.
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_SortOnly&highlight=3-4)]
-These are ternary statements. The first one specifies that if the `sortOrder` parameter is null or empty, NameSortParm should be set to "name_desc"; otherwise, it should be set to an empty string. These two statements enable the view to set the column heading hyperlinks as follows:
+These code changes include ternary statements for the elements. The first statement specifies that if the `sortOrder` parameter is null or empty, the `NameSortParm` element is set to "name_desc." Otherwise, the element is set to an empty string. These two statements enable the view to set the column heading hyperlinks as follows:
-| Current sort order | Last Name Hyperlink | Date Hyperlink |
-|:--------------------:|:-------------------:|:--------------:|
-| Last Name ascending | descending | ascending |
-| Last Name descending | ascending | ascending |
-| Date ascending | ascending | descending |
-| Date descending | ascending | ascending |
+| Current sort order | Last name hyperlink | Date hyperlink |
+|---|---|---|
+| Last Name ascending | Descending | Ascending |
+| Last Name descending | Ascending | Ascending |
+| Date ascending | Ascending | Descending |
+| Date descending | Ascending | Ascending |
-The method uses LINQ to Entities to specify the column to sort by. The code creates an `IQueryable` variable before the switch statement, modifies it in the switch statement, and calls the `ToListAsync` method after the `switch` statement. When you create and modify `IQueryable` variables, no query is sent to the database. The query isn't executed until you convert the `IQueryable` object into a collection by calling a method such as `ToListAsync`. Therefore, this code results in a single query that's not executed until the `return View` statement.
+The method uses `LINQ` statements to student entities to specify the column to sort by. The code creates an `IQueryable` variable before the `switch` statement, modifies it in the `switch` statement, and calls the `ToListAsync` method after the `switch` statement. When you create and modify `IQueryable` variables, no query is sent to the database. The query isn't executed until you convert the `IQueryable` object into a collection by calling a method such as `ToListAsync`. Therefore, this code results in a single query that isn't executed until the `return View` statement.
-This code could get verbose with a large number of columns. [The last tutorial in this series](advanced.md#dynamic-linq) shows how to write code that lets you pass the name of the `OrderBy` column in a string variable.
+This code can get verbose when you work with a large number of columns. The last tutorial in this series, [Learn about advanced scenarios > Use dynamic LINQ to simplify code](advanced.md#dynamic-linq), shows how to write code that lets you pass the name of the `OrderBy` column in a string variable.
### Add column heading hyperlinks to the Student Index view
-Replace the code in `Views/Students/Index.cshtml`, with the following code to add column heading hyperlinks. The changed lines are highlighted.
+Replace the code in the _Views/Students/Index.cshtml_ file with the following code that adds column heading hyperlinks. The changed lines are highlighted.
[!code-cshtml[](intro/samples/cu/Views/Students/Index2.cshtml?highlight=16,22)]
-This code uses the information in `ViewData` properties to set up hyperlinks with the appropriate query string values.
+This code uses the information in the `ViewData` properties to set up hyperlinks with the appropriate query string values.
-Run the app, select the **Students** tab, and click the **Last Name** and **Enrollment Date** column headings to verify that sorting works.
+Run the app, select the **Students** tab, and select the **Last Name** and **Enrollment Date** column headings to verify sorting works.
-
+:::image type="content" source="sort-filter-page/_static/name-order.png" border="false" alt-text="Screenshot of the Students Index page showing the student last names sorted in ascending order.":::
## Add a Search box
-To add filtering to the Students Index page, you'll add a text box and a submit button to the view and make corresponding changes in the `Index` method. The text box will let you enter a string to search for in the first name and last name fields.
+To add filtering to the **Students Index** page, you add a text box and a **Submit** button to the view and make corresponding changes in the `Index` method. In the text box, you can enter a string to search for in the Student first name and last name fields.
-### Add filtering functionality to the Index method
+### Add filtering support to the Index method
-In `StudentsController.cs`, replace the `Index` method with the following code (the changes are highlighted).
+In the _StudentsController.cs_ file, replace the `Index` method with the following code (the changes are highlighted).
[!code-csharp[](intro/samples/cu/Controllers/StudentsController.cs?name=snippet_SortFilter&highlight=1,5,9-13)]
-You've added a `searchString` parameter to the `Index` method. The search string value is received from a text box that you'll add to the Index view. You've also added to the LINQ statement a where clause that selects only students whose first name or last name contains the search string. The statement that adds the where clause is executed only if there's a value to search for.
+You added a `searchString` parameter to the `Index` method. The search string value is received from a text box that you add to the Index view. You also added a `where` clause to the `LINQ` statement that selects only students whose first name or last name match the search string. The statement that adds the `where` clause is executed only if there's a searchable value.
+
+#### Determine how to use the Where method
+
+In this scenario, you call the `Where` method on an `IQueryable` object, and the filter is processed on the server. In some scenarios, you might call the `Where` method as an extension method on an in-memory collection. Suppose you change the reference to `_context.Students`. Instead of calling the Entity Framework `DbSet` method, it references a repository method that returns an `IEnumerable` collection. The result is normally the same, but some cases might be different.
-> [!NOTE]
-> Here you are calling the `Where` method on an `IQueryable` object, and the filter will be processed on the server. In some scenarios you might be calling the `Where` method as an extension method on an in-memory collection. (For example, suppose you change the reference to `_context.Students` so that instead of an EF `DbSet` it references a repository method that returns an `IEnumerable` collection.) The result would normally be the same but in some cases may be different.
->
->For example, the .NET Framework implementation of the `Contains` method performs a case-sensitive comparison by default, but in SQL Server this is determined by the collation setting of the SQL Server instance. That setting defaults to case-insensitive. You could call the `ToUpper` method to make the test explicitly case-insensitive: *Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())*. That would ensure that results stay the same if you change the code later to use a repository which returns an `IEnumerable` collection instead of an `IQueryable` object. (When you call the `Contains` method on an `IEnumerable` collection, you get the .NET Framework implementation; when you call it on an `IQueryable` object, you get the database provider implementation.) However, there's a performance penalty for this solution. The `ToUpper` code would put a function in the WHERE clause of the TSQL SELECT statement. That would prevent the optimizer from using an index. Given that SQL is mostly installed as case-insensitive, it's best to avoid the `ToUpper` code until you migrate to a case-insensitive data store.
+For example, the .NET Framework implementation of the `Contains` method performs a case-sensitive comparison by default. In SQL Server, the determination occurs by the collation setting of the SQL Server instance. The setting defaults to case-insensitive. You might call the `ToUpper` method to make the test explicitly case-insensitive: `Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`. This option ensures the results stay the same if you change the code later to use a repository that returns an `IEnumerable` collection rather than an `IQueryable` object. (When you call the `Contains` method on an `IEnumerable` collection, you get the .NET Framework implementation. When you call it on an `IQueryable` object, you get the database provider implementation.)
-### Add a Search Box to the Student Index View
+However, there's a performance penalty for this solution. The `ToUpper` code puts a function in the `WHERE` clause of the `TSQL SELECT` statement. This behavior prevents the optimizer from using an index. Given that SQL is mostly installed as case-insensitive, it's best to avoid the `ToUpper` code until you migrate to a case-insensitive data store.
-In `Views/Student/Index.cshtml`, add the highlighted code immediately before the opening table tag in order to create a caption, a text box, and a **Search** button.
+### Add a Search Box to the Student Index view
+
+In the _Views/Student/Index.cshtml_ file, add the highlighted code immediately before the opening table tag `` in order to create a caption, a text box, and a **Search** button.
[!code-cshtml[](intro/samples/cu/Views/Students/Index3.cshtml?range=9-23&highlight=5-13)]
-This code uses the `