Jsinvokable Only On Public Method

ID

csharp.jsinvokable_only_on_public_method

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

attributes, interop, reliability

Description

Reports a method carrying the [JSInvokable] attribute that is not public. Both instance and static methods are covered, and the accessibility is the effective one: a class or struct member written with no access modifier at all is private and is reported, while an interface member without a modifier is public and is not.

protected, internal, protected internal and private protected are all narrower than public and are reported. The attribute is recognised in its short form, in the explicit JSInvokableAttribute form and qualified with Microsoft.JSInterop.

Rationale

The JavaScript interop dispatcher resolves the target of DotNet.invokeMethodAsync by reflecting over the public members of the type. A method marked [JSInvokable] with any narrower accessibility can never be reached from JavaScript, so the attribute is dead code and every call from the browser fails at run time with a "method not found" error.

Nothing warns about it beforehand. The attribute is valid on a method of any accessibility, so the compiler is satisfied and the declaration reads exactly like a working one. The defect surfaces only when the interop call is exercised, and the error arrives on the JavaScript side, far from the C# declaration that caused it.

public class Counter
{
    [JSInvokable]
    public void Increment() { }             // OK

    [JSInvokable]
    public static void Reset() { }          // OK, static is equally invokable

    [JSInvokable]
    private void Reload() { }               // FLAW, unreachable from JavaScript

    [JSInvokable]
    void Restore() { }                      // FLAW, a class member is private by default

    public void NotExposed() { }            // OK, no attribute
}

Remediation

Make the annotated method public. The attribute is the statement that the method is part of the component’s JavaScript-facing surface, so its accessibility has to say the same thing:

[JSInvokable]
public void Reload() { }

If the method is not meant to be called from JavaScript, remove the attribute instead — keeping it on a non-public method leaves a promise in the source that the runtime cannot keep.