0

So I found myself in a situation where I need to subclass a few Blazor components, and one of the reasons is that I need to essentially create a decorator that extends it's functionality. Part of that extension is to tack on some additional event handling. Since components use EventCallback now which isn't a delegate type, I can't simply add another handler to it like you could with multicast delegates. I can replace it, but then that means any consumer of that component cannot register any handler of their own because mine would overwrite it, so now I'm trying to wrap it. Here's a pseudo representation of the scenario and what I'm trying to do

public class OriginalBlazorComponent : ComponentBase
{
    [Parameter]
    public EventCallback<int> SomethingChanged { get; set; }

    private async Task SomeInternalProcess()
    {
        // ... some work here
        await SomethingChanged.InvokeAsync(1);
    }
}

public class MySubclassedComponent : OriginalBlazorComponent
{
    public override async Task SetParametersAsync(ParameterView parameters)
    {
        // I want to combine anything that the user may have registered with my own handling
        SomethingChanged = EventCallback.Factory.Create(this, async (int i) => 
        {
            // this causes a stack overflow because i just replaced it with this callback
            // so it's essentially calling itself by this point.
            await SomethingChanged.InvokeAsync(i); 

            await DoMyOwnStuff(i);
        });
        
        await base.SetParametersAsync(this);
    }
}

The idea here is that I'm just making sure that the user's handler has been bound by addressing this in SetParametersAsync() so that I can wrap it in a new callback that will call their handler first and then run mine after. But since it's the base component that has the property that gets invoked by the base class, that means I need to replace that specific property with my new handler, but in doing so, that means the new handler is calling the old handler which is actually now the new handler, so it's now an infinitely recursive call stack, and causes a stack overflow.

So my first thought was that if I could somehow get a copy of the original EventCallback, or at least extract its delegate so I can create a new callback, then it wouldn't be referencing itself anymore (confused because it's a struct, I thought it would always naturally be a copy), but I can't find any way to do that. I tried just using EventCallback.Factory.Create(this, SomethingChanged) in hopes that it would create a completely new instance of the callback using the same delegate, but it didn't change anything; same result.

This would of course be a non-issue if I could override the original component's SomeInternalProcess() method so that I could insert my process there before or after calling the base method, but it's a 3rd party library. Or if the SomethingChanged property itself was virtual I could override it to intercept its setter, but that is also not the case.

So in short, is there some way to achieve the same effect as a multicast delegate so that I can preserve any registered handlers but combine with my own? Or is there at least some way dereference the original EventCallback or extract its delegate so that I can create a new one?

e.g.

// how do I acheive something akin to
SomethingChanged += MyDelegate;

Update 1:

I tried "hiding" the SomethingChanged event callback by declaring my own on the child class so that I could register my own handler on the base which would include the user's handler in addition to my own. That worked in standard C# tests, but Blazor did not like it. It saw it as a duplicate property during render time and threw an exception.

Update 2:

Hackaroonie. EventCallback and EventCallback<T> both store the delegate in an internal field called Delegate. Just to see if it would work, I pulled it out via reflection and used it to create a new EventCallback that would replace the one created by the user, which would wrap both of ours together, executing theirs first and then mine. It works, and I haven't seen any strange side effects yet. But I hate it for obvious reasons. But it makes me wonder if maybe all I needed was for Microsoft to expose that field. I'm sure there's some sort of risk with it, but it's just a function pointer. So long as it's read-only, it should be fine right?

Sinaesthetic
  • 11,426
  • 28
  • 107
  • 176
  • I'm going to experiment with simply hiding `SomethingChanged' by declaring my own on my child class. So then I would add my handler to the base, and then in my handler, I'll call the clients handler since they should be separate. Feels. So. Wrong. though.... – Sinaesthetic Sep 24 '22 at 18:58
  • Welp that works in standard c# but Blazor didn't like it as it sees it as a duplicate property and throws. – Sinaesthetic Sep 24 '22 at 19:55
  • Who else, other than the parent component of a `OriginalBlazorComponent` instance, is trying to use `SomethingChanged`? And how? – MrC aka Shaun Curtis Sep 24 '22 at 20:13
  • @MrCakaShaunCurtis 'unknown' by design. It's a component, you don't know who is going to use it. It could be any other developer in the world for all you know. The design of the original component exposes that event callback so the user of that control can subscribe and do anything they want with it (e.g. when something changes the tab index, I also want to update this value so that this other control can react). I just need to insert some other things into it without breaking their ability to still use that interface. – Sinaesthetic Sep 24 '22 at 20:41
  • See my updated answer with some mods to your answer. – MrC aka Shaun Curtis Sep 25 '22 at 20:06

3 Answers3

1

Updated Answer

I struggled to get my head around what you were trying to do. However, with your answer and comments here's my summary and some code.

You have a Callback that you want to make sure always calls a base method even when invoked from a child.

I think your answer overcomplicates what you're trying to do. For the record here's my version:

public class MyBaseComponent : ComponentBase
{
    private EventCallback<string> _childSomethingChanged;
    private Func<string, Task> BaseSomethingChanged;

    [Parameter] public EventCallback<string> SomethingChanged { get; set; }

    public MyBaseComponent()
    {
        // Cache the delegate
        BaseSomethingChanged = (value) =>
        {
            return MustRunProcess(value);
        };
    }

    private async Task MustRunProcess(string value)
    {
        // run whatever processes you need in here
        await _childSomethingChanged.InvokeAsync("Updated by Base");
    }

    public override Task SetParametersAsync(ParameterView parameters)
    {
        parameters.SetParameterProperties(this);

        if (this.SomethingChanged.HasDelegate)
            _childSomethingChanged = SomethingChanged;

        // This is a little naughty 
        // you shouldn't really update a parameter set value but needs must 
        SomethingChanged = EventCallback.Factory.Create( this, this.BaseSomethingChanged);

        return base.SetParametersAsync(ParameterView.Empty);
    }
}

Here's the child component:

@inherits MyBaseComponent

<h3>MyComponent</h3>

<div class="alert alert-primary">
    @this.ChildData
</div>

<div>
    <button class="btn btn-primary" @onclick=DoUpdate>Update</button>
</div>
@code {
    private string ChildData = string.Empty;

    private void DoUpdate()
    {
        this.ChildData = DateTime.Now.ToLongTimeString();
        this.SomethingChanged.InvokeAsync("Updated by Child");
    }
}

And a test page:

@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

<MyComponent SomethingChanged=DoSomething />

<div class="alert alert-dark mt-2">
    @this.dataValue
</div>


@code {
    private string dataValue = string.Empty;

    private void DoSomething(string value)
        => this.dataValue = value;
}
MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • I don't think you're quite getting what I've illustrated in the question; It's a decorator pattern. Your example shows the original component, we are not trying to use the original component, we're trying to subclass it so that the child component is the same component (same look, same function) but with injected functionality (decorated) so that I don't have to keep wiring up the same exact functions every time I want to use it. I don't want to notify other components, I want to extend an existing component. – Sinaesthetic Sep 24 '22 at 21:24
  • NP :-) - often happens here! I've seen your answer so get what you're up to now. I'll remove this answer and take a look at yours in detail later. – MrC aka Shaun Curtis Sep 25 '22 at 07:16
0

Ok so a simple chain of responsibility pattern actually worked:

public class OriginalBlazorComponent : ComponentBase
{
    [Parameter]
    public EventCallback<int> SomethingChanged { get; set; }

    private async Task SomeInternal Process()
    {
        // ... some work here
        await SomethingChanged.InvokeAsync(1);
    }
}

public class MySubclassedComponent : OriginalBlazorComponent
{
    private EventCallback<int> _callerSomethingChanged;

    public override async Task SetParametersAsync(ParameterView parameters)
    {        
        parameters.SetParameterProperties(this);

        _callerSomethingChanged = SomethingChanged;

        SomethingChanged = EventCallback.Factory.Create(this, async (int i) => 
        {
            await _callerSomethingChanged.InvokeAsync(i);
            await DoMyStuff(i); // this is the decoration
        });
        
        await base.SetParametersAsync(ParameterView.Empty);
    }
}

This simply stores any callback that the user/developer may have defined in a private field and then replaces it with my callback which will invoke theirs and then mine. You have to call set parameters first so that their callback binds or else it'll end up overwriting yours. Then at the end, call base with an empty parameter view to indicate parameters are already set (it's a pattern within the framework, failing to do this will cause an exception).

Now from the razor side, from the user/developer perspective, instead of using the original component like this:

<OriginalBlazorComponent SomethingChanged="MyChangeHandler" />

They can use the decorated component with the extended function

<MySubclassedComponent SomethingChanged="MyChangeHandler" />

And this does so without replacing their handler, it just makes sure that my handler gets called too. And also, from the base component's perspective (the third party original component), it just operates per normal like the subclass doesn't even exist.

At the end of the day, EventCallback is what we use in Blazor, but apparently it can't be combined with other callbacks like we could with delegates (to my knowledge, anyway) otherwise this would have been pretty straight forward; I'd just add my handler to the delegate and be done with it. If someone knows a way to do that, I'd really want to know.

And then for convenience, refactor as extension:

public static class EventCallbackExt
{
    public static EventCallback<T> Chain<T>(this EventCallback<T> original, IHandleEvent receiver, Action<T> del)
    {
        var chain = new EventCallbackChainCore<T>(receiver, original, del);
        return chain.Callback;
    }

    public static EventCallback<T> Chain<T>(this EventCallback<T> original, IHandleEvent receiver, Func<T, Task> del)
    {
        var chain = new EventCallbackChainCore<T>(receiver, original, del);
        return chain.Callback;
    }

    public static EventCallback Chain(this EventCallback original, IHandleEvent receiver, Action del)
    {
        var chain = new EventCallbackChainCore(receiver, original, del);
        return chain.Callback;
    }

    public static EventCallback Chain(this EventCallback original, IHandleEvent receiver, Func<Task> del)
    {
        var chain = new EventCallbackChainCore(receiver, original, del);
        return chain.Callback;
    }

    private class EventCallbackChainCore<T>
    {
        private readonly EventCallback<T> _original;

        public EventCallback<T> Callback { get; }

        public EventCallbackChainCore(IHandleEvent receiver, EventCallback<T> original, MulticastDelegate del)
        {
            _original = original;

            var chained = new EventCallbackWorkItem(del);
            Callback = EventCallback.Factory.Create(receiver, async (T input) =>
            {
                await _original.InvokeAsync(input);
                await chained.InvokeAsync(input);
            });
        }
    }

    private class EventCallbackChainCore
    {
        private readonly EventCallback _original;

        public EventCallback Callback { get; }

        public EventCallbackChainCore(IHandleEvent receiver, EventCallback original, MulticastDelegate del)
        {
            _original = original;

            var chained = new EventCallbackWorkItem(del);
            Callback = EventCallback.Factory.Create(receiver, async () =>
            {
                await _original.InvokeAsync();
                await chained.InvokeAsync(null);
            });
        }
    }
}

So this simplifies it for the future and would be used like this:

public class MySubclassedComponent : OriginalBlazorComponent
{
    public override async Task SetParametersAsync(ParameterView parameters)
    {        
        parameters.SetParameterProperties(this);
        SomethingChanged = SomethingChanged.Chain(this, DoMyStuff);
        return base.SetParametersAsync(ParameterView.Empty);
    }

    private async Task DoMyStuff(int newIndex)
    {
        // do stuff
    }
}

The extension just does the same logic but abstracts it and the state management away so you don't have to crud up your control with private fields to hold it. Hope this helps.

Sinaesthetic
  • 11,426
  • 28
  • 107
  • 176
  • Actually in retrospect, I might have been able to just bind it, then move there's to a private field and replace SomethingChanged with my handler that would call that private field (theirs) and then run mine... far less fun but less complex and no reflection. I'll have to try that out later – Sinaesthetic Sep 24 '22 at 22:27
  • yep... that worked. Will update this answer. – Sinaesthetic Sep 24 '22 at 23:11
  • `It's a decorator pattern.` I don't think it's a decorator pattern. It's merely subclassing. Decorator pattern is supported in C#, but I'm not sure you can presently implement it in Blazor, as, though a Blazor component is a class object, it's instantiation is special because of the rendering features. In any case, what you do here is subclassing. I'll vote you up as your answer provides the developers here with some ideas how to overcome the limitations of Blazor. – enet Sep 25 '22 at 23:50
  • @enet it's not a textbook decorator in that it doesn't take and hold a reference to the original to proxy invocations. I was originally approaching the design that way, but to your point, because instantiation works differently, implementing it correctly would be very awkward. So yeah it's mostly just subclassing, but the approach taken to it is to maintain the original interface while adding additional responsibilities and remaining within the same class of objects which is the main goal of the pattern. The difference being that it's the class and not the object that is decorated I guess. – Sinaesthetic Sep 26 '22 at 18:14
  • So maybe "some blend of subclassing, decorator and CoR ideas pattern". Doesn't quite roll off the tongue the same. – Sinaesthetic Sep 26 '22 at 18:16
0

I actually needed to do something similar for a wizard, and my solution was a combination of a delegate EventHandler and event with EventCallback.

Base Class:

public class MyComponentBaseClass : ComponentBase
{
    [Parameter]
    [EditorRequired]
    public EventCallback OnClick { get; set; }

    public delegate Task OnClickInternalEventHandler();

    public event OnClickInternalEventHandler OnClickInternalEvent;

    public async Task ComponentClickMethod()
    {
        if (OnClick.HasDelegate)
            await OnClick.InvokeAsync();

        //Do the things

        await OnClickInternalEvent();
    }
}

Parent Component:

@inherits MyComponentBaseClass

<div ChildContainer>
    <CascadingValue Value="(MyComponentBaseClass)this">
        @ChildContent
    </CascadingValue>
</div>

@code {
    [Parameter]
    [EditorRequired]
    public RenderFragment ChildContent { get; set; }
}

Child Component:

<button @onclick="ParentBase.ComponentClickMethod">Execute Event on Parent</button>

@code {
    [CascadingParameter]
    public MyComponentBaseClass ParentBase { get; set; }

    protected override void OnInitialized()
    {
        ParentBase.OnClickInternalEvent += PrivateChildMethod;
    }

    async Task PrivateChildMethod() =>
        //Your code here
        Console.WriteLine("Private Child Method");
}

These delegates are very versatile and can be used in many different ways. The example above isn't how I implemented on my wizard, but should be a good and clear example to follow.

Marius
  • 1,420
  • 1
  • 11
  • 19
  • In retrospect, I could've used better naming. – Marius Sep 25 '22 at 20:26
  • Interesting, but I think that is a different scenario where you actually own the base component and could set it up that way. In my scenario, the base component is a 3rd party library that I cannot modify, so I'm stuck working with whatever their implementation was. – Sinaesthetic Oct 07 '22 at 16:49