内容目录
C# Writing a SignalR Client Requires Manual Injection of Client Methods:
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
this.Dispatcher.Invoke(() =>
{
var newMessage = $"{user}: {message}";
messagesList.Items.Add(newMessage);
});
});
This can be cumbersome, so we need to implement automatic scanning of instance methods to register them automatically.
Define a class to hold instance method information:
public class RegisterMethod
{
public MethodInfo MethodInfo { get; init; }
public bool IsAsync { get; init; }
public Type[] Types { get; init; }
}
Define an attribute to annotate instance methods that need binding:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class SignalRAttribute : Attribute
{
public string Name { get; private set; }
public SignalRAttribute(string name)
{
Name = name;
}
}
Then implement scanning and automatic registration:
public static class SignalRHelper<T> where T : class
{
private static Dictionary<string, RegisterMethod> MethodCache = new();
static SignalRHelper()
{
var methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var item in methods)
{
var attr = item.GetCustomAttribute<SignalRAttribute>();
if (attr == null) continue;
MethodCache.Add(attr.Name, new RegisterMethod
{
MethodInfo = item,
IsAsync = item.ReturnType == typeof(Task),
Types = item.GetParameters().Select(x => x.ParameterType).ToArray()
});
}
}
/// <summary>
/// Bind methods to HubConnection
/// </summary>
/// <param name="connection"></param>
/// <param name="targetObj"></param>
public static void Bind(HubConnection connection, T targetObj)
{
foreach (var item in MethodCache)
{
MethodInfo? onMethod;
// Has generic parameters
if (item.Value.Types.Length > 0)
{
var ms = typeof(HubConnectionExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(x => x.Name == "On" &&
x.GetGenericArguments().Length == item.Value.Types.Length &&
x.GetParameters().Length == 3
);
// Asynchronous method (HubConnection, methodName, Func<...., Task>)
if (item.Value.IsAsync)
{
onMethod = ms.FirstOrDefault(x => x.GetParameters()[2].ParameterType!.Name!.Contains("Func"));
}
// Asynchronous method (HubConnection, methodName, Action<....>)
else
{
onMethod = ms.FirstOrDefault(x => x.GetParameters()[2].ParameterType!.Name!.Contains("Action"));
}
if (onMethod != null) onMethod = onMethod.MakeGenericMethod(item.Value.Types);
}
else
{
if (item.Value.IsAsync)
{
onMethod = typeof(HubConnectionExtensions).GetMethod("On", BindingFlags.Static | BindingFlags.Public, new Type[]
{
typeof(HubConnection),
typeof(string),
typeof(Func<Task>)
});
}
else
{
onMethod = typeof(HubConnectionExtensions).GetMethod("On", BindingFlags.Static | BindingFlags.Public, new Type[]
{
typeof(HubConnection),
typeof(string),
typeof(Action)
});
}
}
if (onMethod == null) continue;
var del = Delegate.CreateDelegate(onMethod.GetParameters()[2].ParameterType, targetObj, item.Value.MethodInfo);
onMethod.Invoke(null, new object[]
{
connection,
item.Key,
del
});
}
}
}
Simply annotate instance methods:
[SignalR("A")]
public async Task A(string a, string b)
{
await Task.CompletedTask;
}
Usage:
MyService a = ....
SignalRHelper<MyService>.Bind(_connection, a);
文章评论