Skip to content

Fine grained disposing of components #1068

@linkdotnet

Description

@linkdotnet

Current state

In our current state, the TestContext and ITestRenderer offers a DisposeComponents method that basically removes all components from the RenderTree so they get disposed of. That has three flaws:

  1. It is an all-or-nothing approach. There is no way of controlling which component should be removed.
  2. The API is rather ugly as we expose DisposeComponents to too many hierarchies (and objects).
  3. If we decide to go with a Renderer instance per Render call our DisposeComponents wouldn't work anymore.

Fine-grained disposing

With v2 we have the ability to correct such things. The Renderer (from Blazor itself) offers a RemoveRootComponent that allows us the remove specific elements from the tree.

API

The API would sit on top of the IRenderFragment type and some API inside the TestRenderer:

TestRenderer.cs

internal void RemoveComponent(IRenderedFragment fragment)
			=> RemoveRootComponent(fragment.ComponentId);

Extensions for IRenderFragment:

public static void DisposeComponent(this IRenderedFragment fragment)
{
	ArgumentNullException.ThrowIfNull(fragment);

	var renderer = fragment.Services.GetRequiredService<TestRenderer>();
	renderer.RemoveComponent(fragment.ComponentId);
}

Usage

The usage would be fairly trivial:

var cut = RenderComponent<Component>();

cut.DisposeComponent();

Alternative Design

Alternatively, we already have a IDisposable implementation inside IRenderFragment. That said, we could leverage this:

internal class RenderedFragment : IRenderedFragment
{
  public void Dispose()
  {
    // Here normal dispose logic 
    testRenderer.RemoveComponent(ComponentId);
  }
}

That would feel more natural to the language itself. It seems odd to have two dedicated functions taking care of disposing (from an outside point of view).

The inherent issue with this approach is that the markup is set to empty - anyway, there should be no check on the markup if your component gets disposed of.

As the IRenderFragment already has IDisposable that could lead to side effects for users that already do things like:

using var cut = RenderComponent<...>();

Seems to be some usage like this: https://grep.app/search?q=using%20var.%2ARenderComponent&regexp=true&case=true&filter[lang][0]=C%23.

What are your thoughts @egil ?

Edit: I am leaning towards the second approach of leveraging the already existing Dispose method.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions