Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions aspnetcore/includes/net-prereqs-vs-10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) with the **ASP.NET and web development** workload.

:::image type="content" source="~/tutorials/min-web-api/_static/asp-net-web-dev-2026.png" alt-text="VS22 installer workloads" lightbox="~/tutorials/min-web-api/_static/asp-net-web-dev-2026.png":::
2 changes: 1 addition & 1 deletion aspnetcore/includes/net-prereqs-vsc-10.0.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
* [Visual Studio Code](https://code.visualstudio.com/download)
* [C# Dev Kit for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit)
* [!INCLUDE [](~/includes/0-latest-SDK.md)]
* [!INCLUDE [](~/includes/10.0-SDK.md)]

You can follow the Visual Studio Code instructions on macOS, Linux, or Windows. Changes may be required if you use an integrated development environment (IDE) other than Visual Studio Code.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 23 additions & 20 deletions aspnetcore/tutorials/razor-pages/da1.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ title: Part 5, update the generated pages
author: wadepickett
description: Part 5 of tutorial series on Razor Pages.
ms.author: wpickett
ms.date: 06/23/2024
ms.date: 01/08/2026
uid: tutorials/razor-pages/da1
---
# Part 5, update the generated pages in an ASP.NET Core app

[!INCLUDE[](~/includes/not-latest-version.md)]

:::moniker range=">= aspnetcore-9.0"
:::moniker range=">= aspnetcore-10.0"

The scaffolded movie app has a good start, but the presentation isn't ideal. **ReleaseDate** should be two words, **Release Date**.
The scaffolded movie app is a good start, but the presentation isn't ideal. **ReleaseDate** should be two words, **Release Date**.

:::image type="content" source="~/tutorials/razor-pages/sql/media/seed-data-in-app.png" alt-text="Movie application open in Chrome.":::

![Movie application open in Chrome](~/tutorials/razor-pages/sql/_static/9/seededDataUI.png)

## Update the model

Update `Models/Movie.cs` with the following highlighted code:

[!code-csharp[Main](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Models/MovieDateFixed.cs?name=snippet_1&highlight=2,11,16)]
[!code-csharp[Main](~/tutorials/razor-pages/razor-pages-start/snapshot-sample10/Models/MovieDateFixed.cs?name=snippet_1&highlight=2,11,16)]

In the previous code:

Expand All @@ -32,11 +33,11 @@ In the previous code:

Browse to *Pages/Movies* and hover over an **Edit** link to see the target URL.

![Browser window with mouse over the Edit link and a link Url of https://localhost:1234/Movies/Edit/5 is shown](~/tutorials/razor-pages/da1/_static/9/edit9.png)
:::image type="content" source="~/tutorials/razor-pages/da1/media/edit-movie.png" alt-text="Browser window with mouse over the Edit link and a link Url of https://localhost:1234/Movies/Edit/5 is shown.":::

The **Edit**, **Details**, and **Delete** links are generated by the [Anchor Tag Helper](xref:mvc/views/tag-helpers/builtin-th/anchor-tag-helper) in the `Pages/Movies/Index.cshtml` file.

[!code-cshtml[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Index.cshtml?highlight=16-18&range=32-)]
[!code-cshtml[](~/tutorials/razor-pages/razor-pages-start/snapshot-sample10/Pages/Movies/Index.cshtml?highlight=16-18&range=32-)]

[Tag Helpers](xref:mvc/views/tag-helpers/intro) enable server-side code to participate in creating and rendering HTML elements in Razor files.

Expand All @@ -52,7 +53,7 @@ Use **View Source** from a browser to examine the generated markup. A portion of
</td>
```

The dynamically generated links pass the movie ID with a [query string](https://launchschool.com/books/http/read/what_is_a_url). For example, the `?id=1` in `https://localhost:5001/Movies/Details?id=1`.
The dynamically generated links pass the movie ID with a [query string](https://launchschool.com/books/http/read/what_is_a_url). For example, the `?id=1` in `https://localhost:7247/Movies/Details?id=1`.

### Add route template

Expand All @@ -68,7 +69,7 @@ The generated HTML adds the ID to the path portion of the URL:
</td>
```

A request to the page with the `{id:int}` route template that does **not** include the integer returns an HTTP 404 (not found) error. For example, `https://localhost:5001/Movies/Details` returns a 404 error. To make the ID optional, append `?` to the route constraint:
A request to the page with the `{id:int}` route template that doesn't include the integer returns an HTTP 404 (not found) error. For example, `https://localhost:7247/Movies/Details` returns a 404 error. To make the ID optional, append `?` to the route constraint:

```cshtml
@page "{id:int?}"
Expand All @@ -78,51 +79,51 @@ Test the behavior of `@page "{id:int?}"`:

1. Set the page directive in `Pages/Movies/Details.cshtml` to `@page "{id:int?}"`.
1. Set a break point in `public async Task<IActionResult> OnGetAsync(int? id)`, in `Pages/Movies/Details.cshtml.cs`.
1. Navigate to `https://localhost:5001/Movies/Details/`.
1. Navigate to `https://localhost:7247/Movies/Details/`.

With the `@page "{id:int}"` directive, the break point is never hit. The routing engine returns HTTP 404. Using `@page "{id:int?}"`, the `OnGetAsync` method returns `NotFound` (HTTP 404):

[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Details.cshtml.cs?name=snippet_1&highlight=3-6)]
[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot-sample10/Pages/Movies/Details.cshtml.cs?name=snippet_1&highlight=3-6)]

### Review concurrency exception handling

Review the `OnPostAsync` method in the `Pages/Movies/Edit.cshtml.cs` file:

[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Edit.cshtml.cs?name=snippet_1)]
[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot-sample10/Pages/Movies/Edit.cshtml.cs?name=snippet_1)]

The previous code detects concurrency exceptions when one client deletes the movie and the other client posts changes to the movie.

To test the `catch` block:

1. Set a breakpoint on `catch (DbUpdateConcurrencyException)`.
1. Select **Edit** for a movie, make changes, but don't enter **Save**.
1. Select **Edit** for a movie, make changes, but don't select **Save**.
1. In another browser window, select the **Delete** link for the same movie, and then delete the movie.
1. In the previous browser window, post changes to the movie.

Production code may want to detect concurrency conflicts. See [Handle concurrency conflicts](xref:data/ef-rp/concurrency) for more information.
Production code might want to detect concurrency conflicts. For more information, see [Handle concurrency conflicts](xref:data/ef-rp/concurrency).

### Posting and binding review

Examine the `Pages/Movies/Edit.cshtml.cs` file:

[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Edit.cshtml.cs?name=snippet2)]
[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot-sample10/Pages/Movies/Edit.cshtml.cs?name=snippet2)]

When an HTTP GET request is made to the Movies/Edit page, for example, `https://localhost:5001/Movies/Edit/3`:
When you make an HTTP GET request to the `Movies/Edit` page, for example, `https://localhost:7247/Movies/Edit/3`:

* The `OnGetAsync` method fetches the movie from the database and returns the `Page` method.
* The `Page` method renders the `Pages/Movies/Edit.cshtml` Razor Page. The `Pages/Movies/Edit.cshtml` file contains the model directive `@model RazorPagesMovie.Pages.Movies.EditModel`, which makes the movie model available on the page.
* The Edit form is displayed with the values from the movie.
* The Edit form displays the values from the movie.

When the Movies/Edit page is posted:
When you post the `Movies/Edit` page:

* The form values on the page are bound to the `Movie` property. The `[BindProperty]` attribute enables [Model binding](xref:mvc/models/model-binding).
* The form values on the page bind to the `Movie` property. The `[BindProperty]` attribute enables [Model binding](xref:mvc/models/model-binding).

```csharp
[BindProperty]
public Movie Movie { get; set; }
```

* If there are errors in the model state, for example, `ReleaseDate` cannot be converted to a date, the form is redisplayed with the submitted values.
* If there are errors in the model state, for example, `ReleaseDate` can't convert to a date, the form redisplays with the submitted values.
* If there are no model errors, the movie is saved.

The HTTP GET methods in the Index, Create, and Delete Razor pages follow a similar pattern. The HTTP POST `OnPostAsync` method in the Create Razor Page follows a similar pattern to the `OnPostAsync` method in the Edit Razor Page.
Expand All @@ -135,6 +136,8 @@ The HTTP GET methods in the Index, Create, and Delete Razor pages follow a simil

:::moniker-end

[!INCLUDE[](~/tutorials/razor-pages/da1/includes/da1-9.md)]

[!INCLUDE[](~/tutorials/razor-pages/da1/includes/da1_8.md)]

[!INCLUDE[](~/tutorials/razor-pages/da1/includes/da1_7.md)]
Expand Down
124 changes: 124 additions & 0 deletions aspnetcore/tutorials/razor-pages/da1/includes/da1-9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
:::moniker range="= aspnetcore-9.0"

The scaffolded movie app has a good start, but the presentation isn't ideal. **ReleaseDate** should be two words, **Release Date**.

![Movie application open in Chrome](~/tutorials/razor-pages/sql/_static/9/seededDataUI.png)

## Update the model

Update `Models/Movie.cs` with the following highlighted code:

[!code-csharp[Main](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Models/MovieDateFixed.cs?name=snippet_1&highlight=2,11,16)]

In the previous code:

* The `[Column(TypeName = "decimal(18, 2)")]` data annotation enables Entity Framework Core to correctly map `Price` to currency in the database. For more information, see [Data Types](/ef/core/modeling/relational/data-types).
* The [[Display]](xref:System.ComponentModel.DataAnnotations.DisplayAttribute) attribute specifies the display name of a field. In the preceding code, `Release Date` instead of `ReleaseDate`.
* The [[DataType]](xref:System.ComponentModel.DataAnnotations.DataTypeAttribute) attribute specifies the type of the data (`Date`). The time information stored in the field isn't displayed.

[DataAnnotations](/aspnet/mvc/overview/older-versions/mvc-music-store/mvc-music-store-part-6) is covered in the next tutorial.

Browse to *Pages/Movies* and hover over an **Edit** link to see the target URL.

![Browser window with mouse over the Edit link and a link Url of https://localhost:1234/Movies/Edit/5 is shown](~/tutorials/razor-pages/da1/_static/9/edit9.png)

The **Edit**, **Details**, and **Delete** links are generated by the [Anchor Tag Helper](xref:mvc/views/tag-helpers/builtin-th/anchor-tag-helper) in the `Pages/Movies/Index.cshtml` file.

[!code-cshtml[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Index.cshtml?highlight=16-18&range=32-)]

[Tag Helpers](xref:mvc/views/tag-helpers/intro) enable server-side code to participate in creating and rendering HTML elements in Razor files.

In the preceding code, the [Anchor Tag Helper](xref:mvc/views/tag-helpers/builtin-th/anchor-tag-helper) dynamically generates the HTML `href` attribute value from the Razor Page (the route is relative), the `asp-page`, and the route identifier (`asp-route-id`). For more information, see [URL generation for Pages](xref:razor-pages/index#url-generation-for-pages).

Use **View Source** from a browser to examine the generated markup. A portion of the generated HTML is shown below:

```html
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
```

The dynamically generated links pass the movie ID with a [query string](https://launchschool.com/books/http/read/what_is_a_url). For example, the `?id=1` in `https://localhost:5001/Movies/Details?id=1`.

### Add route template

Update the Edit, Details, and Delete Razor Pages to use the `{id:int}` route template. Change the page directive for each of these pages from `@page` to `@page "{id:int}"`. Run the app and then view source.

The generated HTML adds the ID to the path portion of the URL:

```html
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
```

A request to the page with the `{id:int}` route template that does **not** include the integer returns an HTTP 404 (not found) error. For example, `https://localhost:5001/Movies/Details` returns a 404 error. To make the ID optional, append `?` to the route constraint:

```cshtml
@page "{id:int?}"
```

Test the behavior of `@page "{id:int?}"`:

1. Set the page directive in `Pages/Movies/Details.cshtml` to `@page "{id:int?}"`.
1. Set a break point in `public async Task<IActionResult> OnGetAsync(int? id)`, in `Pages/Movies/Details.cshtml.cs`.
1. Navigate to `https://localhost:5001/Movies/Details/`.

With the `@page "{id:int}"` directive, the break point is never hit. The routing engine returns HTTP 404. Using `@page "{id:int?}"`, the `OnGetAsync` method returns `NotFound` (HTTP 404):

[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Details.cshtml.cs?name=snippet_1&highlight=3-6)]

### Review concurrency exception handling

Review the `OnPostAsync` method in the `Pages/Movies/Edit.cshtml.cs` file:

[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Edit.cshtml.cs?name=snippet_1)]

The previous code detects concurrency exceptions when one client deletes the movie and the other client posts changes to the movie.

To test the `catch` block:

1. Set a breakpoint on `catch (DbUpdateConcurrencyException)`.
1. Select **Edit** for a movie, make changes, but don't enter **Save**.
1. In another browser window, select the **Delete** link for the same movie, and then delete the movie.
1. In the previous browser window, post changes to the movie.

Production code may want to detect concurrency conflicts. See [Handle concurrency conflicts](xref:data/ef-rp/concurrency) for more information.

### Posting and binding review

Examine the `Pages/Movies/Edit.cshtml.cs` file:

[!code-csharp[](~/tutorials/razor-pages/razor-pages-start/snapshot_sample9/Pages/Movies/Edit.cshtml.cs?name=snippet2)]

When an HTTP GET request is made to the Movies/Edit page, for example, `https://localhost:5001/Movies/Edit/3`:

* The `OnGetAsync` method fetches the movie from the database and returns the `Page` method.
* The `Page` method renders the `Pages/Movies/Edit.cshtml` Razor Page. The `Pages/Movies/Edit.cshtml` file contains the model directive `@model RazorPagesMovie.Pages.Movies.EditModel`, which makes the movie model available on the page.
* The Edit form is displayed with the values from the movie.

When the Movies/Edit page is posted:

* The form values on the page are bound to the `Movie` property. The `[BindProperty]` attribute enables [Model binding](xref:mvc/models/model-binding).

```csharp
[BindProperty]
public Movie Movie { get; set; }
```

* If there are errors in the model state, for example, `ReleaseDate` cannot be converted to a date, the form is redisplayed with the submitted values.
* If there are no model errors, the movie is saved.

The HTTP GET methods in the Index, Create, and Delete Razor pages follow a similar pattern. The HTTP POST `OnPostAsync` method in the Create Razor Page follows a similar pattern to the `OnPostAsync` method in the Edit Razor Page.

## Next steps

> [!div class="step-by-step"]
> [Previous: Work with a database](xref:tutorials/razor-pages/sql)
> [Next: Add search](xref:tutorials/razor-pages/search)

:::moniker-end
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions aspnetcore/tutorials/razor-pages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Tutorial: Create a Razor Pages web app with ASP.NET Core"
author: wadepickett
description: This series of tutorials explains the basics of building a Razor Pages web app.
ms.author: wpickett
ms.date: 06/23/2024
ms.date: 01/09/2026
monikerRange: '>= aspnetcore-3.1'
uid: tutorials/razor-pages/index
---
Expand All @@ -30,6 +30,6 @@ This series includes the following tutorials:

At the end, you'll have an app that can display and manage a database of movies.

![Sample page of sample app](~/tutorials/razor-pages/index/_static/sample-page.png)
:::image type="content" source="~/tutorials/razor-pages/search/media/search-string-ghost.png" alt-text="Sample page of sample app.":::

![Sample edit validation of sample app](~/tutorials/razor-pages/index/_static/sample-page-validation.png)
:::image type="content" source="~/tutorials/razor-pages/validation/media/validation-errors.png" alt-text="Sample edit validation of sample app.":::
Loading