diff --git a/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj b/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj
index e427dcc..1262c3c 100644
--- a/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj
+++ b/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj
@@ -18,6 +18,9 @@
+
+
+
@@ -36,6 +39,9 @@
..\..\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Services.dll
+
+ ..\..\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Services.Server.dll
+
..\..\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Utils.dll
@@ -44,9 +50,11 @@
+
+
diff --git a/AyCode.Blazor.Components/Services/AcWebSignalRHubBase.cs b/AyCode.Blazor.Components/Services/AcWebSignalRHubBase.cs
new file mode 100644
index 0000000..cb1803b
--- /dev/null
+++ b/AyCode.Blazor.Components/Services/AcWebSignalRHubBase.cs
@@ -0,0 +1,196 @@
+using System.Linq.Expressions;
+using System.Security.Claims;
+using AyCode.Blazor.Models.Server.Models;
+using AyCode.Core;
+using AyCode.Core.Extensions;
+using AyCode.Core.Helpers;
+using AyCode.Core.Loggers;
+using AyCode.Services.Server.SignalRs;
+using AyCode.Services.SignalRs;
+using MessagePack;
+using MessagePack.Resolvers;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Configuration;
+
+namespace AyCode.Blazor.Components.Services;
+
+public abstract class AcWebSignalRHubBase(IConfiguration configuration, TLogger logger)
+ : Hub, IAcSignalRHubServer where TSignalRTags : AcSignalRTags where TLogger : AcLoggerBase
+{
+ protected readonly List> DynamicMethodCallModels = [];
+ //protected readonly TIAM.Core.Loggers.Logger> Logger = new(logWriters.ToArray());
+ protected TLogger Logger = logger;
+ protected IConfiguration Configuration = configuration;
+
+ //private readonly ServiceProviderAPIController _serviceProviderApiController;
+ //private readonly TransferDataAPIController _transferDataApiController;
+
+ //_serviceProviderApiController = serviceProviderApiController;
+ //_transferDataApiController = transferDataApiController;
+
+ // https://docs.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-3.1#strongly-typed-hubs
+ public override async Task OnConnectedAsync()
+ {
+ Logger.Debug($"Server OnConnectedAsync; ConnectionId: {Context.ConnectionId}; UserIdentifier: {Context.UserIdentifier}");
+
+ LogContextUserNameAndId();
+
+ await base.OnConnectedAsync();
+
+ //Clients.Caller.ConnectionId = Context.ConnectionId;
+ //Clients.Caller.UserIdentifier = Context.UserIdentifier;
+ }
+
+ public override async Task OnDisconnectedAsync(Exception? exception)
+ {
+ var logText = $"Server OnDisconnectedAsync; ConnectionId: {Context.ConnectionId}; UserIdentifier: {Context.UserIdentifier};";
+
+ if (exception == null) Logger.Debug(logText);
+ else Logger.Error(logText, exception);
+
+ LogContextUserNameAndId();
+
+ await base.OnDisconnectedAsync(exception);
+ }
+
+ public virtual Task OnReceiveMessage(int messageTag, byte[]? message, int? requestId)
+ {
+ return ProcessOnReceiveMessage(messageTag, message, requestId, null);
+ }
+
+ protected async Task ProcessOnReceiveMessage(int messageTag, byte[]? message, int? requestId, Func? notFoundCallback)
+ {
+ var tagName = ConstHelper.NameByValue(messageTag);
+ var logText = $"Server OnReceiveMessage; {nameof(requestId)}: {requestId}; ConnectionId: {Context.ConnectionId}; {tagName}";
+
+ if (message is { Length: 0 }) Logger.Warning($"message.Length == 0! {logText}");
+ else Logger.Info($"[{message?.Length:N0}b] {logText}");
+
+ try
+ {
+ if (AcDomain.IsDeveloperVersion) LogContextUserNameAndId();
+
+ foreach (var methodsByDeclaringObject in DynamicMethodCallModels)
+ {
+ if (!methodsByDeclaringObject.MethodsByMessageTag.TryGetValue(messageTag, out var methodInfoModel)) continue;
+
+ object[]? paramValues = null;
+
+ logText = $"Found dynamic method for the tag! method: {methodsByDeclaringObject.InstanceObject.GetType().Name}.{methodInfoModel.MethodInfo.Name}";
+
+ if (methodInfoModel.ParamInfos is { Length: > 0 })
+ {
+ Logger.Debug($"{logText}({string.Join(", ", methodInfoModel.ParamInfos.Select(x => x.Name))}); {tagName}");
+
+ paramValues = new object[methodInfoModel.ParamInfos.Length];
+
+ var firstParamType = methodInfoModel.ParamInfos[0].ParameterType;
+ if (methodInfoModel.ParamInfos.Length > 1 || firstParamType == typeof(string) || firstParamType.IsEnum || firstParamType.IsValueType || firstParamType == typeof(DateTime))
+ {
+ var msg = message!.MessagePackTo>();
+
+ for (var i = 0; i < msg.PostData.Ids.Count; i++)
+ {
+ //var obj = (string)msg.PostData.Ids[i];
+ //if (msg.PostData.Ids[i] is Guid id)
+ //{
+ // if (id.IsNullOrEmpty()) throw new NullReferenceException($"PostData.Id.IsNullOrEmpty(); Ids: {msg.PostData.Ids}");
+ // paramValues[i] = id;
+ //}
+ //else if (Guid.TryParse(obj, out id))
+ //{
+ // if (id.IsNullOrEmpty()) throw new NullReferenceException($"PostData.Id.IsNullOrEmpty(); Ids: {msg.PostData.Ids}");
+ // paramValues[i] = id;
+ //}
+ //else if (Enum.TryParse(methodInfoModel.ParameterType, obj, out var enumObj))
+ //{
+ // paramValues[i] = enumObj;
+ //}
+ //else paramValues[i] = Convert.ChangeType(obj, methodInfoModel.ParameterType);
+
+ var obj = msg.PostData.Ids[i];
+ //var config = new MapperConfiguration(cfg =>
+ //{
+ // cfg.CreateMap(obj.GetType(), methodInfoModel.ParameterType);
+ //});
+
+ //var mapper = new Mapper(config);
+ //paramValues[i] = mapper.Map(obj, methodInfoModel.ParameterType);
+
+ //paramValues[i] = obj;
+
+ var a = Array.CreateInstance(methodInfoModel.ParamInfos[i].ParameterType, 1);
+
+ if (methodInfoModel.ParamInfos[i].ParameterType == typeof(Expression))
+ {
+ //var serializer = new ExpressionSerializer(new JsonSerializer());
+ //paramValues[i] = serializer.DeserializeText((string)(obj.JsonTo(a.GetType()) as Array)?.GetValue(0)!);
+ }
+ else paramValues[i] = (obj.JsonTo(a.GetType()) as Array)?.GetValue(0)!;
+
+ }
+ }
+ else paramValues[0] = message!.MessagePackTo>(MessagePackSerializerOptions.Standard).PostDataJson.JsonTo(firstParamType)!;
+ }
+ else Logger.Debug($"{logText}(); {tagName}");
+
+ var responseDataJson = new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Success, methodInfoModel.MethodInfo.InvokeMethod(methodsByDeclaringObject.InstanceObject, paramValues));
+ var responseDataJsonKiloBytes = System.Text.Encoding.Unicode.GetByteCount(responseDataJson.ResponseData!) / 1024;
+
+ //File.WriteAllText(Path.Combine("h:", $"{requestId}.json"), responseDataJson.ResponseData);
+
+ Logger.Info($"[{responseDataJsonKiloBytes}kb] responseData serialized to json");
+ await ResponseToCaller(messageTag, responseDataJson, requestId);
+
+ return;
+ }
+
+ Logger.Debug($"Not found dynamic method for the tag! {tagName}");
+ notFoundCallback?.Invoke(tagName);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"Server OnReceiveMessage; {ex.Message}; {tagName}", ex);
+ }
+
+ await ResponseToCaller(messageTag, new SignalResponseJsonMessage(messageTag, SignalResponseStatus.Error), requestId);
+ }
+
+ protected async Task ResponseToCaller(int messageTag, ISignalRMessage message, int? requestId)
+ => await SendMessageToClient(Clients.Caller, messageTag, message, requestId);
+
+ public async Task SendMessageToUserId(string userId, int messageTag, ISignalRMessage message, int? requestId)
+ => await SendMessageToClient(Clients.User(userId), messageTag, message, requestId);
+
+ public async Task SendMessageToConnectionId(string connectionId, int messageTag, ISignalRMessage message, int? requestId)
+ => await SendMessageToClient(Clients.Client(Context.ConnectionId), messageTag, message, requestId);
+
+ protected async Task SendMessageToClient(IAcSignalRHubItemServer sendTo, int messageTag, ISignalRMessage message, int? requestId = null)
+ {
+ var responseDataMessagePack = message.ToMessagePack(ContractlessStandardResolver.Options);
+ Logger.Info($"[{(responseDataMessagePack.Length/1024)}kb] Server sending responseDataMessagePack to client; {nameof(requestId)}: {requestId}; ConnectionId: {Context.ConnectionId}; {ConstHelper.NameByValue(messageTag)}");
+
+ await sendTo.OnReceiveMessage(messageTag, responseDataMessagePack, requestId);
+ }
+
+ public async Task SendMessageToGroup(string groupId, int messageTag, string message)
+ {
+ //await Clients.Group(groupId).Post("", messageTag, message);
+ }
+
+ //[Conditional("DEBUG")]
+ private void LogContextUserNameAndId()
+ {
+ string? userName = null;
+ var userId = Guid.Empty;
+
+ if (Context.User != null)
+ {
+ userName = Context.User.Identity?.Name;
+ Guid.TryParse((string?)Context.User.FindFirstValue(ClaimTypes.NameIdentifier), out userId);
+ }
+
+ if (AcDomain.IsDeveloperVersion) Logger.WarningConditional($"SignalR.Context; userName: {userName}; userId: {userId}");
+ else Logger.Debug($"SignalR.Context; userName: {userName}; userId: {userId}");
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Blazor.Components/Services/ExtensionMethods.cs b/AyCode.Blazor.Components/Services/ExtensionMethods.cs
new file mode 100644
index 0000000..baf4d1e
--- /dev/null
+++ b/AyCode.Blazor.Components/Services/ExtensionMethods.cs
@@ -0,0 +1,18 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace AyCode.Blazor.Components.Services;
+
+public static class ExtensionMethods
+{
+ public static object? InvokeMethod(this MethodInfo methodInfo, object obj, params object[]? parameters)
+ {
+ if (methodInfo.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) is AsyncStateMachineAttribute isAsyncTask)
+ {
+ dynamic awaitable = methodInfo.Invoke(obj, parameters)!;
+ return awaitable.GetAwaiter().GetResult();
+ }
+
+ return methodInfo.Invoke(obj, parameters);
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Blazor.Components/Services/Logins/AcWebAuthService.cs b/AyCode.Blazor.Components/Services/Logins/AcWebAuthService.cs
new file mode 100644
index 0000000..1f052cf
--- /dev/null
+++ b/AyCode.Blazor.Components/Services/Logins/AcWebAuthService.cs
@@ -0,0 +1,7 @@
+namespace AyCode.Blazor.Components.Services.Logins;
+
+public class AcWebAuthService
+{
+ public virtual void Logout()
+ {}
+}
\ No newline at end of file
diff --git a/AyCode.Blazor.Controllers/AyCode.Blazor.Controllers.csproj b/AyCode.Blazor.Controllers/AyCode.Blazor.Controllers.csproj
index 4284043..e5f88ff 100644
--- a/AyCode.Blazor.Controllers/AyCode.Blazor.Controllers.csproj
+++ b/AyCode.Blazor.Controllers/AyCode.Blazor.Controllers.csproj
@@ -58,7 +58,6 @@
-
diff --git a/AyCode.Blazor.Models.Server/AyCode.Blazor.Models.Server.csproj b/AyCode.Blazor.Models.Server/AyCode.Blazor.Models.Server.csproj
index ecc9c3a..5734d80 100644
--- a/AyCode.Blazor.Models.Server/AyCode.Blazor.Models.Server.csproj
+++ b/AyCode.Blazor.Models.Server/AyCode.Blazor.Models.Server.csproj
@@ -31,6 +31,9 @@
..\..\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Models.Server.dll
+
+ ..\..\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Services.dll
+
..\..\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Services.Server.dll
@@ -39,4 +42,8 @@
+
+
+
+
diff --git a/AyCode.Blazor.Models.Server/Models/DynamicMethodCallModel.cs b/AyCode.Blazor.Models.Server/Models/DynamicMethodCallModel.cs
new file mode 100644
index 0000000..58ff192
--- /dev/null
+++ b/AyCode.Blazor.Models.Server/Models/DynamicMethodCallModel.cs
@@ -0,0 +1,35 @@
+using System.Collections.Concurrent;
+using System.Reflection;
+using AyCode.Services.SignalRs;
+
+namespace AyCode.Blazor.Models.Server.Models;
+
+public class DynamicMethodCallModel where TAttribute : TagAttribute
+{
+ public object InstanceObject { get; init; }
+ public ConcurrentDictionary> MethodsByMessageTag { get; init; } = new();
+
+
+ public DynamicMethodCallModel(Type instanceObjectType) : this(instanceObjectType, null!)
+ {
+ }
+
+ public DynamicMethodCallModel(Type instanceObjectType, params object[] constructorParams) : this(Activator.CreateInstance(instanceObjectType, constructorParams)!)
+ {
+ }
+
+ public DynamicMethodCallModel(object instanceObject)
+ {
+ InstanceObject = instanceObject;
+
+ foreach (var methodInfo in instanceObject.GetType().GetMethods())
+ {
+ if (methodInfo.GetCustomAttribute(typeof(TAttribute)) is not TAttribute attribute) continue;
+
+ if (MethodsByMessageTag.ContainsKey(attribute.MessageTag))
+ throw new Exception($"Multiple SignaRMessageTag! messageTag: {attribute.MessageTag}; methodName: {methodInfo.Name}");
+
+ MethodsByMessageTag[attribute.MessageTag] = new MethodInfoModel(attribute, methodInfo!);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AyCode.Blazor.Models.Server/Models/MethodInfoModel.cs b/AyCode.Blazor.Models.Server/Models/MethodInfoModel.cs
new file mode 100644
index 0000000..75bd3ec
--- /dev/null
+++ b/AyCode.Blazor.Models.Server/Models/MethodInfoModel.cs
@@ -0,0 +1,24 @@
+using System.Reflection;
+using AyCode.Services.SignalRs;
+
+namespace AyCode.Blazor.Models.Server.Models;
+
+public class MethodInfoModel where TAttribute : TagAttribute
+{
+ public ParameterInfo[]? ParamInfos { get; init; } = null;
+ public TAttribute Attribute { get; init; }
+ public MethodInfo MethodInfo { get; init; }
+
+ public MethodInfoModel(TAttribute attribute, MethodInfo methodInfo)
+ {
+ Attribute = attribute;
+ MethodInfo = methodInfo;
+
+ var parameters = methodInfo.GetParameters();
+
+ //if (parameters.Length > 1)
+ // throw new Exception("MethodInfoModel; parameters.Length > 1");
+
+ ParamInfos = parameters;
+ }
+}
\ No newline at end of file