diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6efc523..99c03ac 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,19 +1,25 @@
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TypedSignalR.Client.sln b/TypedSignalR.Client.sln
index f465083..7c496f7 100644
--- a/TypedSignalR.Client.sln
+++ b/TypedSignalR.Client.sln
@@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypedSignalR.Client.SourceGeneratorTests", "tests\TypedSignalR.Client.SourceGeneratorTests\TypedSignalR.Client.SourceGeneratorTests.csproj", "{547B45CB-8CAD-6B6B-5E80-88AE3B93A43F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -86,6 +88,10 @@ Global
{4AD46B59-DB78-465F-BF10-F0F74B34722D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AD46B59-DB78-465F-BF10-F0F74B34722D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AD46B59-DB78-465F-BF10-F0F74B34722D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {547B45CB-8CAD-6B6B-5E80-88AE3B93A43F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {547B45CB-8CAD-6B6B-5E80-88AE3B93A43F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {547B45CB-8CAD-6B6B-5E80-88AE3B93A43F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {547B45CB-8CAD-6B6B-5E80-88AE3B93A43F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -102,6 +108,7 @@ Global
{0628FEE3-2C6E-4C02-A398-4E55781A5A85} = {2F614DC6-F5D9-4872-9B4E-70F39B02D6EC}
{4D174400-02DF-4816-8CFC-A94DB3DBBBFA} = {2F614DC6-F5D9-4872-9B4E-70F39B02D6EC}
{4AD46B59-DB78-465F-BF10-F0F74B34722D} = {196DB73F-4D3E-4A90-AE48-40925CB33560}
+ {547B45CB-8CAD-6B6B-5E80-88AE3B93A43F} = {2F614DC6-F5D9-4872-9B4E-70F39B02D6EC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3C64E712-2479-4BF8-9169-B21DC3E787EC}
diff --git a/src/TypedSignalR.Client/CodeAnalysis/MethodMetadata.cs b/src/TypedSignalR.Client/CodeAnalysis/MethodMetadata.cs
index b974302..76db70e 100644
--- a/src/TypedSignalR.Client/CodeAnalysis/MethodMetadata.cs
+++ b/src/TypedSignalR.Client/CodeAnalysis/MethodMetadata.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
@@ -12,6 +13,7 @@ public sealed class MethodMetadata
public IReadOnlyList Parameters { get; }
public string MethodName { get; }
+ public string SignalRMethodName { get; }
public string ReturnType { get; }
public bool IsGenericReturnType { get; }
public string? GenericReturnTypeArgument { get; }
@@ -25,6 +27,15 @@ public MethodMetadata(IMethodSymbol methodSymbol)
.Select(x => new ParameterMetadata(x))
.ToArray();
+ SignalRMethodName =
+ (
+ methodSymbol.GetAttributes()
+ .FirstOrDefault(x => x.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.SignalR.HubMethodNameAttribute")
+ ?.ConstructorArguments.SingleOrDefault()
+ )
+ ?.Value?.ToString()
+ ?? MethodName;
+
ReturnType = methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormatRule.FullyQualifiedNullableReferenceTypeFormat);
INamedTypeSymbol? returnTypeSymbol = methodSymbol.ReturnType as INamedTypeSymbol;
diff --git a/src/TypedSignalR.Client/Templates/HubConnectionExtensionsBinderTemplate.cs b/src/TypedSignalR.Client/Templates/HubConnectionExtensionsBinderTemplate.cs
index c037610..fc99520 100644
--- a/src/TypedSignalR.Client/Templates/HubConnectionExtensionsBinderTemplate.cs
+++ b/src/TypedSignalR.Client/Templates/HubConnectionExtensionsBinderTemplate.cs
@@ -106,7 +106,7 @@ private string CreateRegistrationString(TypeMetadata receiverType)
private string CreateRegistrationStringCore(MethodMetadata method)
{
return $$"""
- compositeDisposable.Add(global::Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions.On(connection, nameof(receiver.{{method.MethodName}}), {{method.CreateParameterTypeArrayString(_specialSymbols)}}, HandlerConverter.Convert{{method.CreateTypeArgumentsStringForHandlerConverter(_specialSymbols)}}(receiver.{{method.MethodName}})));
+ compositeDisposable.Add(global::Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions.On(connection, "{{method.SignalRMethodName}}", {{method.CreateParameterTypeArrayString(_specialSymbols)}}, HandlerConverter.Convert{{method.CreateTypeArgumentsStringForHandlerConverter(_specialSymbols)}}(receiver.{{method.MethodName}})));
""";
}
}
diff --git a/src/TypedSignalR.Client/Templates/MethodMetadataExtensions.cs b/src/TypedSignalR.Client/Templates/MethodMetadataExtensions.cs
index 404fc02..bbd158e 100644
--- a/src/TypedSignalR.Client/Templates/MethodMetadataExtensions.cs
+++ b/src/TypedSignalR.Client/Templates/MethodMetadataExtensions.cs
@@ -278,7 +278,7 @@ private static string CreateUnaryMethodString(MethodMetadata method)
return $$"""
public {{method.ReturnType}} {{method.MethodName}}({{method.CreateParametersString()}})
{
- return global::Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions.InvokeCoreAsync{{method.CreateGenericReturnTypeArgumentString()}}(_connection, nameof({{method.MethodName}}), {{method.CreateArgumentsString()}}, _cancellationToken);
+ return global::Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions.InvokeCoreAsync{{method.CreateGenericReturnTypeArgumentString()}}(_connection, "{{method.SignalRMethodName}}", {{method.CreateArgumentsString()}}, _cancellationToken);
}
""";
}
@@ -288,7 +288,7 @@ private static string CreateServerStreamingMethodAsAsyncEnumerableString(MethodM
return $$"""
public {{method.ReturnType}} {{method.MethodName}}({{method.CreateParametersString()}})
{
- return _connection.StreamAsyncCore{{method.CreateGenericReturnTypeArgumentString()}}(nameof({{method.MethodName}}), {{method.CreateArgumentsStringExceptCancellationToken(specialSymbols)}}, {{method.CreateCancellationTokenString("_cancellationToken", specialSymbols)}});
+ return _connection.StreamAsyncCore{{method.CreateGenericReturnTypeArgumentString()}}("{{method.SignalRMethodName}}", {{method.CreateArgumentsStringExceptCancellationToken(specialSymbols)}}, {{method.CreateCancellationTokenString("_cancellationToken", specialSymbols)}});
}
""";
}
@@ -298,7 +298,7 @@ private static string CreateServerStreamingMethodAsTaskAsyncEnumerableString(Met
return $$"""
public {{method.ReturnType}} {{method.MethodName}}({{method.CreateParametersString()}})
{
- var ret = _connection.StreamAsyncCore{{method.CreateGenericReturnTypeArgumentStringForStreaming()}}(nameof({{method.MethodName}}), {{method.CreateArgumentsStringExceptCancellationToken(specialSymbols)}}, {{method.CreateCancellationTokenString("_cancellationToken", specialSymbols)}});
+ var ret = _connection.StreamAsyncCore{{method.CreateGenericReturnTypeArgumentStringForStreaming()}}("{{method.SignalRMethodName}}", {{method.CreateArgumentsStringExceptCancellationToken(specialSymbols)}}, {{method.CreateCancellationTokenString("_cancellationToken", specialSymbols)}});
return global::System.Threading.Tasks.Task.FromResult(ret);
}
""";
@@ -309,7 +309,7 @@ private static string CreateServerStreamingMethodAsChannelString(MethodMetadata
return $$"""
public {{method.ReturnType}} {{method.MethodName}}({{method.CreateParametersString()}})
{
- return global::Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions.StreamAsChannelCoreAsync{{method.CreateGenericReturnTypeArgumentStringForStreaming()}}(_connection, nameof({{method.MethodName}}), {{method.CreateArgumentsStringExceptCancellationToken(specialSymbols)}}, {{method.CreateCancellationTokenString("_cancellationToken", specialSymbols)}});
+ return global::Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions.StreamAsChannelCoreAsync{{method.CreateGenericReturnTypeArgumentStringForStreaming()}}(_connection, "{{method.SignalRMethodName}}", {{method.CreateArgumentsStringExceptCancellationToken(specialSymbols)}}, {{method.CreateCancellationTokenString("_cancellationToken", specialSymbols)}});
}
""";
}
@@ -319,7 +319,7 @@ private static string CreateClientStreamingMethodAsAsyncEnumerableString(MethodM
return $$"""
public {{method.ReturnType}} {{method.MethodName}}({{method.CreateParametersString()}})
{
- return _connection.SendCoreAsync(nameof({{method.MethodName}}), {{method.CreateArgumentsString()}}, _cancellationToken);
+ return _connection.SendCoreAsync("{{method.SignalRMethodName}}", {{method.CreateArgumentsString()}}, _cancellationToken);
}
""";
}
@@ -330,7 +330,7 @@ private static string CreateClientStreamingMethodAsChannelString(MethodMetadata
public {{method.ReturnType}} {{method.MethodName}}({{method.CreateParametersString()}})
{
- return _connection.SendCoreAsync(nameof({{method.MethodName}}), {{method.CreateArgumentsString()}}, _cancellationToken);
+ return _connection.SendCoreAsync("{{method.SignalRMethodName}}", {{method.CreateArgumentsString()}}, _cancellationToken);
}
""";
}
diff --git a/tests/TypedSignalR.Client.SourceGeneratorTests/CompilationHelper.cs b/tests/TypedSignalR.Client.SourceGeneratorTests/CompilationHelper.cs
new file mode 100644
index 0000000..9c81ff9
--- /dev/null
+++ b/tests/TypedSignalR.Client.SourceGeneratorTests/CompilationHelper.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace TypedSignalR.Client.SourceGeneratorTests;
+
+using System.Collections.Generic;
+using global::TypedSignalR.Client;
+
+public static class CompilationHelper
+{
+ public static (ImmutableArray diagnostics, Dictionary outputs) GetGeneratedOutput(string source)
+ {
+ const LanguageVersion LanguageVersion = LanguageVersion.CSharp12;
+
+ var generator = new SourceGenerator();
+
+ var sourceSyntaxTree = CSharpSyntaxTree.ParseText(source, path: "source.cs");
+ var parsingOptions = new CSharpParseOptions(LanguageVersion);
+ var sourceSyntaxTreeWithOptions = sourceSyntaxTree.WithRootAndOptions(sourceSyntaxTree.GetRoot(), parsingOptions);
+
+ var references = AppDomain.CurrentDomain.GetAssemblies()
+ .Where(assembly => !assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location))
+ .Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
+ .Concat([
+ MetadataReference.CreateFromFile(generator.GetType().Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.Threading.Tasks.Task).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.SignalR.HubMethodNameAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.SignalR.Client.HubConnection).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.DisplayAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.CodeDom.Compiler.GeneratedCodeAttribute).Assembly.Location)
+ ]);
+
+ var compilation = CSharpCompilation.Create(
+ "generator",
+ [sourceSyntaxTreeWithOptions],
+ references,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+
+ GeneratorDriver driver =
+ CSharpGeneratorDriver.Create(
+ [generator.AsSourceGenerator()],
+ driverOptions: new GeneratorDriverOptions(
+ disabledOutputs: IncrementalGeneratorOutputKind.None,
+ trackIncrementalGeneratorSteps: true),
+ optionsProvider: null,
+ parseOptions: new CSharpParseOptions(LanguageVersion));
+
+ driver = driver.RunGenerators(compilation);
+
+ var runResult = driver.GetRunResult();
+
+ return (runResult.Diagnostics, runResult.Results.SelectMany(r => r.GeneratedSources).ToDictionary(s => s.HintName, s => s.SourceText.ToString()));
+ }
+}
diff --git a/tests/TypedSignalR.Client.SourceGeneratorTests/HubMethodNameTests.cs b/tests/TypedSignalR.Client.SourceGeneratorTests/HubMethodNameTests.cs
new file mode 100644
index 0000000..5a5eff2
--- /dev/null
+++ b/tests/TypedSignalR.Client.SourceGeneratorTests/HubMethodNameTests.cs
@@ -0,0 +1,1214 @@
+using System.Threading.Tasks;
+using TypedSignalR.Client.SourceGeneratorTests;
+using Xunit;
+
+namespace TypedSignalR.Client.SourceGenerator.Tests;
+
+public class HubMethodNameTests
+{
+ [Fact]
+ public async Task InvokerMethodWithoutUseHubMethodName()
+ {
+ // Arrange
+
+ var source = """
+ using Microsoft.AspNetCore.SignalR;
+ using Microsoft.AspNetCore.SignalR.Client;
+ using TypedSignalR.Client;
+
+ public interface IHubInvokerContractWithHubMethodName
+ {
+ System.Threading.Tasks.Task SendMessage();
+ }
+
+ public static class HubConnectionExtensions
+ {
+ public static IHubInvokerContractWithHubMethodName CreateHubProxy(this HubConnection connection)
+ {
+ return connection.CreateHubProxy();
+ }
+ }
+ """;
+
+ // Act
+
+ var (diagnostics, outputs) = CompilationHelper.GetGeneratedOutput(source);
+
+ // Assert
+
+ Assert.Empty(diagnostics);
+
+ Assert.Equal(4, outputs.Count);
+
+ Assert_TypedSignalR_Client_Components_Generated(outputs);
+
+ Assert_TypedSignalR_Client_HubConnectionExtensions_Generated(outputs);
+
+ Assert.Equal(
+ """
+ //
+ // THIS (.cs) FILE IS GENERATED BY TypedSignalR.Client
+ //
+ #nullable enable
+ #pragma warning disable CS1591
+ #pragma warning disable CS8767
+ #pragma warning disable CS8613
+ namespace TypedSignalR.Client
+ {
+ internal static partial class HubConnectionExtensions
+ {
+ private static partial global::System.Collections.Generic.Dictionary CreateBinders()
+ {
+ var binders = new global::System.Collections.Generic.Dictionary();
+
+
+ return binders;
+ }
+ }
+ }
+ #pragma warning restore CS8613
+ #pragma warning restore CS8767
+ #pragma warning restore CS1591
+
+ """.Replace("\r\n", "\n"),
+ outputs["TypedSignalR.Client.HubConnectionExtensions.Binder.Generated.cs"]);
+
+ Assert.Equal(
+ """
+ //
+ // THIS (.cs) FILE IS GENERATED BY TypedSignalR.Client
+ //
+ #nullable enable
+ #pragma warning disable CS1591
+ #pragma warning disable CS8767
+ #pragma warning disable CS8613
+ namespace TypedSignalR.Client
+ {
+ internal static partial class HubConnectionExtensions
+ {
+ private sealed class HubInvokerFor_global__IHubInvokerContractWithHubMethodName : global::IHubInvokerContractWithHubMethodName, IHubInvoker
+ {
+ private readonly global::Microsoft.AspNetCore.SignalR.Client.HubConnection _connection;
+ private readonly global::System.Threading.CancellationToken _cancellationToken;
+
+ public HubInvokerFor_global__IHubInvokerContractWithHubMethodName(global::Microsoft.AspNetCore.SignalR.Client.HubConnection connection, global::System.Threading.CancellationToken cancellationToken)
+ {
+ _connection = connection;
+ _cancellationToken = cancellationToken;
+ }
+
+ public global::System.Threading.Tasks.Task SendMessage()
+ {
+ return global::Microsoft.AspNetCore.SignalR.Client.HubConnectionExtensions.InvokeCoreAsync(_connection, "SendMessage", global::System.Array.Empty