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
2 changes: 1 addition & 1 deletion aspnetcore/blazor/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Based on the interactive render mode selected at app creation, the `Layout` fold
* The `NavMenu.razor.css` is the collocated stylesheet for the app's navigation menu.
* The `ReconnectModal` component reflects the server-side connection state in the UI and is included when the app's interactive render mode is either Interactive Server or Interactive Auto. For more information, see <xref:blazor/fundamentals/signalr#reflect-the-server-side-connection-state-in-the-ui>.
* The `ReconnectModal.razor.css` is the collocated stylesheet for the `ReconnectModal` component.
* The `ReconnectModal.razor.css` is the collocated JavaScript file for the `ReconnectModal` component.
* The `ReconnectModal.razor.js` is the collocated JavaScript file for the `ReconnectModal` component.

:::moniker-end

Expand Down
3 changes: 3 additions & 0 deletions aspnetcore/blazor/tutorials/movie-database-app/part-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ The `Components/Layout` folder contains the following layout components and styl
* `MainLayout.razor.css`: Stylesheet for the app's main layout.
* `NavMenu` component (`NavMenu.razor`): Implements sidebar navigation. This component uses several `NavLink` components to render navigation links to other Razor components.
* `NavMenu.razor.css`: Stylesheet for the app's navigation menu.
* `ReconnectModal` component (`ReconnectModal.razor`): Reflects the server-side connection state in the UI.
* `ReconnectModal.razor.css`: Stylesheet for the `ReconnectModal` component.
* `ReconnectModal.razor.js`: JavaScript file for the `ReconnectModal` component.

### `Components/_Imports.razor` file

Expand Down
70 changes: 65 additions & 5 deletions aspnetcore/blazor/tutorials/movie-database-app/part-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,13 +421,31 @@ The `movie` variable is a private field of type `Movie`, which is a null-referen

The `Id` is a *component parameter* supplied from the component's query string due to the presence of the [`[SupplyParameterFromQuery]` attribute](xref:Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute). If the identifier is missing, `Id` defaults to zero (`0`).

`OnInitializedAsync` is the first component lifecycle method that we've seen. This method is executed when the component loads. <xref:Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync%2A> is called on the database set (`DbSet<Movie>`) to retrieve the movie entity with and `Id` equal to the `Id` parameter that was set by the query string. If `movie` is `null`, <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A?displayProperty=nameWithType> is used to navigate to a `notfound` endpoint.
`OnInitializedAsync` is the first component lifecycle method that we've seen. This method is executed when the component loads. <xref:Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync%2A> is called on the database set (`DbSet<Movie>`) to retrieve the movie entity with an `Id` equal to the `Id` parameter that was set by the query string. If `movie` is `null`, <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A?displayProperty=nameWithType> is used to navigate to a `notfound` endpoint.

<!-- UPDATE 10.0 - See https://github.com/dotnet/aspnetcore/issues/45654
for .NET 10 work on handling a 404 with static SSR without \
having to navigate to a non-existent endpoint. -->
<!-- UPDATE 10.0 - Update for scaffolder changes when they
appear: https://github.com/dotnet/Scaffolding/issues/3177 -->

There isn't an actual `notfound` endpoint (Razor component) in the app. When adopting server-side rendering (SSR), Blazor doesn't have a mechanism to return a 404 (Not Found) status code. As a temporary workaround, a 404 is generated by navigating to a non-existent endpoint. This scaffolded code is for your further implementation of a suitable result when not finding an entity. For example, you could have the component direct the user to a page where they can file an inquiry with your support team, or you could remove the injected <xref:Microsoft.AspNetCore.Components.NavigationManager> and <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A?displayProperty=nameWithType> code and replace it with Razor markup and code that displays a message to the user that the entity wasn't found.
:::moniker range=">= aspnetcore-10.0"

The .NET scaffolder currently doesn't implement .NET 10's Not Found feature, but it can be implemented manually.

Update the line that navigates to a non-existent `notfound` endpoint. Change the line to call <xref:Microsoft.AspNetCore.Components.NavigationManager.NotFound%2A?displayProperty=nameWithType>:

```diff
- NavigationManager.NavigateTo("notfound");
+ NavigationManager.NotFound();
```

When a movie isn't found, this line change results in rendering the `NotFound` component, which produces a Not Found page in the browser with a 404 (Not Found) status code.

:::moniker-end

:::moniker range="< aspnetcore-10.0"

There isn't an actual `notfound` endpoint (Razor component) in the app. When adopting server-side rendering (SSR) in .NET 8 or .NET 9, the app doesn't have a mechanism to return a 404 (Not Found) status code. A 404 is generated by navigating to a non-existent endpoint. This scaffolded code is for your further implementation of a suitable result when not finding an entity in .NET 8/9. For example, you could have the component direct the user to a page where they can file an inquiry with your support team, or you could remove the injected <xref:Microsoft.AspNetCore.Components.NavigationManager> and <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A?displayProperty=nameWithType> code and replace it with Razor markup and code that displays a message to the user that the entity wasn't found. In .NET 10 or later, Blazor has a built-in Not Found feature. For more information, see the [.NET 10 version of this section](?view=aspnetcore-10.0&pivots=vs&preserve-view=true#details-component).

:::moniker-end

### `Create` component

Expand Down Expand Up @@ -540,6 +558,24 @@ private async Task DeleteMovie()
}
```

<!-- UPDATE 10.0 - Update for scaffolder changes when they
appear: https://github.com/dotnet/Scaffolding/issues/3177 -->

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

The .NET scaffolder currently doesn't implement .NET 10's Not Found feature, but it can be implemented manually.

Update the line that navigates to a non-existent `notfound` endpoint. Change the line to call <xref:Microsoft.AspNetCore.Components.NavigationManager.NotFound%2A?displayProperty=nameWithType>:

```diff
- NavigationManager.NavigateTo("notfound");
+ NavigationManager.NotFound();
```

When a movie isn't found, this line change results in rendering the `NotFound` component, which produces a Not Found page in the browser with a 404 (Not Found) status code.

:::moniker-end

### `Edit` component

Open the `Edit` component definition file (`Components/Pages/Movies/Edit.razor`).
Expand Down Expand Up @@ -595,8 +631,32 @@ private bool MovieExists(int id)

The movie entity's <xref:Microsoft.EntityFrameworkCore.EntityState> is set to <xref:Microsoft.EntityFrameworkCore.EntityState.Modified>, which signifies that the entity is tracked by the context, exists in the database, and that some or all of its property values are modified.

<!-- UPDATE 10.0 - Update for scaffolder changes when they
appear: https://github.com/dotnet/Scaffolding/issues/3177 -->

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

The .NET scaffolder currently doesn't implement .NET 10's Not Found feature, but it can be implemented manually.

Update the line that navigates to a non-existent `notfound` endpoint. Change the line to call <xref:Microsoft.AspNetCore.Components.NavigationManager.NotFound%2A?displayProperty=nameWithType>:

```diff
- NavigationManager.NavigateTo("notfound");
+ NavigationManager.NotFound();
```

When a movie isn't found, this line change results in rendering the `NotFound` component, which produces a Not Found page in the browser with a 404 (Not Found) status code.

If there's a concurrency exception and the movie entity no longer exists at the time that changes are saved, the component redirects to the Not Found page, which results in returning a 404 (Not Found) status code. If the movie exists and a concurrency exception is thrown, for example when another user has already modified the entity, the exception is rethrown by the component with the [`throw` statement (C# Language Reference)](/dotnet/csharp/language-reference/statements/exception-handling-statements#the-throw-statement). Additional guidance on handling concurrency with EF Core in Blazor apps is provided by the Blazor documentation.

:::moniker-end

:::moniker range="< aspnetcore-10.0"

If there's a concurrency exception and the movie entity no longer exists at the time that changes are saved, the component redirects to the non-existent endpoint (`notfound`), which results in returning a 404 (Not Found) status code. You could change this code to notify the user that the movie no longer exists in the database or create a dedicated *Not Found* component and navigate the user to that endpoint. If the movie exists and a concurrency exception is thrown, for example when another user has already modified the entity, the exception is rethrown by the component with the [`throw` statement (C# Language Reference)](/dotnet/csharp/language-reference/statements/exception-handling-statements#the-throw-statement). Additional guidance on handling concurrency with EF Core in Blazor apps is provided by the Blazor documentation.

:::moniker-end

> [!WARNING]
> Although it isn't a concern for the app in this tutorial, binding form data to entity data models can be susceptible to overposting attacks. Additional information on this subject appears in the next section.

Expand Down
43 changes: 43 additions & 0 deletions aspnetcore/blazor/tutorials/movie-database-app/part-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,47 @@ If the model state has errors when the form is posted, for example if `ReleaseDa

Review the `UpdateMovie` method of the `Edit` component (`Components/Pages/MoviePages/Edit.razor`):

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

```csharp
private async Task UpdateMovie()
{
using var context = DbFactory.CreateDbContext();
context.Attach(Movie!).State = EntityState.Modified;

try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie!.Id))
{
NavigationManager.NotFound();
}
else
{
throw;
}
}

NavigationManager.NavigateTo("/movies");
}
```

Concurrency exceptions are detected when one client deletes the movie and a different client posts changes to the movie.

To test how concurrency is handled by the preceding code:

1. Select **:::no-loc text="Edit":::** for a movie, make changes, but don't select **:::no-loc text="Save":::**.
1. In a different browser window, open the app to the movie `Index` page and select the **:::no-loc text="Delete":::** link for the same movie to delete the movie.
1. In the previous browser window, post changes to the movie by selecting the **:::no-loc text="Save":::** button.
1. The browser is navigated to the Not Found page with a 404 (Not Found) status code.

:::moniker-end

:::moniker range="< aspnetcore-10.0"

```csharp
private async Task UpdateMovie()
{
Expand Down Expand Up @@ -321,6 +362,8 @@ To test how concurrency is handled by the preceding code:
1. In the previous browser window, post changes to the movie by selecting the **:::no-loc text="Save":::** button.
1. The browser is navigated to the `notfound` endpoint, which doesn't exist and yields a 404 (Not Found) result.

:::moniker-end

Additional guidance on handling concurrency with EF Core in Blazor apps is available in the Blazor documentation.

## Stop the app
Expand Down