Up until the day before this post was first made, I have been calling JSInvokable methods just fine from any page loaded in the app. I was creating and establishing the the DotNotObjectReference objects as traditionally shown in most examples like so:
var dotNetReference = DotNetObjectReference.Create(this);
JSRuntime.InvokeVoidAsync("SetDotNetObjectRef", dotNetReference);
In the individual pages themselves, I would create the invokable methods like so:
@page "/Application/Search/Index/"
@code {
[JSInvokable]
public void InvokableMethodOnIndexPage(string id)
{
return;
}
protected override void OnAfterRender(bool isFirstRender)
{
if (isFirstRender)
{
var dotNetReference = DotNetObjectReference.Create(this);
JSRuntime.InvokeVoidAsync("SetDotNetObjectRef", dotNetReference);
}
}
}
Then, I needed to build a new JSInvokable method that was to be re-used on any page in the app, and so I placed that method in my "MainLayout.razor" component that all of my pages derive the design layout from like so:
@code {
[JSInvokable]
public void InvokableMethodOnMainLayout(string id)
{
return;
}
protected override void OnAfterRender(bool isFirstRender)
{
if (isFirstRender)
{
var dotNetReference = DotNetObjectReference.Create(this);
JSRuntime.InvokeVoidAsync("SetDotNetObjectRef", dotNetReference);
}
}
}
In my first attempts to call the new function that I placed in the "MainLayout.razor" file, I was getting the "The type 'Index' does not contain a public invokable method with [JSInvokableAttribute('InvokableMethodOnMainLayout')].".
Through my research and testing, I discovered that both of the DotNetObjectReference.Create(this)
lines in each file were clashing with each other with child instance overriding the "MainLayout" DotNetObjectReference instance. When I removed the child instance reference, the call to the method in MainLayout would execute fine but then in turn, it broke the ability to the call the InvokableMethodOnIndexPage(string id)
method.
My first thought was to alter the _Host view file to maintain an array of references and then build a helper function that would loop through the references and try/catch
each reference until it hit a reference that it could find the function I wanted to call but I decided that may not be the best approach and/or would be expensive operation.
Therefore, may I ask what the appropriate implementation here to provide the functionality to call JSInvokable methods from any component?
Regards.
So, after having a revelation and giving this some more thought, I tested a different way to address the original clashing of DotNetObjectReference
between parent and child Blazor components and came up with the following code to address the issue at hand.
The first thing was to redo/create JavaScript helper functions inside of the _Host
file to resemble the following:
window.DotNetObjectRefs = [];
window.SetDotNetObjectRef = function (dotNetObjectRef, componentName) {
if(window.DotNetObjectRefs.filter(dnor => return dnor.ComponentName === componentName).length <= 0)
{
window.DotNetObjectRefs.push({ ComponentName: componentName, Reference: dotNetObjectRef });
}
};
window.InvokeMethodAsync = function (assembly, componentName, method, args) {
for(let r = 0; r < window.DotNetObjectRefs.length; r++)
{
if (window.DotNetObjectRefs[r].ComponentName === componentName)
{
window.DotNetObjectRefs[r].Reference.invokeMethodAsync(method, args);
}
}
};
The key change to this was to hold an array of references that don't clash with each other. By creating an array to hold references, you can create multiple references and add them with a naming convention that refers to the component the reference was instantiated from. Then, when you need to call an invokable function, you just specify the component the invokable method comes from in addition to the method name and arguments.
Then, in any component that requires JSInterop capabilities, this:
JSRuntime.InvokeVoidAsync("SetDotNetObjectRef", DotNetObjectReference.Create(this));
becomes this:
JSRuntime.InvokeVoidAsync("SetDotNetObjectRef", DotNetObjectReference.Create(this), "Reference_Component_Name");
This will now allow you to define JSInvokable methods on any component and have them be called from any other component as long as you reference the component name in the Invoke
calls.