Skip to content

Commit 9f204c3

Browse files
authored
Using Dispose to unhook Blazor event handlers (#17339)
1 parent 372c333 commit 9f204c3

File tree

5 files changed

+131
-15
lines changed

5 files changed

+131
-15
lines changed

aspnetcore/blazor/forms-validation.md

+15-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to use forms and field validation scenarios in Blazor.
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 12/18/2019
8+
ms.date: 03/17/2020
99
no-loc: [Blazor, SignalR]
1010
uid: blazor/forms-validation
1111
---
@@ -452,8 +452,11 @@ To enable and disable the submit button based on form validation:
452452

453453
* Use the form's `EditContext` to assign the model when the component is initialized.
454454
* Validate the form in the context's `OnFieldChanged` callback to enable and disable the submit button.
455+
* Unhook the event handler in the `Dispose` method. For more information, see <xref:blazor/lifecycle#component-disposal-with-idisposable>.
455456

456457
```razor
458+
@implements IDisposable
459+
457460
<EditForm EditContext="@_editContext">
458461
<DataAnnotationsValidator />
459462
<ValidationSummary />
@@ -471,12 +474,18 @@ To enable and disable the submit button based on form validation:
471474
protected override void OnInitialized()
472475
{
473476
_editContext = new EditContext(_starship);
477+
_editContext.OnFieldChanged += HandleFieldChanged;
478+
}
474479
475-
_editContext.OnFieldChanged += (_, __) =>
476-
{
477-
_formInvalid = !_editContext.Validate();
478-
StateHasChanged();
479-
};
480+
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
481+
{
482+
_formInvalid = !_editContext.Validate();
483+
StateHasChanged();
484+
}
485+
486+
public void Dispose()
487+
{
488+
_editContext.OnFieldChanged -= HandleFieldChanged;
480489
}
481490
}
482491
```

aspnetcore/blazor/lifecycle.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to use Razor component lifecycle methods in ASP.NET Core
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 12/18/2019
8+
ms.date: 03/17/2020
99
no-loc: [Blazor, SignalR]
1010
uid: blazor/lifecycle
1111
---
@@ -48,6 +48,8 @@ To prevent developer code in `OnInitializedAsync` from running twice, see the [S
4848

4949
While a Blazor Server app is prerendering, certain actions, such as calling into JavaScript, aren't possible because a connection with the browser hasn't been established. Components may need to render differently when prerendered. For more information, see the [Detect when the app is prerendering](#detect-when-the-app-is-prerendering) section.
5050

51+
If any event handlers are set up, unhook them on disposal. For more information, see the [Component disposal with IDisposable](#component-disposal-with-idisposable) section.
52+
5153
### Before parameters are set
5254

5355
<xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync*> sets parameters supplied by the component's parent in the render tree:
@@ -67,6 +69,8 @@ The default implementation of `SetParametersAsync` sets the value of each proper
6769

6870
If `base.SetParametersAync` isn't invoked, the custom code can interpret the incoming parameters value in any way required. For example, there's no requirement to assign the incoming parameters to the properties on the class.
6971

72+
If any event handlers are set up, unhook them on disposal. For more information, see the [Component disposal with IDisposable](#component-disposal-with-idisposable) section.
73+
7074
### After parameters are set
7175

7276
<xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync*> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSet*> are called:
@@ -93,6 +97,8 @@ protected override void OnParametersSet()
9397
}
9498
```
9599

100+
If any event handlers are set up, unhook them on disposal. For more information, see the [Component disposal with IDisposable](#component-disposal-with-idisposable) section.
101+
96102
### After component render
97103

98104
<xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync*> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender*> are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps using the rendered content, such as activating third-party JavaScript libraries that operate on the rendered DOM elements.
@@ -129,6 +135,8 @@ protected override void OnAfterRender(bool firstRender)
129135

130136
`OnAfterRender` and `OnAfterRenderAsync` *aren't called when prerendering on the server.*
131137

138+
If any event handlers are set up, unhook them on disposal. For more information, see the [Component disposal with IDisposable](#component-disposal-with-idisposable) section.
139+
132140
### Suppress UI refreshing
133141

134142
Override <xref:Microsoft.AspNetCore.Components.ComponentBase.ShouldRender*> to suppress UI refreshing. If the implementation returns `true`, the UI is refreshed:
@@ -181,6 +189,16 @@ If a component implements <xref:System.IDisposable>, the [Dispose method](/dotne
181189
> [!NOTE]
182190
> Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged*> in `Dispose` isn't supported. `StateHasChanged` might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported.
183191
192+
Unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms-validation) examples show how to unhook an event handler in the `Dispose` method:
193+
194+
* Private field and lambda approach
195+
196+
[!code-razor[](lifecycle/samples_snapshot/3.x/event-handler-disposal-1.razor?highlight=23,28)]
197+
198+
* Private method approach
199+
200+
[!code-razor[](lifecycle/samples_snapshot/3.x/event-handler-disposal-2.razor?highlight=16,26)]
201+
184202
## Handle errors
185203

186204
For information on handling errors during lifecycle method execution, see <xref:blazor/handle-errors#lifecycle-methods>.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@implements IDisposable
2+
3+
<EditForm EditContext="@_editContext">
4+
5+
...
6+
7+
<button type="submit" disabled="@_formInvalid">Submit</button>
8+
</EditForm>
9+
10+
@code {
11+
...
12+
private EventHandler<FieldChangedEventArgs> _fieldChanged;
13+
14+
protected override void OnInitialized()
15+
{
16+
_editContext = new EditContext(...);
17+
18+
_fieldChanged = (_, __) =>
19+
{
20+
...
21+
};
22+
23+
_editContext.OnFieldChanged += _fieldChanged;
24+
}
25+
26+
public void Dispose()
27+
{
28+
_editContext.OnFieldChanged -= _fieldChanged;
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@implements IDisposable
2+
3+
<EditForm EditContext="@_editContext">
4+
5+
...
6+
7+
<button type="submit" disabled="@_formInvalid">Submit</button>
8+
</EditForm>
9+
10+
@code {
11+
...
12+
13+
protected override void OnInitialized()
14+
{
15+
_editContext = new EditContext(...);
16+
_editContext.OnFieldChanged += HandleFieldChanged;
17+
}
18+
19+
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
20+
{
21+
...
22+
}
23+
24+
public void Dispose()
25+
{
26+
_editContext.OnFieldChanged -= HandleFieldChanged;
27+
}
28+
}

aspnetcore/blazor/routing.md

+39-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to route requests in apps and about the NavLink component
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 12/18/2019
8+
ms.date: 03/17/2020
99
no-loc: [Blazor, SignalR]
1010
uid: blazor/routing
1111
---
@@ -190,16 +190,16 @@ The following HTML markup is rendered:
190190

191191
## URI and navigation state helpers
192192

193-
Use `Microsoft.AspNetCore.Components.NavigationManager` to work with URIs and navigation in C# code. `NavigationManager` provides the event and methods shown in the following table.
193+
Use <xref:Microsoft.AspNetCore.Components.NavigationManager> to work with URIs and navigation in C# code. `NavigationManager` provides the event and methods shown in the following table.
194194

195195
| Member | Description |
196196
| ------ | ----------- |
197-
| `Uri` | Gets the current absolute URI. |
198-
| `BaseUri` | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, `BaseUri` corresponds to the `href` attribute on the document's `<base>` element in *wwwroot/index.html* (Blazor WebAssembly) or *Pages/_Host.cshtml* (Blazor Server). |
199-
| `NavigateTo` | Navigates to the specified URI. If `forceLoad` is `true`:<ul><li>Client-side routing is bypassed.</li><li>The browser is forced to load the new page from the server, whether or not the URI is normally handled by the client-side router.</li></ul> |
200-
| `LocationChanged` | An event that fires when the navigation location has changed. |
201-
| `ToAbsoluteUri` | Converts a relative URI into an absolute URI. |
202-
| `ToBaseRelativePath` | Given a base URI (for example, a URI previously returned by `GetBaseUri`), converts an absolute URI into a URI relative to the base URI prefix. |
197+
| Uri | Gets the current absolute URI. |
198+
| BaseUri | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, `BaseUri` corresponds to the `href` attribute on the document's `<base>` element in *wwwroot/index.html* (Blazor WebAssembly) or *Pages/_Host.cshtml* (Blazor Server). |
199+
| NavigateTo | Navigates to the specified URI. If `forceLoad` is `true`:<ul><li>Client-side routing is bypassed.</li><li>The browser is forced to load the new page from the server, whether or not the URI is normally handled by the client-side router.</li></ul> |
200+
| LocationChanged | An event that fires when the navigation location has changed. |
201+
| ToAbsoluteUri | Converts a relative URI into an absolute URI. |
202+
| <span style="word-break:normal;word-wrap:normal">ToBaseRelativePath</span> | Given a base URI (for example, a URI previously returned by `GetBaseUri`), converts an absolute URI into a URI relative to the base URI prefix. |
203203

204204
The following component navigates to the app's `Counter` component when the button is selected:
205205

@@ -220,3 +220,34 @@ The following component navigates to the app's `Counter` component when the butt
220220
}
221221
}
222222
```
223+
224+
The following component handles a location changed event. The `HandleLocationChanged` method is unhooked when `Dispose` is called by the framework. Unhooking the method permits garbage collection of the component.
225+
226+
```razor
227+
@implement IDisposable
228+
@inject NavigationManager NavigationManager
229+
230+
...
231+
232+
protected override void OnInitialized()
233+
{
234+
NavigationManager.LocationChanged += HandleLocationChanged;
235+
}
236+
237+
private void HandleLocationChanged(object sender, LocationChangedEventArgs e)
238+
{
239+
...
240+
}
241+
242+
public void Dispose()
243+
{
244+
NavigationManager.LocationChanged -= HandleLocationChanged;
245+
}
246+
```
247+
248+
<xref:Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> provides the following information about the event:
249+
250+
* <xref:Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs.Location> &ndash; The URL of the new location.
251+
* <xref:Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs.IsNavigationIntercepted> &ndash; If `true`, Blazor intercepted the navigation from the browser. If `false`, [NavigationManager.NavigateTo](xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A) caused the navigation to occur.
252+
253+
For more information on component disposal, see <xref:blazor/lifecycle#component-disposal-with-idisposable>.

0 commit comments

Comments
 (0)