{"record":{"id":"dc48f95714270c87","repo":"elsa-workflows/elsa-core","slug":"multiple-invoke-methods-were-found-use-either-invoke-or","errorCode":null,"errorMessage":"Multiple Invoke methods were found. Use either Invoke or InvokeAsync.","messagePattern":"Multiple Invoke methods were found\\. Use either Invoke or InvokeAsync\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/common/Elsa.Mediator/Middleware/MiddlewareHelpers.cs","lineNumber":26,"sourceCode":"public static class MiddlewareHelpers\n{\n    /// <summary>\n    /// Gets the Invoke or InvokeAsync method from the middleware type.\n    /// </summary>\n    /// <param name=\"middleware\">The middleware type.</param>\n    /// <returns>The Invoke or InvokeAsync method.</returns>\n    /// <exception cref=\"InvalidOperationException\">Thrown when the Invoke or InvokeAsync method cannot be found or the return type is not Task or ValueTask.</exception>\n    public static MethodInfo GetInvokeMethod(Type middleware)\n    {\n        const string invokeMethodName = \"Invoke\";\n        const string invokeAsyncMethodName = \"InvokeAsync\";\n        var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);\n        var invokeMethods = methods.Where(m => string.Equals(m.Name, invokeMethodName, StringComparison.Ordinal) || string.Equals(m.Name, invokeAsyncMethodName, StringComparison.Ordinal)).ToArray();\n\n        switch (invokeMethods.Length)\n        {\n            case > 1:\n                throw new InvalidOperationException(\"Multiple Invoke methods were found. Use either Invoke or InvokeAsync.\");\n            case 0:\n                throw new InvalidOperationException(\"No Invoke methods were found. Use either Invoke or InvokeAsync\");\n        }\n\n        var methodInfo = invokeMethods[0];\n\n        if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType) && !typeof(ValueTask).IsAssignableFrom(methodInfo.ReturnType))\n            throw new InvalidOperationException($\"The {methodInfo.Name} method must return Task or ValueTask\");\n\n        return methodInfo;\n    }\n}","sourceCodeStart":8,"sourceCodeEnd":38,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/common/Elsa.Mediator/Middleware/MiddlewareHelpers.cs#L8-L38","documentation":"MiddlewareHelpers.GetInvokeMethod reflects over a middleware type to locate its single public instance Invoke or InvokeAsync method, which the mediator pipeline uses to compose middleware. This error is thrown when the type exposes more than one public method named Invoke or InvokeAsync (overloads count). The library deliberately rejects ambiguous middleware contracts.","triggerScenarios":"Defining a middleware class with overloaded InvokeAsync methods (e.g. InvokeAsync(MyContext) and InvokeAsync(OtherContext)); a middleware with both an Invoke and an InvokeAsync method; overloads differing only by parameter types all match by name and are counted together.","commonSituations":"Developers add a second InvokeAsync overload for a different context type or for convenience/default parameters; refactoring renames one method but leaves the old overload; generic middleware written with multiple public entry points; copy-pasting middleware code that kept an unused old method.","solutions":["Keep exactly one public instance method named Invoke or InvokeAsync; rename or make the other overload private/internal.","If you need to handle multiple context types, split into separate middleware classes, one per context type.","If the extra method is a helper, rename it (e.g. InvokeCoreAsync) so it no longer collides with the pipeline entry-point name.","Make helper overloads non-public so reflection (BindingFlags.Public | BindingFlags.Instance) only finds the single entry point."],"exampleFix":"// before\npublic class MyMiddleware : IMiddleware\n{\n    public ValueTask InvokeAsync(CommandContext context, CommandMiddlewareDelegate next) => ...;\n    public ValueTask InvokeAsync(OtherContext context, OtherDelegate next) => ...; // ambiguous\n}\n\n// after\npublic class MyMiddleware : IMiddleware\n{\n    public ValueTask InvokeAsync(CommandContext context, CommandMiddlewareDelegate next) => ...;\n    private ValueTask InvokeOtherAsync(OtherContext context, OtherDelegate next) => ...;","handlingStrategy":"validation","validationCode":"// Before registering middleware, check it has exactly one Invoke/InvokeAsync entry point:\nvar names = new[] { \"Invoke\", \"InvokeAsync\" };\nvar count = middlewareType.GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)\n    .Count(m => names.Contains(m.Name, StringComparer.Ordinal));\nif (count != 1)\n    throw new InvalidOperationException($\"{middlewareType.Name} must define exactly one public Invoke or InvokeAsync method (found {count}).\");","typeGuard":"static bool HasSingleInvokeEntryPoint(Type t) =>\n    t.GetMethods(BindingFlags.Instance | BindingFlags.Public)\n     .Count(m => m.Name is \"Invoke\" or \"InvokeAsync\") == 1;","tryCatchPattern":"try\n{\n    pipeline.AddMiddleware(typeof(MyMiddleware));\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"Multiple Invoke methods\"))\n{\n    logger.LogError(ex, \"Middleware {Type} has overloads; keep a single entry point\", typeof(MyMiddleware).Name);\n    throw;\n}","preventionTips":["Implement the library's middleware interface (IMiddleware/ICommandMiddleware) and never add InvokeAsync overloads.","Name helper methods something other than Invoke/InvokeAsync.","Keep alternate logic in private methods not named Invoke/InvokeAsync.","Add a unit test registering each custom middleware to catch convention breaks early."],"tags":["mediator","middleware","reflection","pipeline"],"backgroundTag":"invalid-argument-value","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}