File: Components\AgentBoundary.cs
Web Access
Project: src\aspnetcore\src\Components\AI\src\Microsoft.AspNetCore.Components.AI.csproj (Microsoft.AspNetCore.Components.AI)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.AspNetCore.Components.Rendering;
 
namespace Microsoft.AspNetCore.Components.AI;
 
/// <summary>
/// Creates the <see cref="AgentContext"/> for a <see cref="UIAgent"/> and cascades it to the
/// chat components underneath it.
/// </summary>
public class AgentBoundary : ComponentBase, IDisposable
{
    private AgentContext _context = default!;
    private UIAgent _currentAgent = default!;
 
    /// <summary>
    /// Gets or sets the agent that drives the conversation.
    /// </summary>
    [Parameter, EditorRequired]
    public UIAgent Agent { get; set; } = default!;
 
    /// <summary>
    /// Gets or sets the content rendered inside the boundary.
    /// </summary>
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
 
    /// <inheritdoc />
    protected override void OnInitialized()
    {
        _currentAgent = Agent;
        _context = new AgentContext(Agent);
    }
 
    /// <inheritdoc />
    protected override void OnParametersSet()
    {
        if (!ReferenceEquals(Agent, _currentAgent))
        {
            // Agent changed — tear down the old context and create a new one.
            // BuildRenderTree uses OpenRegion keyed on the context, so Blazor will
            // also tear down and recreate all descendant components.
            _context?.Dispose();
            _currentAgent = Agent;
            _context = new AgentContext(Agent);
        }
    }
 
    /// <inheritdoc />
    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        // If Agent changes, the region key changes, causing Blazor to tear down
        // and recreate all descendants. This is the same trick EditForm uses with
        // EditContext. It lets us safely use IsFixed=true on the CascadingValue.
        builder.OpenRegion(_context.GetHashCode());
 
        builder.OpenComponent<CascadingValue<AgentContext>>(0);
        builder.AddComponentParameter(1, "Value", _context);
        builder.AddComponentParameter(2, "IsFixed", true);
        builder.AddComponentParameter(3, "ChildContent", (RenderFragment)(inner =>
        {
            inner.AddContent(10, ChildContent);
        }));
        builder.CloseComponent();
 
        builder.CloseRegion();
    }
 
    /// <summary>
    /// Disposes the <see cref="AgentContext"/> owned by this boundary.
    /// </summary>
    public void Dispose()
    {
        _context?.Dispose();
        GC.SuppressFinalize(this);
    }
}