diff --git a/.editorconfig b/.editorconfig
index 879a997..5b17aa8 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,28 +1,89 @@
-[*.{cs,vb}]
+# https://editorconfig.org/
+root = true
-# IDE0060: 사용하지 않는 매개 변수를 제거하세요.
-dotnet_code_quality_unused_parameters = non_public:none
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+tab_width = 8
+trim_trailing_whitespace = true
+max_line_length = 80
+indent_style = space
+continuation_indent_size = 4
+indent_size = 4
-# IDE0003: 한정자 제거
-dotnet_style_qualification_for_event = false:none
+[*.{json,ps1,sh,yaml,yml}]
+indent_size = 2
+continuation_indent_size = 2
-# IDE0003: 한정자 제거
-dotnet_style_qualification_for_field = false:none
+[*.{csproj,xml}]
+indent_size = 2
+quote_type = double
-# IDE0003: 한정자 제거
-dotnet_style_qualification_for_method = false:none
+[*.cs]
+max_line_length = 100
+curly_bracket_next_line = true
+spaces_around_operators = true
+indent_brace_style = Allman
+dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
+dotnet_naming_symbols.public_symbols.applicable_kinds = property,method,field,event,delegate
+dotnet_naming_symbols.public_symbols.applicable_accessibilities = public
+dotnet_naming_rule.public_members_must_be_capitalized.style = first_word_upper_case_style
+dotnet_naming_style.first_word_upper_case_style.capitalization = first_word_upper
+dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
-# IDE0003: 한정자 제거
-dotnet_style_qualification_for_property = false:none
+# SA0001: XML comment analysis is disabled due to project configuration
+dotnet_diagnostic.SA0001.severity = none
-# IDE0059: 불필요한 값 할당
-csharp_style_unused_value_assignment_preference = unused_local_variable:suggestion
+# SA1101: Prefix local calls with this
+dotnet_diagnostic.SA1101.severity = none
-# IDE0056: 인덱스 연산자 사용
-csharp_style_prefer_index_operator = true:silent
+# SA1309: Field names should not begin with underscore
+dotnet_diagnostic.SA1309.severity = none
-# IDE0032: auto 속성 사용
-dotnet_style_prefer_auto_properties = true:suggestion
+# SA1600: Elements should be documented
+dotnet_diagnostic.SA1600.severity = none
-# IDE0057: 범위 연산자 사용
-csharp_style_prefer_range_operator = false
+# S1133: Deprecated code should be removed
+dotnet_diagnostic.S1133.severity = none
+
+# S1125: Boolean literals should not be redundant
+dotnet_diagnostic.S1125.severity = none
+
+# S2372: Exceptions should not be thrown from property getters
+dotnet_diagnostic.S2372.severity = none
+
+# S3881: "IDisposable" should be implemented correctly
+dotnet_diagnostic.S3881.severity = none
+
+# S3971: "GC.SuppressFinalize" should not be called
+dotnet_diagnostic.S3971.severity = none
+
+# S4143: Collection elements should not be replaced unconditionally
+dotnet_diagnostic.S4143.severity = none
+
+# S6605: Collection-specific "Exists" method should be used instead of the "Any" extension
+dotnet_diagnostic.S6605.severity = none
+
+# S6608: Prefer indexing instead of "Enumerable" methods on types implementing "IList"
+dotnet_diagnostic.S6608.severity = none
+
+# MEN007: Use a single return
+dotnet_diagnostic.MEN007.severity = none
+
+# MEN016: Avoid top-level statements
+dotnet_diagnostic.MEN016.severity = none
+
+[*.csproj]
+quote_type = double
+
+[*.sln]
+indent_style = tab
+indent_size = 2
+
+[hooks/*]
+indent_size = 2
+continuation_indent_size = 2
+
+[stylecop.json]
+max_line_length =
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b460f96..cad5ced 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,9 @@
{
+ "[csharp]": {
+ "editor.rulers": [
+ 100
+ ]
+ },
"files.exclude": {
".vs": true,
"**/bin": true,
diff --git a/Directory.Build.props b/Directory.Build.props
index cf60c78..b9dfd85 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -21,7 +21,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. -->
+
+
+
+
+ all
+
+ runtime; build; native; contentfiles; analyzers
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+ Menees.Analyzers.Settings.xml
+
+
+
net6.0;net7.0;net8.0;netstandard2.1net8.0
@@ -51,8 +75,10 @@ SOFTWARE. -->
communication$(MSBuildThisFileDirectory).build/public.snk
+
+
\ No newline at end of file
diff --git a/Menees.Analyzers.Settings.xml b/Menees.Analyzers.Settings.xml
new file mode 100644
index 0000000..fdd5b33
--- /dev/null
+++ b/Menees.Analyzers.Settings.xml
@@ -0,0 +1,5 @@
+
+
+ 100
+ 200
+
diff --git a/src/JSSoft.Communication.Client/ClientContext.cs b/src/JSSoft.Communication.Client/ClientContext.cs
index 4029d11..c4e85ba 100644
--- a/src/JSSoft.Communication.Client/ClientContext.cs
+++ b/src/JSSoft.Communication.Client/ClientContext.cs
@@ -1,32 +1,15 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System.ComponentModel.Composition;
-namespace JSSoft.Communication.ConsoleApp;
+namespace JSSoft.Communication.Client;
[Export(typeof(IServiceContext))]
[method: ImportingConstructor]
-sealed class ClientContext([ImportMany] IService[] services)
+internal sealed class ClientContext([ImportMany] IService[] services)
: Communication.ClientContext(services)
{
}
diff --git a/src/JSSoft.Communication.Client/Program.cs b/src/JSSoft.Communication.Client/Program.cs
index 6100944..2405340 100644
--- a/src/JSSoft.Communication.Client/Program.cs
+++ b/src/JSSoft.Communication.Client/Program.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using JSSoft.Communication.ConsoleApp;
diff --git a/src/JSSoft.Communication.Client/Services/DataService.cs b/src/JSSoft.Communication.Client/Services/DataService.cs
index 6376cbc..2342c4b 100644
--- a/src/JSSoft.Communication.Client/Services/DataService.cs
+++ b/src/JSSoft.Communication.Client/Services/DataService.cs
@@ -1,37 +1,22 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
+using JSSoft.Communication.Services;
-namespace JSSoft.Communication.Services;
+namespace JSSoft.Communication.Client.Services;
[Export(typeof(IService))]
[Export(typeof(IDataService))]
-sealed class DataService : ClientService, IDataService
+internal sealed class DataService : ClientService, IDataService
{
- public Task CreateDataBaseAsync(string dataBaseName, CancellationToken cancellationToken)
+ public Task CreateDataBaseAsync(
+ string dataBaseName, CancellationToken cancellationToken)
{
return Server.CreateDataBaseAsync(dataBaseName, cancellationToken);
}
diff --git a/src/JSSoft.Communication.Client/Services/UserService.cs b/src/JSSoft.Communication.Client/Services/UserService.cs
index a86dba8..4409480 100644
--- a/src/JSSoft.Communication.Client/Services/UserService.cs
+++ b/src/JSSoft.Communication.Client/Services/UserService.cs
@@ -1,103 +1,86 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
+using JSSoft.Communication.Services;
-namespace JSSoft.Communication.Services;
+namespace JSSoft.Communication.Client.Services;
[Export(typeof(IService))]
[Export(typeof(IUserService))]
[Export(typeof(INotifyUserService))]
-sealed class UserService : ClientService, IUserService, IUserCallback, INotifyUserService
+internal sealed class UserService
+ : ClientService, IUserService, IUserCallback, INotifyUserService
{
- public Task CreateAsync(Guid token, string userID, string password, Authority authority, CancellationToken cancellationToken)
- => Server.CreateAsync(token, userID, password, authority, cancellationToken);
-
- public Task DeleteAsync(Guid token, string userID, CancellationToken cancellationToken)
- => Server.DeleteAsync(token, userID, cancellationToken);
+ public event EventHandler? LoggedIn;
- public Task RenameAsync(Guid token, string userName, CancellationToken cancellationToken)
- => Server.RenameAsync(token, userName, cancellationToken);
+ public event EventHandler? LoggedOut;
- public Task SetAuthorityAsync(Guid token, string userID, Authority authority, CancellationToken cancellationToken)
- => Server.SetAuthorityAsync(token, userID, authority, cancellationToken);
+ public event EventHandler? Created;
- public Task LoginAsync(string userID, string password, CancellationToken cancellationToken)
- => Server.LoginAsync(userID, password, cancellationToken);
+ public event EventHandler? Deleted;
- public Task LogoutAsync(Guid token, CancellationToken cancellationToken)
- => Server.LogoutAsync(token, cancellationToken);
+ public event EventHandler? MessageReceived;
- public Task<(string userName, Authority authority)> GetInfoAsync(Guid token, string userID, CancellationToken cancellationToken)
- => Server.GetInfoAsync(token, userID, cancellationToken);
+ public event EventHandler? Renamed;
- public Task GetUsersAsync(Guid token, CancellationToken cancellationToken)
- => Server.GetUsersAsync(token, cancellationToken);
+ public event EventHandler? AuthorityChanged;
- public Task IsOnlineAsync(Guid token, string userID, CancellationToken cancellationToken)
- => Server.IsOnlineAsync(token, userID, cancellationToken);
+ public Task CreateAsync(UserCreateOptions options, CancellationToken cancellationToken)
+ => Server.CreateAsync(options, cancellationToken);
- public Task SendMessageAsync(Guid token, string userID, string message, CancellationToken cancellationToken)
- => Server.SendMessageAsync(token, userID, message, cancellationToken);
+ public Task DeleteAsync(UserDeleteOptions options, CancellationToken cancellationToken)
+ => Server.DeleteAsync(options, cancellationToken);
- public event EventHandler? LoggedIn;
+ public Task RenameAsync(UserRenameOptions options, CancellationToken cancellationToken)
+ => Server.RenameAsync(options, cancellationToken);
- public event EventHandler? LoggedOut;
+ public Task SetAuthorityAsync(UserAuthorityOptions options, CancellationToken cancellationToken)
+ => Server.SetAuthorityAsync(options, cancellationToken);
- public event EventHandler? Created;
+ public Task LoginAsync(UserLoginOptions options, CancellationToken cancellationToken)
+ => Server.LoginAsync(options, cancellationToken);
- public event EventHandler? Deleted;
+ public Task LogoutAsync(Guid token, CancellationToken cancellationToken)
+ => Server.LogoutAsync(token, cancellationToken);
- public event EventHandler? MessageReceived;
+ public Task GetInfoAsync(
+ Guid token, string userId, CancellationToken cancellationToken)
+ => Server.GetInfoAsync(token, userId, cancellationToken);
- public event EventHandler? Renamed;
+ public Task GetUsersAsync(Guid token, CancellationToken cancellationToken)
+ => Server.GetUsersAsync(token, cancellationToken);
- public event EventHandler? AuthorityChanged;
+ public Task IsOnlineAsync(Guid token, string userId, CancellationToken cancellationToken)
+ => Server.IsOnlineAsync(token, userId, cancellationToken);
- #region IUserCallback
+ public Task SendMessageAsync(
+ UserSendMessageOptions options, CancellationToken cancellationToken)
+ => Server.SendMessageAsync(options, cancellationToken);
- void IUserCallback.OnCreated(string userID)
- => Created?.Invoke(this, new UserEventArgs(userID));
+ void IUserCallback.OnCreated(string userId)
+ => Created?.Invoke(this, new UserEventArgs(userId));
- void IUserCallback.OnDeleted(string userID)
- => Deleted?.Invoke(this, new UserEventArgs(userID));
+ void IUserCallback.OnDeleted(string userId)
+ => Deleted?.Invoke(this, new UserEventArgs(userId));
- void IUserCallback.OnLoggedIn(string userID)
- => LoggedIn?.Invoke(this, new UserEventArgs(userID));
+ void IUserCallback.OnLoggedIn(string userId)
+ => LoggedIn?.Invoke(this, new UserEventArgs(userId));
- void IUserCallback.OnLoggedOut(string userID)
- => LoggedOut?.Invoke(this, new UserEventArgs(userID));
+ void IUserCallback.OnLoggedOut(string userId)
+ => LoggedOut?.Invoke(this, new UserEventArgs(userId));
void IUserCallback.OnMessageReceived(string sender, string receiver, string message)
=> MessageReceived?.Invoke(this, new UserMessageEventArgs(sender, receiver, message));
- void IUserCallback.OnRenamed(string userID, string userName)
- => Renamed?.Invoke(this, new UserNameEventArgs(userID, userName));
-
- void IUserCallback.OnAuthorityChanged(string userID, Authority authority)
- => AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userID, authority));
+ void IUserCallback.OnRenamed(string userId, string userName)
+ => Renamed?.Invoke(this, new UserNameEventArgs(userId, userName));
- #endregion
+ void IUserCallback.OnAuthorityChanged(string userId, Authority authority)
+ => AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userId, authority));
}
diff --git a/src/JSSoft.Communication.Server/Program.cs b/src/JSSoft.Communication.Server/Program.cs
index 6100944..2405340 100644
--- a/src/JSSoft.Communication.Server/Program.cs
+++ b/src/JSSoft.Communication.Server/Program.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using JSSoft.Communication.ConsoleApp;
diff --git a/src/JSSoft.Communication.Server/ServerContext.cs b/src/JSSoft.Communication.Server/ServerContext.cs
index efe490f..514dcfa 100644
--- a/src/JSSoft.Communication.Server/ServerContext.cs
+++ b/src/JSSoft.Communication.Server/ServerContext.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System.ComponentModel.Composition;
@@ -26,7 +9,7 @@ namespace JSSoft.Communication.ConsoleApp;
[Export(typeof(IServiceContext))]
[method: ImportingConstructor]
-sealed class ServerContext([ImportMany] IService[] services)
+internal sealed class ServerContext([ImportMany] IService[] services)
: Communication.ServerContext(services)
{
}
diff --git a/src/JSSoft.Communication.Server/Services/DataService.cs b/src/JSSoft.Communication.Server/Services/DataService.cs
index ee68929..37f6465 100644
--- a/src/JSSoft.Communication.Server/Services/DataService.cs
+++ b/src/JSSoft.Communication.Server/Services/DataService.cs
@@ -1,37 +1,21 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
+using JSSoft.Communication.Services;
using JSSoft.Communication.Threading;
-namespace JSSoft.Communication.Services;
+namespace JSSoft.Communication.Server.Services;
[Export(typeof(IService))]
[Export(typeof(IDataService))]
-sealed class DataService : ServerService, IDataService, IDisposable
+internal sealed class DataService : ServerService, IDataService, IDisposable
{
private readonly HashSet _dataBases = [];
private Dispatcher? _dispatcher;
@@ -41,26 +25,31 @@ public DataService()
_dispatcher = new Dispatcher(this);
}
- public Task CreateDataBaseAsync(string dataBaseName, CancellationToken cancellationToken)
+ public Dispatcher Dispatcher => _dispatcher ?? throw new ObjectDisposedException($"{this}");
+
+ public Task CreateDataBaseAsync(
+ string dataBaseName, CancellationToken cancellationToken)
{
- return Dispatcher.InvokeAsync(() =>
+ return Dispatcher.InvokeAsync(() => CreateDataBase(dataBaseName));
+
+ DateTime CreateDataBase(string dataBaseName)
{
if (_dataBases.Contains(dataBaseName) == true)
- throw new ArgumentNullException(nameof(dataBaseName));
+ {
+ throw new ArgumentException("The database already exists.", nameof(dataBaseName));
+ }
+
_dataBases.Add(dataBaseName);
return DateTime.UtcNow;
- });
+ }
}
public void Dispose()
{
- if (_dispatcher == null)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_dispatcher == null, this);
_dispatcher.Dispose();
_dispatcher = null;
GC.SuppressFinalize(this);
}
-
- public Dispatcher Dispatcher => _dispatcher ?? throw new ObjectDisposedException($"{this}");
-}
\ No newline at end of file
+}
diff --git a/src/JSSoft.Communication.Server/Services/User.cs b/src/JSSoft.Communication.Server/Services/User.cs
new file mode 100644
index 0000000..7fcbb62
--- /dev/null
+++ b/src/JSSoft.Communication.Server/Services/User.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using JSSoft.Communication.Services;
+
+namespace JSSoft.Communication.Server.Services;
+
+internal sealed class User
+{
+ public string UserId { get; set; } = string.Empty;
+
+ public string UserName { get; set; } = string.Empty;
+
+ public string Password { get; set; } = string.Empty;
+
+ public Authority Authority { get; set; }
+
+ public Guid Token { get; set; }
+}
diff --git a/src/JSSoft.Communication.Server/Services/UserInfo.cs b/src/JSSoft.Communication.Server/Services/UserInfo.cs
deleted file mode 100644
index f71c857..0000000
--- a/src/JSSoft.Communication.Server/Services/UserInfo.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-
-using System;
-
-namespace JSSoft.Communication.Services;
-
-class UserInfo
-{
- public string UserID { get; set; } = string.Empty;
-
- public string UserName { get; set; } = string.Empty;
-
- public string Password { get; set; } = string.Empty;
-
- public Authority Authority { get; set; }
-
- public Guid Token { get; set; }
-}
\ No newline at end of file
diff --git a/src/JSSoft.Communication.Server/Services/UserService.cs b/src/JSSoft.Communication.Server/Services/UserService.cs
index af765b4..bb5bf6f 100644
--- a/src/JSSoft.Communication.Server/Services/UserService.cs
+++ b/src/JSSoft.Communication.Server/Services/UserService.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Generic;
@@ -26,25 +9,27 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using JSSoft.Communication.Services;
using JSSoft.Communication.Threading;
-namespace JSSoft.Communication.Services;
+namespace JSSoft.Communication.Server.Services;
[Export(typeof(IService))]
[Export(typeof(IUserService))]
[Export(typeof(INotifyUserService))]
-sealed class UserService : ServerService, IUserService, INotifyUserService, IDisposable
+internal sealed class UserService
+ : ServerService, IUserService, INotifyUserService, IDisposable
{
- private readonly Dictionary _userByID = [];
- private readonly Dictionary _userByToken = [];
+ private readonly Dictionary _userById = [];
+ private readonly Dictionary _userByToken = [];
private Dispatcher? _dispatcher;
public UserService()
{
- _userByID.Add("admin", new UserInfo()
+ _userById.Add("admin", new User()
{
- UserID = "admin",
+ UserId = "admin",
Password = "admin",
UserName = "Administrator",
Authority = Authority.Admin,
@@ -52,59 +37,82 @@ public UserService()
for (var i = 0; i < 10; i++)
{
- var user = new UserInfo()
+ var user = new User()
{
- UserID = $"user{i}",
+ UserId = $"user{i}",
Password = "1234",
UserName = $"사용자{i}",
Authority = Authority.Member,
};
- _userByID.Add(user.UserID, user);
+ _userById.Add(user.UserId, user);
}
+
_dispatcher = new Dispatcher(this);
}
- public Task CreateAsync(Guid token, string userID, string password, Authority authority, CancellationToken cancellationToken)
+ public event EventHandler? Created;
+
+ public event EventHandler? Deleted;
+
+ public event EventHandler? LoggedIn;
+
+ public event EventHandler? LoggedOut;
+
+ public event EventHandler? MessageReceived;
+
+ public event EventHandler? Renamed;
+
+ public event EventHandler? AuthorityChanged;
+
+ public Dispatcher Dispatcher => _dispatcher
+ ?? throw new InvalidOperationException($"'{this}' has not been initialized.");
+
+ public Task CreateAsync(UserCreateOptions options, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
- ValidateCreate(token, userID, password);
+ ValidateCreate(options);
- var user = _userByToken[token];
- var userInfo = new UserInfo()
+ var user = new User()
{
- UserID = userID,
- Password = password,
+ UserId = options.UserId,
+ Password = options.Password,
UserName = string.Empty,
- Authority = authority
+ Authority = options.Authority,
};
- _userByID.Add(userID, userInfo);
- Client.OnCreated(userID);
- Created?.Invoke(this, new UserEventArgs(userID));
+ _userById.Add(options.UserId, user);
+ Client.OnCreated(options.UserId);
+ Created?.Invoke(this, new UserEventArgs(options.UserId));
});
}
- public Task DeleteAsync(Guid token, string userID, CancellationToken cancellationToken)
+ public Task DeleteAsync(UserDeleteOptions options, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
- ValidateDelete(token, userID);
+ ValidateDelete(options);
- _userByID.Remove(userID);
- Client.OnDeleted(userID);
- Deleted?.Invoke(this, new UserEventArgs(userID));
+ var userId = options.UserId;
+ _userById.Remove(userId);
+ Client.OnDeleted(userId);
+ Deleted?.Invoke(this, new UserEventArgs(userId));
});
}
- public Task<(string, Authority)> GetInfoAsync(Guid token, string userID, CancellationToken cancellationToken)
+ public Task GetInfoAsync(
+ Guid token, string userId, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
ValidateUser(token);
- ValidateUser(userID);
+ ValidateUser(userId);
- var user = _userByID[userID];
- return (user.UserName, user.Authority);
+ var user = _userById[userId];
+ return new UserInfo
+ {
+ UserName = user.UserName,
+ Authority = user.Authority,
+ };
});
}
@@ -114,34 +122,36 @@ public Task GetUsersAsync(Guid token, CancellationToken cancellationTo
{
ValidateUser(token);
- return _userByID.Keys.ToArray();
+ return _userById.Keys.ToArray();
});
}
- public Task IsOnlineAsync(Guid token, string userID, CancellationToken cancellationToken)
+ public Task IsOnlineAsync(Guid token, string userId, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
ValidateUser(token);
- ValidateUser(userID);
+ ValidateUser(userId);
- var user = _userByID[userID];
+ var user = _userById[userId];
return user.Token != Guid.Empty;
});
}
- public Task LoginAsync(string userID, string password, CancellationToken cancellationToken)
+ public Task LoginAsync(UserLoginOptions options, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
- ValidatePassword(userID, password);
+ var userId = options.UserId;
+ var password = options.Password;
+ ValidatePassword(userId, password);
var token = Guid.NewGuid();
- var user = _userByID[userID];
+ var user = _userById[userId];
user.Token = token;
_userByToken.Add(token, user);
- Client.OnLoggedIn(userID);
- LoggedIn?.Invoke(this, new UserEventArgs(userID));
+ Client.OnLoggedIn(userId);
+ LoggedIn?.Invoke(this, new UserEventArgs(userId));
return token;
});
}
@@ -155,158 +165,174 @@ public Task LogoutAsync(Guid token, CancellationToken cancellationToken)
var user = _userByToken[token];
user.Token = Guid.Empty;
_userByToken.Remove(token);
- Client.OnLoggedOut(user.UserID);
- LoggedOut?.Invoke(this, new UserEventArgs(user.UserID));
+ Client.OnLoggedOut(user.UserId);
+ LoggedOut?.Invoke(this, new UserEventArgs(user.UserId));
});
}
- public Task SendMessageAsync(Guid token, string userID, string message, CancellationToken cancellationToken)
+ public Task SendMessageAsync(
+ UserSendMessageOptions options, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
+ var token = options.Token;
+ var userId = options.UserId;
+ var message = options.Message;
ValidateUser(token);
- ValidateOnline(userID);
+ ValidateOnline(userId);
ValidateMessage(message);
var user = _userByToken[token];
- Client.OnMessageReceived(user.UserID, userID, message);
- MessageReceived?.Invoke(this, new UserMessageEventArgs(user.UserID, userID, message));
+ Client.OnMessageReceived(user.UserId, userId, message);
+ MessageReceived?.Invoke(this, new UserMessageEventArgs(user.UserId, userId, message));
});
}
- public Task RenameAsync(Guid token, string userName, CancellationToken cancellationToken)
+ public Task RenameAsync(UserRenameOptions options, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
+ var token = options.Token;
+ var userName = options.UserName;
ValidateRename(token, userName);
var user = _userByToken[token];
user.UserName = userName;
- Client.OnRenamed(user.UserID, userName);
- Renamed?.Invoke(this, new UserNameEventArgs(user.UserID, userName));
+ Client.OnRenamed(user.UserId, userName);
+ Renamed?.Invoke(this, new UserNameEventArgs(user.UserId, userName));
});
}
- public Task SetAuthorityAsync(Guid token, string userID, Authority authority, CancellationToken cancellationToken)
+ public Task SetAuthorityAsync(UserAuthorityOptions options, CancellationToken cancellationToken)
{
return Dispatcher.InvokeAsync(() =>
{
- ValidateSetAuthority(token, userID);
+ var token = options.Token;
+ var userId = options.UserId;
+ var authority = options.Authority;
+ ValidateSetAuthority(token, userId);
- var user = _userByID[userID];
+ var user = _userById[userId];
user.Authority = authority;
- Client.OnAuthorityChanged(userID, authority);
- AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userID, authority));
+ Client.OnAuthorityChanged(userId, authority);
+ AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userId, authority));
});
}
public void Dispose()
{
- if (_dispatcher == null)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_dispatcher == null, $"{this}");
_dispatcher.Dispose();
_dispatcher = null;
GC.SuppressFinalize(this);
}
- public Dispatcher Dispatcher
+ private void ValidateUser(string userId)
{
- get
+ Dispatcher.VerifyAccess();
+ if (userId == null)
{
- if (_dispatcher == null)
- throw new InvalidOperationException($"'{this}' has not been initialized.");
- return _dispatcher;
+ throw new ArgumentNullException(nameof(userId));
}
- }
-
- public event EventHandler? Created;
-
- public event EventHandler? Deleted;
-
- public event EventHandler? LoggedIn;
- public event EventHandler? LoggedOut;
-
- public event EventHandler? MessageReceived;
-
- public event EventHandler? Renamed;
-
- public event EventHandler? AuthorityChanged;
-
- private void ValidateUser(string userID)
- {
- Dispatcher.VerifyAccess();
- if (userID == null)
- throw new ArgumentNullException(nameof(userID));
- if (_userByID.ContainsKey(userID) != true)
- throw new ArgumentException("Invalid userID", nameof(userID));
+ if (_userById.ContainsKey(userId) != true)
+ {
+ throw new ArgumentException("Invalid userID", nameof(userId));
+ }
}
- private void ValidateNotUser(string userID)
+ private void ValidateUser(Guid token)
{
Dispatcher.VerifyAccess();
- if (userID == null)
- throw new ArgumentNullException(nameof(userID));
- if (_userByID.ContainsKey(userID) == true)
- throw new ArgumentException("user is already exists.", nameof(userID));
+ if (_userByToken.ContainsKey(token) != true)
+ {
+ throw new ArgumentException("Invalid token.", nameof(token));
+ }
}
- private void ValidateUser(Guid token)
+ private void ValidateNotUser(string userId)
{
Dispatcher.VerifyAccess();
- if (_userByToken.ContainsKey(token) != true)
- throw new ArgumentException("Invalid token.", nameof(token));
+ if (userId == null)
+ {
+ throw new ArgumentNullException(nameof(userId));
+ }
+
+ if (_userById.ContainsKey(userId) == true)
+ {
+ throw new ArgumentException("user is already exists.", nameof(userId));
+ }
}
private void ValidatePassword(string password)
{
Dispatcher.VerifyAccess();
if (password == null)
+ {
throw new ArgumentNullException(nameof(password));
+ }
+
if (password == string.Empty)
+ {
throw new ArgumentException("Invalid password.", nameof(password));
+ }
+
if (password.Length < 4)
- throw new ArgumentException("length of password must be greater or equal than 4.", nameof(password));
+ {
+ throw new ArgumentException(
+ "length of password must be greater or equal than 4.", nameof(password));
+ }
}
- private void ValidatePassword(string userID, string password)
+ private void ValidatePassword(string userId, string password)
{
Dispatcher.VerifyAccess();
- ValidateUser(userID);
+ ValidateUser(userId);
if (password == null)
+ {
throw new ArgumentNullException(nameof(password));
- var user = _userByID[userID];
+ }
+
+ var user = _userById[userId];
if (user.Password != password)
+ {
throw new InvalidOperationException("wrong userID or password.");
+ }
}
- private void ValidateCreate(Guid token, string userID, string password)
+ private void ValidateCreate(UserCreateOptions options)
{
Dispatcher.VerifyAccess();
- ValidateUser(token);
- ValidateNotUser(userID);
- ValidatePassword(password);
- var user = _userByToken[token];
+ ValidateUser(options.Token);
+ ValidateNotUser(options.UserId);
+ ValidatePassword(options.Password);
+ var user = _userByToken[options.Token];
if (user.Authority != Authority.Admin)
+ {
throw new InvalidOperationException("permission denied.");
+ }
}
private void ValidateMessage(string message)
{
Dispatcher.VerifyAccess();
if (message == null)
+ {
throw new ArgumentNullException(nameof(message));
+ }
}
- private void ValidateOnline(string userID)
+ private void ValidateOnline(string userId)
{
Dispatcher.VerifyAccess();
- ValidateUser(userID);
- var user = _userByID[userID];
+ ValidateUser(userId);
+ var user = _userById[userId];
if (user.Token == Guid.Empty)
+ {
throw new InvalidOperationException($"user is offline.");
+ }
}
private void ValidateRename(Guid token, string userName)
@@ -314,45 +340,75 @@ private void ValidateRename(Guid token, string userName)
Dispatcher.VerifyAccess();
ValidateUser(token);
if (userName == null)
+ {
throw new ArgumentNullException(nameof(userName));
+ }
+
if (userName == string.Empty)
+ {
throw new ArgumentException("Invalid name.", nameof(userName));
+ }
+
var user = _userByToken[token];
if (user.UserName == userName)
+ {
throw new ArgumentException("same name can not set.", nameof(userName));
- if (user.UserID == "admin")
+ }
+
+ if (user.UserId == "admin")
+ {
throw new InvalidOperationException("permission denied.");
+ }
}
- private void ValidateSetAuthority(Guid token, string userID)
+ private void ValidateSetAuthority(Guid token, string userId)
{
Dispatcher.VerifyAccess();
ValidateUser(token);
- ValidateUser(userID);
+ ValidateUser(userId);
var user1 = _userByToken[token];
- if (user1.UserID == userID)
+ if (user1.UserId == userId)
+ {
throw new InvalidOperationException("can not set authority.");
+ }
+
if (user1.Authority != Authority.Admin)
+ {
throw new InvalidOperationException("permission denied.");
- var user2 = _userByID[userID];
+ }
+
+ var user2 = _userById[userId];
if (user2.Token != Guid.Empty)
+ {
throw new InvalidOperationException("can not set authority of online user.");
- if (userID == "admin")
+ }
+
+ if (userId == "admin")
+ {
throw new InvalidOperationException("permission denied.");
+ }
}
- private void ValidateDelete(Guid token, string userID)
+ private void ValidateDelete(UserDeleteOptions options)
{
Dispatcher.VerifyAccess();
- ValidateUser(token);
- ValidateNotUser(userID);
- var user1 = _userByToken[token];
+ ValidateUser(options.Token);
+ ValidateNotUser(options.UserId);
+ var user1 = _userByToken[options.Token];
if (user1.Authority != Authority.Admin)
+ {
throw new InvalidOperationException("permission denied.");
- var user2 = _userByID[userID];
+ }
+
+ var user2 = _userById[options.UserId];
if (user2.Token != Guid.Empty)
+ {
throw new InvalidOperationException("can not delete online user.");
- if (userID == "admin")
+ }
+
+ if (options.UserId == "admin")
+ {
throw new InvalidOperationException("permission denied.");
+ }
}
}
diff --git a/src/JSSoft.Communication/AdaptorProvider.cs b/src/JSSoft.Communication/AdaptorProvider.cs
index 937c377..799dc1b 100644
--- a/src/JSSoft.Communication/AdaptorProvider.cs
+++ b/src/JSSoft.Communication/AdaptorProvider.cs
@@ -1,48 +1,31 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
namespace JSSoft.Communication;
-sealed class AdaptorProvider : IAdaptorProvider
+internal sealed class AdaptorProvider : IAdaptorProvider
{
public const string DefaultName = "grpc";
+ public static readonly AdaptorProvider Default = new();
- public IAdaptor Create(IServiceContext serviceContext, IInstanceContext instanceContext, ServiceToken token)
- {
- // Environment.SetEnvironmentVariable("GRPC_VERBOSITY", "DEBUG");
- // Environment.SetEnvironmentVariable("GRPC_TRACE", "all");
- // global::Grpc.Core.GrpcEnvironment.SetLogger(new global::Grpc.Core.Logging.ConsoleLogger());
+ public string Name => DefaultName;
+ public IAdaptor Create(
+ IServiceContext serviceContext, IInstanceContext instanceContext, ServiceToken serviceToken)
+ {
if (serviceContext is ServerContext serverContext)
+ {
return new Grpc.AdaptorServer(serverContext, instanceContext);
+ }
else if (serviceContext is ClientContext clientContext)
+ {
return new Grpc.AdaptorClient(clientContext, instanceContext);
+ }
throw new NotSupportedException($"'{serviceContext.GetType()}' is not supported.");
}
-
- public string Name => DefaultName;
-
- public static readonly AdaptorProvider Default = new();
}
diff --git a/src/JSSoft.Communication/ClientContext.cs b/src/JSSoft.Communication/ClientContext.cs
index 1bd9cda..9e2b9f8 100644
--- a/src/JSSoft.Communication/ClientContext.cs
+++ b/src/JSSoft.Communication/ClientContext.cs
@@ -1,26 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-
-using System;
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
diff --git a/src/JSSoft.Communication/ClientService.cs b/src/JSSoft.Communication/ClientService.cs
index afa0f52..4368318 100644
--- a/src/JSSoft.Communication/ClientService.cs
+++ b/src/JSSoft.Communication/ClientService.cs
@@ -1,27 +1,12 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+// File may only contain a single type
+#pragma warning disable SA1402
using System;
-using System.Reflection;
namespace JSSoft.Communication;
@@ -41,18 +26,20 @@ public ClientService(TClient client)
public ClientService()
: base(typeof(TServer), typeof(TClient))
{
- if (this is TClient client)
+ var obj = this;
+ if (obj is TClient client)
{
_client = client;
-
}
else
{
- throw new InvalidOperationException($"'{GetType()}' must be implemented by '{typeof(TClient)}'.");
+ throw new InvalidOperationException(
+ $"'{GetType()}' must be implemented by '{typeof(TClient)}'.");
}
}
- public TServer Server => _server ?? throw new InvalidOperationException("Server is not created.");
+ public TServer Server
+ => _server ?? throw new InvalidOperationException("Server is not created.");
protected virtual TClient CreateClient(IPeer peer) => _client;
@@ -85,7 +72,8 @@ public ClientService()
{
}
- public TServer Server => _server ?? throw new InvalidOperationException("Server is not created.");
+ public TServer Server
+ => _server ?? throw new InvalidOperationException("Server is not created.");
protected virtual void OnServiceCreated(IPeer peer, TServer server)
{
diff --git a/src/JSSoft.Communication/EndPointUtility.cs b/src/JSSoft.Communication/EndPointUtility.cs
index 3870098..6d5afe7 100644
--- a/src/JSSoft.Communication/EndPointUtility.cs
+++ b/src/JSSoft.Communication/EndPointUtility.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Diagnostics.CodeAnalysis;
@@ -30,7 +13,7 @@ namespace JSSoft.Communication;
public static class EndPointUtility
{
- public static (string host, int port) GetElements(EndPoint endPoint)
+ public static (string Host, int Port) GetElements(EndPoint endPoint)
{
if (endPoint is DnsEndPoint dnsEndPoint)
{
@@ -40,6 +23,7 @@ public static (string host, int port) GetElements(EndPoint endPoint)
{
return ($"{iPEndPoint.Address}", iPEndPoint.Port);
}
+
throw new NotSupportedException($"'{endPoint}' is not supported.");
}
@@ -53,6 +37,7 @@ public static string ToString(EndPoint endPoint)
{
return $"{iPEndPoint.Address}:{iPEndPoint.Port}";
}
+
throw new NotSupportedException($"'{endPoint}' is not supported.");
}
@@ -86,7 +71,8 @@ public static bool TryParse(string text, [MaybeNullWhen(false)] out EndPoint end
}
#if NETSTANDARD
- internal static global::Grpc.Core.ServerPort GetServerPort(EndPoint endPoint, global::Grpc.Core.ServerCredentials credentials)
+ internal static global::Grpc.Core.ServerPort GetServerPort(
+ EndPoint endPoint, global::Grpc.Core.ServerCredentials credentials)
{
if (endPoint is DnsEndPoint dnsEndPoint)
{
diff --git a/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs b/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs
index dba7f59..fe9fb91 100644
--- a/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs
+++ b/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs
@@ -1,31 +1,14 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Threading;
namespace JSSoft.Communication.Extensions;
-static class ISerializerExtensions
+internal static class ISerializerExtensions
{
public static string[] SerializeMany(this ISerializer @this, Type[] types, object?[] args)
{
@@ -36,6 +19,7 @@ public static string[] SerializeMany(this ISerializer @this, Type[] types, objec
var value = args[i];
items[i] = @this.Serialize(type, value);
}
+
return items;
}
@@ -48,10 +32,12 @@ public static string[] SerializeMany(this ISerializer @this, Type[] types, objec
var value = datas[i];
items[i] = @this.Deserialize(type, value);
}
+
return items;
}
- public static object?[] DeserializeMany(this ISerializer @this, Type[] types, string[] datas, CancellationToken? cancellationToken)
+ public static object?[] DeserializeMany(
+ this ISerializer @this, Type[] types, string[] datas, CancellationToken? cancellationToken)
{
var length = cancellationToken != null ? datas.Length + 1 : datas.Length;
var items = new object?[length];
@@ -61,10 +47,12 @@ public static string[] SerializeMany(this ISerializer @this, Type[] types, objec
var value = datas[i];
items[i] = @this.Deserialize(type, value);
}
+
if (cancellationToken != null)
{
items[datas.Length] = cancellationToken;
}
+
return items;
}
}
diff --git a/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs b/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs
index 5831031..0fe215d 100644
--- a/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs
+++ b/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Threading.Tasks;
@@ -38,11 +21,11 @@ public static void Error(this IServiceContext @this, string message)
/// Close the service context without exception throwing.
///
/// Indicates a service context instance to close.
- /// Indicates a token to close. you can get the token when
+ /// Indicates a token to close. you can get the token when
/// the service context is opened.
///
///
- /// If the service context is closed successfully, it returns 0.
+ /// If the service context is closed successfully, it returns 0.
/// If the service context is faulted, it returns 1.
///
public static async Task ReleaseAsync(this IServiceContext @this, Guid token)
@@ -55,6 +38,7 @@ public static async Task ReleaseAsync(this IServiceContext @this, Guid toke
}
catch
{
+ // do nothing
}
}
diff --git a/src/JSSoft.Communication/Grpc/AdaptorClient.cs b/src/JSSoft.Communication/Grpc/AdaptorClient.cs
index 9b4e0ac..6cf7899 100644
--- a/src/JSSoft.Communication/Grpc/AdaptorClient.cs
+++ b/src/JSSoft.Communication/Grpc/AdaptorClient.cs
@@ -1,34 +1,19 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
-using Grpc.Core;
-using JSSoft.Communication.Extensions;
-using JSSoft.Communication.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using Grpc.Core;
+using JSSoft.Communication.Extensions;
+using JSSoft.Communication.Logging;
+using Newtonsoft.Json;
+
#if NETSTANDARD
using GrpcChannel = Grpc.Core.Channel;
#elif NET
@@ -38,7 +23,7 @@
namespace JSSoft.Communication.Grpc;
-sealed class AdaptorClient : IAdaptor
+internal sealed class AdaptorClient : IAdaptor
{
private static readonly TimeSpan Timeout = new(0, 0, 15);
@@ -59,23 +44,32 @@ public AdaptorClient(IServiceContext serviceContext, IInstanceContext instanceCo
_serviceContext = serviceContext;
_instanceContext = instanceContext;
_serviceByName = serviceContext.Services;
- _methodsByService = _serviceByName.ToDictionary(item => item.Value, item => new MethodDescriptorCollection(item.Value.ClientType));
+ _methodsByService = _serviceByName.ToDictionary(
+ keySelector: item => item.Value,
+ elementSelector: item => new MethodDescriptorCollection(item.Value.ClientType));
}
+ public event EventHandler? Disconnected;
+
public async Task OpenAsync(EndPoint endPoint, CancellationToken cancellationToken)
{
if (_adaptorImpl != null)
+ {
throw new InvalidOperationException("Already opened.");
+ }
+
try
{
#if NETSTANDARD
_channel = new Channel(EndPointUtility.ToString(endPoint), ChannelCredentials.Insecure);
await _channel.ConnectAsync(deadline: DateTime.UtcNow.AddSeconds(15));
#elif NET
- _channel = GrpcChannel.ForAddress($"http://{EndPointUtility.ConvertToIPEndPoint(endPoint)}");
+ _channel = GrpcChannel.ForAddress(
+ $"http://{EndPointUtility.ConvertToIPEndPoint(endPoint)}");
await _channel.ConnectAsync(cancellationToken);
#endif
- _adaptorImpl = new AdaptorClientImpl(_channel, _serviceContext.Id, _serviceByName.Values.ToArray());
+ _adaptorImpl = new AdaptorClientImpl(
+ _channel, _serviceContext.Id, _serviceByName.Values.ToArray());
await _adaptorImpl.OpenAsync(cancellationToken);
_descriptor = _instanceContext.CreateInstance(_adaptorImpl);
_cancellationTokenSource = new CancellationTokenSource();
@@ -90,35 +84,43 @@ public async Task OpenAsync(EndPoint endPoint, CancellationToken cancellationTok
await _channel.ShutdownAsync();
_channel = null;
}
+
throw;
}
}
public async Task CloseAsync(CancellationToken cancellationToken)
{
- _cancellationTokenSource?.Cancel();
+ if (_cancellationTokenSource is not null)
+ {
+ await _cancellationTokenSource.CancelAsync();
+ _cancellationTokenSource.Dispose();
+ _cancellationTokenSource = null;
+ }
+
if (_timer != null)
{
await _timer.DisposeAsync();
_timer = null;
}
+
if (_adaptorImpl != null)
{
_instanceContext.DestroyInstance(_adaptorImpl);
await _adaptorImpl.CloseAsync(cancellationToken);
_adaptorImpl = null;
}
+
if (_task != null)
{
await _task;
}
+
if (_channel != null)
{
await _channel.ShutdownAsync();
_channel = null;
}
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
}
public async ValueTask DisposeAsync()
@@ -129,133 +131,34 @@ public async ValueTask DisposeAsync()
await _timer.DisposeAsync();
_timer = null;
}
+
if (_adaptorImpl != null)
{
_instanceContext.DestroyInstance(_adaptorImpl);
await _adaptorImpl.TryCloseAsync(cancellationToken: default);
_adaptorImpl = null;
}
+
if (_channel != null)
{
await _channel.ShutdownAsync();
_channel = null;
}
- GC.SuppressFinalize(this);
- }
-
- public event EventHandler? Disconnected;
- private async void Timer_TimerCallback(object? state)
- {
- try
- {
- if (_adaptorImpl != null)
- {
- var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
- var request = new PingRequest();
- await _adaptorImpl.PingAsync(request, metaData);
- }
- }
- catch
- {
- }
+ GC.SuppressFinalize(this);
}
- private async Task PollAsync(CancellationToken cancellationToken)
+ void IAdaptor.Invoke(InvokeOptions options)
{
if (_adaptorImpl == null)
- throw new InvalidOperationException("adaptor is not set.");
-
- var closeCode = int.MinValue;
- try
- {
- var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
- using var call = _adaptorImpl.Poll(metaData);
- var request = new PollRequest();
- await call.RequestStream.WriteAsync(request);
- while (await MoveAsync(call, cancellationToken) == true)
- {
- var reply = call.ResponseStream.Current;
- if (reply.Code != int.MinValue)
- {
- closeCode = reply.Code;
- break;
- }
- InvokeCallback(reply);
- await call.RequestStream.WriteAsync(request);
- }
- await call.RequestStream.CompleteAsync();
- }
- catch (Exception e)
- {
- closeCode = -2;
- LogUtility.Error(e);
- }
- if (closeCode != int.MinValue)
- {
- Disconnected?.Invoke(this, EventArgs.Empty);
- }
- _serviceContext.Debug("Poll finished.");
-
- static async Task MoveAsync(AsyncDuplexStreamingCall call, CancellationToken cancellationToken)
{
- if (cancellationToken.IsCancellationRequested == true)
- return false;
- using var cancellationTokenSource = new CancellationTokenSource(Timeout);
- return await call.ResponseStream.MoveNext(cancellationTokenSource.Token);
- }
- }
-
- private void InvokeCallback(IService service, string name, string[] data)
- {
- if (_adaptorImpl == null)
throw new InvalidOperationException("adaptor is not set.");
- var methodDescriptors = _methodsByService[service];
- if (methodDescriptors.Contains(name) != true)
- throw new InvalidOperationException("Invalid method name.");
-
- var methodDescriptor = methodDescriptors[name];
- var args = _serializer!.DeserializeMany(methodDescriptor.ParameterTypes, data);
- var instance = _descriptor!.ClientInstances[service];
- Task.Run(() => methodDescriptor.InvokeAsync(_serviceContext, instance, args));
- }
-
- private void InvokeCallback(PollReply reply)
- {
- foreach (var item in reply.Items)
- {
- var service = _serviceByName[item.ServiceName];
- InvokeCallback(service, item.Name, [.. item.Data]);
}
- reply.Items.Clear();
- }
-
- private void HandleReply(InvokeReply reply)
- {
- if (reply.ID != string.Empty && Type.GetType(reply.ID) is { } exceptionType)
- {
- ThrowException(exceptionType, reply.Data);
- }
- }
-
- private void ThrowException(Type exceptionType, string data)
- {
- if (_serializer == null)
- throw new InvalidOperationException("serializer is not set.");
-
- if (Newtonsoft.Json.JsonConvert.DeserializeObject(data, exceptionType) is Exception exception)
- throw exception;
-
- throw new UnreachableException($"This code should not be reached in {nameof(ThrowException)}.");
- }
-
- #region IAdaptor
-
- void IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args)
- {
- if (_adaptorImpl == null)
- throw new InvalidOperationException("adaptor is not set.");
+ var instance = options.Instance;
+ var name = options.Name;
+ var types = options.Types;
+ var args = options.Args;
var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
var data = _serializer!.SerializeMany(types, args);
var request = new InvokeRequest
@@ -268,11 +171,17 @@ void IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[]
HandleReply(reply);
}
- async void IAdaptor.InvokeOneWay(InstanceBase instance, string name, Type[] types, object?[] args)
+ async void IAdaptor.InvokeOneWay(InvokeOptions options)
{
if (_adaptorImpl == null)
+ {
throw new InvalidOperationException("adaptor is not set.");
+ }
+ var instance = options.Instance;
+ var name = options.Name;
+ var types = options.Types;
+ var args = options.Args;
var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
var data = _serializer!.SerializeMany(types, args);
var request = new InvokeRequest
@@ -287,16 +196,26 @@ async void IAdaptor.InvokeOneWay(InstanceBase instance, string name, Type[] type
}
catch
{
+ // do nothing
}
}
- T IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args)
+ T IAdaptor.Invoke(InvokeOptions options)
{
if (_adaptorImpl == null)
+ {
throw new InvalidOperationException("adaptor is not set.");
+ }
+
if (_serializer == null)
+ {
throw new InvalidOperationException("serializer is not set.");
+ }
+ var instance = options.Instance;
+ var name = options.Name;
+ var types = options.Types;
+ var args = options.Args;
var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
var data = _serializer.SerializeMany(types, args);
var request = new InvokeRequest
@@ -308,18 +227,30 @@ T IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[]
var reply = _adaptorImpl.Invoke(request, metaData);
HandleReply(reply);
if (_serializer.Deserialize(typeof(T), reply.Data) is T value)
+ {
return value;
+ }
- throw new UnreachableException($"This code should not be reached in {nameof(IAdaptor.Invoke)}.");
+ throw new UnreachableException(
+ $"This code should not be reached in {nameof(IAdaptor.Invoke)}.");
}
- async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken)
+ async Task IAdaptor.InvokeAsync(InvokeOptions options, CancellationToken cancellationToken)
{
if (_adaptorImpl == null)
+ {
throw new InvalidOperationException("adaptor is not set.");
+ }
+
if (_serializer == null)
+ {
throw new InvalidOperationException("serializer is not set.");
+ }
+ var instance = options.Instance;
+ var name = options.Name;
+ var types = options.Types;
+ var args = options.Args;
var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
var data = _serializer.SerializeMany(types, args);
var request = new InvokeRequest
@@ -330,24 +261,40 @@ async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types
};
try
{
- var reply = await _adaptorImpl.InvokeAsync(request, metaData, cancellationToken: cancellationToken);
+ var reply = await _adaptorImpl.InvokeAsync(
+ request: request,
+ headers: metaData,
+ cancellationToken: cancellationToken);
HandleReply(reply);
}
catch (RpcException e)
{
if (e.StatusCode == StatusCode.Cancelled)
+ {
cancellationToken.ThrowIfCancellationRequested();
+ }
+
throw;
}
}
- async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken)
+ async Task IAdaptor.InvokeAsync(
+ InvokeOptions options, CancellationToken cancellationToken)
{
if (_adaptorImpl == null)
+ {
throw new InvalidOperationException("adaptor is not set.");
+ }
+
if (_serializer == null)
+ {
throw new InvalidOperationException("serializer is not set.");
+ }
+ var instance = options.Instance;
+ var name = options.Name;
+ var types = options.Types;
+ var args = options.Args;
var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
var data = _serializer.SerializeMany(types, args);
var request = new InvokeRequest
@@ -358,17 +305,147 @@ async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[]
request.Data.AddRange(data);
try
{
- var reply = await _adaptorImpl.InvokeAsync(request, metaData, cancellationToken: cancellationToken);
+ var reply = await _adaptorImpl.InvokeAsync(
+ request: request,
+ headers: metaData,
+ cancellationToken: cancellationToken);
HandleReply(reply);
return _serializer.Deserialize(typeof(T), reply.Data) is T value ? value : default!;
}
catch (RpcException e)
{
if (e.StatusCode == StatusCode.Cancelled)
+ {
cancellationToken.ThrowIfCancellationRequested();
+ }
+
throw;
}
}
- #endregion
+ private static async Task MoveAsync(
+ AsyncDuplexStreamingCall call, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested == true)
+ {
+ return false;
+ }
+
+ using var cancellationTokenSource = new CancellationTokenSource(Timeout);
+ return await call.ResponseStream.MoveNext(cancellationTokenSource.Token);
+ }
+
+ private async void Timer_TimerCallback(object? state)
+ {
+ try
+ {
+ if (_adaptorImpl != null)
+ {
+ var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
+ var request = new PingRequest();
+ await _adaptorImpl.PingAsync(request, metaData);
+ }
+ }
+ catch
+ {
+ // do nothing
+ }
+ }
+
+ private async Task PollAsync(CancellationToken cancellationToken)
+ {
+ if (_adaptorImpl == null)
+ {
+ throw new InvalidOperationException("adaptor is not set.");
+ }
+
+ var closeCode = int.MinValue;
+ try
+ {
+ var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } };
+ using var call = _adaptorImpl.Poll(metaData);
+ var request = new PollRequest();
+ await call.RequestStream.WriteAsync(request);
+ while (await MoveAsync(call, cancellationToken) == true)
+ {
+ var reply = call.ResponseStream.Current;
+ if (reply.Code != int.MinValue)
+ {
+ closeCode = reply.Code;
+ break;
+ }
+
+ InvokeCallback(reply);
+ await call.RequestStream.WriteAsync(request);
+ }
+
+ await call.RequestStream.CompleteAsync();
+ }
+ catch (Exception e)
+ {
+ closeCode = -2;
+ LogUtility.Error(e);
+ }
+
+ if (closeCode != int.MinValue)
+ {
+ Disconnected?.Invoke(this, EventArgs.Empty);
+ }
+
+ _serviceContext.Debug("Poll finished.");
+ }
+
+ private void InvokeCallback(IService service, string name, string[] data)
+ {
+ if (_adaptorImpl == null)
+ {
+ throw new InvalidOperationException("adaptor is not set.");
+ }
+
+ var methodDescriptors = _methodsByService[service];
+ if (methodDescriptors.Contains(name) != true)
+ {
+ throw new InvalidOperationException("Invalid method name.");
+ }
+
+ var methodDescriptor = methodDescriptors[name];
+ var args = _serializer!.DeserializeMany(methodDescriptor.ParameterTypes, data);
+ var instance = _descriptor!.ClientInstances[service];
+ Task.Run(() => methodDescriptor.InvokeAsync(_serviceContext, instance, args));
+ }
+
+ private void InvokeCallback(PollReply reply)
+ {
+ foreach (var item in reply.Items)
+ {
+ var service = _serviceByName[item.ServiceName];
+ InvokeCallback(service, item.Name, [.. item.Data]);
+ }
+
+ reply.Items.Clear();
+ }
+
+ private void HandleReply(InvokeReply reply)
+ {
+ if (reply.ID != string.Empty && Type.GetType(reply.ID) is { } exceptionType)
+ {
+ ThrowException(exceptionType, reply.Data);
+ }
+ }
+
+ private void ThrowException(Type exceptionType, string data)
+ {
+ if (_serializer == null)
+ {
+ throw new InvalidOperationException("serializer is not set.");
+ }
+
+ if (JsonConvert.DeserializeObject(data, exceptionType) is Exception exception)
+ {
+ throw exception;
+ }
+
+ throw new UnreachableException(
+ $"This code should not be reached in {nameof(ThrowException)}.");
+ }
}
diff --git a/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs b/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs
index ebd0026..2181f7e 100644
--- a/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs
+++ b/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs
@@ -1,30 +1,12 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
-using Grpc.Core;
using System;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Grpc.Core;
#if NETSTANDARD
using GrpcChannel = Grpc.Core.Channel;
#elif NET
@@ -33,7 +15,7 @@
namespace JSSoft.Communication.Grpc;
-sealed class AdaptorClientImpl(GrpcChannel channel, Guid id, IService[] services)
+internal sealed class AdaptorClientImpl(GrpcChannel channel, Guid id, IService[] services)
: Adaptor.AdaptorClient(channel), IPeer
{
public string Id { get; } = $"{id}";
@@ -42,7 +24,6 @@ sealed class AdaptorClientImpl(GrpcChannel channel, Guid id, IService[] services
public async Task OpenAsync(CancellationToken cancellationToken)
{
- var serviceNames = Services.Select(item => item.Name).ToArray();
var request = new OpenRequest();
var metaData = new Metadata() { { "id", $"{Id}" } };
try
@@ -52,7 +33,10 @@ public async Task OpenAsync(CancellationToken cancellationToken)
catch (RpcException e)
{
if (e.StatusCode == StatusCode.Cancelled)
+ {
cancellationToken.ThrowIfCancellationRequested();
+ }
+
throw;
}
}
@@ -68,7 +52,10 @@ public async Task CloseAsync(CancellationToken cancellationToken)
catch (RpcException e)
{
if (e.StatusCode == StatusCode.Cancelled)
+ {
cancellationToken.ThrowIfCancellationRequested();
+ }
+
throw;
}
}
@@ -85,6 +72,7 @@ public async Task TryCloseAsync(CancellationToken cancellationToken)
{
return true;
}
+
return false;
}
}
diff --git a/src/JSSoft.Communication/Grpc/AdaptorServer.cs b/src/JSSoft.Communication/Grpc/AdaptorServer.cs
index 5406758..ca0c0ed 100644
--- a/src/JSSoft.Communication/Grpc/AdaptorServer.cs
+++ b/src/JSSoft.Communication/Grpc/AdaptorServer.cs
@@ -1,55 +1,37 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
-using Grpc.Core;
-using JSSoft.Communication.Logging;
-using JSSoft.Communication.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using Grpc.Core;
+using JSSoft.Communication.Extensions;
+using JSSoft.Communication.Logging;
#if NET
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
#endif
namespace JSSoft.Communication.Grpc;
-record struct CallbackData(IService Service, string Name, string[] Data);
-
-sealed class AdaptorServer : IAdaptor
+internal sealed class AdaptorServer : IAdaptor
{
private static readonly TimeSpan Timeout = new(0, 0, 30);
private static readonly TimeSpan PollTimeout = new(0, 0, 10);
private readonly IServiceContext _serviceContext;
private readonly IReadOnlyDictionary _serviceByName;
private readonly Dictionary _methodsByService;
+ private readonly Timer _timer;
#if NETSTANDARD
private Server? _server;
private AdaptorServerImpl? _adaptor;
@@ -57,41 +39,54 @@ sealed class AdaptorServer : IAdaptor
private IHost? _host;
#endif
private ISerializer? _serializer;
- private readonly Timer _timer;
private EventHandler? _disconnectedEventHandler;
public AdaptorServer(IServiceContext serviceContext, IInstanceContext instanceContext)
{
_serviceContext = serviceContext;
_serviceByName = serviceContext.Services;
- _methodsByService = _serviceByName.ToDictionary(item => item.Value, item => new MethodDescriptorCollection(item.Value.ServerType));
+ _methodsByService = _serviceByName.ToDictionary(
+ keySelector: item => item.Value,
+ elementSelector: item => new MethodDescriptorCollection(item.Value.ServerType));
Peers = new PeerCollection(instanceContext);
_timer = new Timer(Timer_TimerCallback, null, TimeSpan.Zero, Timeout);
}
+ event EventHandler? IAdaptor.Disconnected
+ {
+ add => _disconnectedEventHandler += value;
+ remove => _disconnectedEventHandler -= value;
+ }
+
public PeerCollection Peers { get; }
public Guid Id => _serviceContext.Id;
- public async Task OpenAsync(OpenRequest request, ServerCallContext context, CancellationToken cancellationToken)
+ public async Task OpenAsync(
+ OpenRequest request, ServerCallContext context, CancellationToken cancellationToken)
{
var id = GetId(context);
if (Peers.TryGetValue(id, out var peer) == true)
+ {
throw new InvalidOperationException($"The peer '{id}' already exists.");
+ }
Peers.Add(_serviceContext, id);
- await Task.CompletedTask;
+ await Task.Delay(1, cancellationToken);
return new OpenReply();
}
- public async Task CloseAsync(CloseRequest request, ServerCallContext context, CancellationToken cancellationToken)
+ public async Task CloseAsync(
+ CloseRequest request, ServerCallContext context, CancellationToken cancellationToken)
{
var id = GetId(context);
if (Peers.TryGetValue(id, out var peer) != true)
+ {
throw new InvalidOperationException($"The peer '{id}' does not exists.");
+ }
Peers.Remove(_serviceContext, id, closeCode: 0);
- await Task.CompletedTask;
+ await Task.Delay(1, cancellationToken);
return new CloseReply();
}
@@ -100,7 +95,9 @@ public async Task PingAsync(PingRequest request, ServerCallContext co
var id = GetId(context);
var dateTime = DateTime.UtcNow;
if (Peers.TryGetValue(id, out var peer) != true)
+ {
throw new InvalidOperationException($"The peer '{id}' does not exists.");
+ }
peer.PingTime = dateTime;
_serviceContext.Debug($"{id} Ping({dateTime})");
@@ -111,53 +108,74 @@ public async Task PingAsync(PingRequest request, ServerCallContext co
public async Task InvokeAsync(InvokeRequest request, ServerCallContext context)
{
if (_serializer == null)
+ {
throw new InvalidOperationException("Serializer is not set.");
- if (_serviceByName.ContainsKey(request.ServiceName) != true)
- throw new InvalidOperationException($"Service '{request.ServiceName}' does not exists.");
- var service = _serviceByName[request.ServiceName];
+ }
+
+ var serviceName = request.ServiceName;
+ if (_serviceByName.TryGetValue(serviceName, out var service) != true)
+ {
+ throw new InvalidOperationException(
+ $"Service '{serviceName}' does not exists.");
+ }
+
var methodDescriptors = _methodsByService[service];
if (methodDescriptors.Contains(request.Name) != true)
+ {
throw new InvalidOperationException($"Method '{request.Name}' does not exists.");
+ }
var id = GetId(context);
if (Peers.TryGetValue(id, out var peer) != true)
+ {
throw new InvalidOperationException($"The peer '{id}' does not exists.");
+ }
var methodDescriptor = methodDescriptors[request.Name];
- var cancellationToken = methodDescriptor.IsCancelable == true ? (CancellationToken?)context.CancellationToken : null;
+ var isCancelable = methodDescriptor.IsCancelable;
+ var cancellationToken = isCancelable ? (CancellationToken?)context.CancellationToken : null;
var instance = peer.Services[service];
- var args = _serializer.DeserializeMany(methodDescriptor.ParameterTypes, [.. request.Data], cancellationToken);
+ var args = _serializer.DeserializeMany(
+ types: methodDescriptor.ParameterTypes,
+ datas: [.. request.Data],
+ cancellationToken: cancellationToken);
if (methodDescriptor.IsOneWay == true)
{
- methodDescriptor.InvokeOneWay(_serviceContext, instance, args);
var reply = new InvokeReply()
{
ID = string.Empty,
- Data = _serializer.Serialize(typeof(void), null)
+ Data = _serializer.Serialize(typeof(void), null),
};
+ var methodShortName = methodDescriptor.ShortName;
- _serviceContext.Debug($"{id} Invoke(one way): {request.ServiceName}.{methodDescriptor.ShortName}");
+ methodDescriptor.InvokeOneWay(_serviceContext, instance, args);
+ _serviceContext.Debug($"{id} Invoke(one way): {serviceName}.{methodShortName}");
return reply;
}
else
{
- var (assemblyQualifiedName, valueType, value) = await methodDescriptor.InvokeAsync(_serviceContext, instance, args);
+ var result = await methodDescriptor.InvokeAsync(_serviceContext, instance, args);
var reply = new InvokeReply()
{
- ID = $"{assemblyQualifiedName}",
- Data = _serializer.Serialize(valueType, value)
+ ID = result.AssemblyQualifiedName,
+ Data = _serializer.Serialize(result.ValueType, result.Value),
};
- _serviceContext.Debug($"{id} Invoke: {request.ServiceName}.{methodDescriptor.ShortName}");
+ _serviceContext.Debug($"{id} Invoke: {methodDescriptor.Name}");
return reply;
}
}
- public async Task PollAsync(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context)
+ public async Task PollAsync(
+ IAsyncStreamReader requestStream,
+ IServerStreamWriter responseStream,
+ ServerCallContext context)
{
var id = GetId(context);
if (Peers.TryGetValue(id, out var peer) != true)
+ {
throw new InvalidOperationException($"The peer '{id}' does not exists.");
+ }
using var manualResetEvent = new ManualResetEvent(initialState: true);
var cancellationToken = peer.BeginPolling(manualResetEvent);
@@ -168,9 +186,15 @@ public async Task PollAsync(IAsyncStreamReader requestStream, IServ
var reply = peer.Collect();
await responseStream.WriteAsync(reply);
if (cancellationToken.IsCancellationRequested == true)
+ {
break;
+ }
+
if (peer.CanCollect == true)
+ {
continue;
+ }
+
manualResetEvent.Reset();
manualResetEvent.WaitOne(PollTimeout);
}
@@ -179,6 +203,7 @@ public async Task PollAsync(IAsyncStreamReader requestStream, IServ
{
_serviceContext.Error(e.Message);
}
+
peer.EndPolling();
_serviceContext.Debug("Poll finished.");
@@ -195,44 +220,6 @@ public async ValueTask DisposeAsync()
GC.SuppressFinalize(this);
}
- private static string GetId(ServerCallContext context)
- {
- if (context.RequestHeaders.Get("id") is { } entry)
- {
- return entry.Value;
- }
-
- throw new ArgumentException("The id is not found.");
- }
-
- private void AddCallback(InstanceBase instance, string name, Type[] types, object?[] args)
- {
- if (_serializer == null)
- throw new UnreachableException("Serializer is not set.");
-
- var data = _serializer.SerializeMany(types, args);
- var peers = instance.Peer is not Peer peer ? Peers.ToArray().Select(item => item.Value) : new Peer[] { peer };
- var service = instance.Service;
- var callbackData = new CallbackData(service, name, data);
- Parallel.ForEach(peers, item => item.AddCallback(callbackData));
- }
-
- private void Timer_TimerCallback(object? state)
- {
- var dateTime = DateTime.UtcNow;
- var peers = Peers.ToArray();
- var query = from item in peers
- let peer = item.Value
- where dateTime - peer.PingTime > Timeout
- select peer;
- foreach (var item in query)
- {
- Peers.Remove(_serviceContext, item.Id, closeCode: -1);
- }
- }
-
- #region IAdaptor
-
#if NETSTANDARD
async Task IAdaptor.OpenAsync(EndPoint endPoint, CancellationToken cancellationToken)
{
@@ -312,36 +299,73 @@ async Task IAdaptor.CloseAsync(CancellationToken cancellationToken)
}
#endif
- void IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args)
+ void IAdaptor.Invoke(InvokeOptions options)
{
- AddCallback(instance, name, types, args);
+ AddCallback(options);
}
- void IAdaptor.InvokeOneWay(InstanceBase instance, string name, Type[] types, object?[] args)
+ void IAdaptor.InvokeOneWay(InvokeOptions options)
{
- AddCallback(instance, name, types, args);
+ AddCallback(options);
}
- T IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args)
+ T IAdaptor.Invoke(InvokeOptions options)
{
- throw new NotSupportedException($"This method '{nameof(IAdaptor.Invoke)}' is not supported.");
+ throw new NotSupportedException(
+ $"This method '{nameof(IAdaptor.Invoke)}' is not supported.");
}
- Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken)
+ Task IAdaptor.InvokeAsync(InvokeOptions options, CancellationToken cancellationToken)
{
- throw new NotSupportedException($"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported.");
+ throw new NotSupportedException(
+ $"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported.");
}
- Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken)
+ Task IAdaptor.InvokeAsync(InvokeOptions options, CancellationToken cancellationToken)
{
- throw new NotSupportedException($"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported.");
+ throw new NotSupportedException(
+ $"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported.");
}
- event EventHandler? IAdaptor.Disconnected
+ private static string GetId(ServerCallContext context)
{
- add => _disconnectedEventHandler += value;
- remove => _disconnectedEventHandler -= value;
+ if (context.RequestHeaders.Get("id") is { } entry)
+ {
+ return entry.Value;
+ }
+
+ throw new ArgumentException("The id is not found.");
}
- #endregion
+ private void AddCallback(InvokeOptions options)
+ {
+ if (_serializer == null)
+ {
+ throw new UnreachableException("Serializer is not set.");
+ }
+
+ var instance = options.Instance;
+ var name = options.Name;
+ var types = options.Types;
+ var args = options.Args;
+ var data = _serializer.SerializeMany(types, args);
+ var peers = instance.Peer is not Peer peer ? Peers.Select(item => item.Value) : [peer];
+ var service = instance.Service;
+ var callbackData = new CallbackData(service, name, data);
+ Parallel.ForEach(peers, item => item.AddCallback(callbackData));
+ }
+
+ private void Timer_TimerCallback(object? state)
+ {
+ var dateTime = DateTime.UtcNow;
+ var peers = Peers.ToArray();
+ var query = from item in peers
+ let peer = item.Value
+ where dateTime - peer.PingTime > Timeout
+ select peer;
+ foreach (var item in query)
+ {
+ Peers.Remove(_serviceContext, item.Id, closeCode: -1);
+ }
+ }
}
diff --git a/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs b/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs
index 03450f7..160e08e 100644
--- a/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs
+++ b/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs
@@ -1,31 +1,14 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
-using Grpc.Core;
using System.Threading.Tasks;
+using Grpc.Core;
namespace JSSoft.Communication.Grpc;
-sealed class AdaptorServerImpl(AdaptorServer adaptorServer) : Adaptor.AdaptorBase
+internal sealed class AdaptorServerImpl(AdaptorServer adaptorServer) : Adaptor.AdaptorBase
{
private readonly AdaptorServer _adaptorServer = adaptorServer;
@@ -41,6 +24,9 @@ public override Task Ping(PingRequest request, ServerCallContext cont
public override Task Invoke(InvokeRequest request, ServerCallContext context)
=> _adaptorServer.InvokeAsync(request, context);
- public override Task Poll(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context)
+ public override Task Poll(
+ IAsyncStreamReader requestStream,
+ IServerStreamWriter responseStream,
+ ServerCallContext context)
=> _adaptorServer.PollAsync(requestStream, responseStream, context);
}
diff --git a/src/JSSoft.Communication/Grpc/CallbackData.cs b/src/JSSoft.Communication/Grpc/CallbackData.cs
new file mode 100644
index 0000000..a0aa55c
--- /dev/null
+++ b/src/JSSoft.Communication/Grpc/CallbackData.cs
@@ -0,0 +1,8 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+namespace JSSoft.Communication.Grpc;
+
+internal record struct CallbackData(IService Service, string Name, string[] Data);
diff --git a/src/JSSoft.Communication/Grpc/Peer.cs b/src/JSSoft.Communication/Grpc/Peer.cs
index 886f4dc..320cb49 100644
--- a/src/JSSoft.Communication/Grpc/Peer.cs
+++ b/src/JSSoft.Communication/Grpc/Peer.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Generic;
@@ -26,7 +9,7 @@
namespace JSSoft.Communication.Grpc;
-sealed class Peer(string id) : IPeer
+internal sealed class Peer(string id) : IPeer
{
private readonly object _lockObject = new();
private readonly List _callbackDataList = [];
@@ -43,6 +26,8 @@ sealed class Peer(string id) : IPeer
public int CloseCode { get; private set; } = int.MinValue;
+ public bool CanCollect => _callbackDataList.Count > 0;
+
public CancellationToken BeginPolling(ManualResetEvent manualResetEvent)
{
lock (_lockObject)
@@ -68,6 +53,7 @@ public void Disconect(int closeCode)
{
CloseCode = closeCode;
_cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_manualResetEvent?.Set();
}
@@ -91,11 +77,10 @@ public PollReply Collect()
Data = { item.Data },
});
}
+
return reply;
}
- public bool CanCollect => _callbackDataList.Count > 0;
-
public void AddCallback(CallbackData callbackData)
{
lock (_lockObject)
@@ -115,6 +100,7 @@ private CallbackData[] Flush()
_callbackDataList.Clear();
return items;
}
+
return [];
}
}
diff --git a/src/JSSoft.Communication/Grpc/PeerCollection.cs b/src/JSSoft.Communication/Grpc/PeerCollection.cs
index 07c5eec..32a8be8 100644
--- a/src/JSSoft.Communication/Grpc/PeerCollection.cs
+++ b/src/JSSoft.Communication/Grpc/PeerCollection.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Concurrent;
@@ -30,7 +13,7 @@
namespace JSSoft.Communication.Grpc;
-sealed class PeerCollection(IInstanceContext instanceContext)
+internal sealed class PeerCollection(IInstanceContext instanceContext)
: ConcurrentDictionary
{
private readonly IInstanceContext _instanceContext = instanceContext;
@@ -59,10 +42,12 @@ public bool Remove(IServiceContext serviceContext, string id, int closeCode)
serviceContext.Debug($"{id} Disconnected ({closeCode})");
return true;
}
+
return false;
}
- public async Task DisconnectAsync(IServiceContext serviceContext, CancellationToken cancellationToken)
+ public async Task DisconnectAsync(
+ IServiceContext serviceContext, CancellationToken cancellationToken)
{
var items = Values.ToArray();
using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 3000);
@@ -70,10 +55,12 @@ public async Task DisconnectAsync(IServiceContext serviceContext, CancellationTo
{
item.Disconect(closeCode: 0);
}
+
while (Count > 0 && cancellationTokenSource.IsCancellationRequested != true)
{
await Task.Delay(1, cancellationToken);
}
+
Clear();
}
}
diff --git a/src/JSSoft.Communication/IAdaptor.cs b/src/JSSoft.Communication/IAdaptor.cs
index 92956b9..0c37c31 100644
--- a/src/JSSoft.Communication/IAdaptor.cs
+++ b/src/JSSoft.Communication/IAdaptor.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Net;
@@ -29,19 +12,19 @@ namespace JSSoft.Communication;
public interface IAdaptor : IAsyncDisposable
{
+ event EventHandler? Disconnected;
+
Task OpenAsync(EndPoint endPoint, CancellationToken cancellationToken);
Task CloseAsync(CancellationToken cancellationToken);
- void InvokeOneWay(InstanceBase instance, string name, Type[] types, object?[] args);
+ void InvokeOneWay(InvokeOptions options);
- void Invoke(InstanceBase instance, string name, Type[] types, object?[] args);
+ void Invoke(InvokeOptions options);
- T Invoke(InstanceBase instance, string name, Type[] types, object?[] args);
+ T Invoke(InvokeOptions options);
- Task InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken);
+ Task InvokeAsync(InvokeOptions options, CancellationToken cancellationToken);
- Task InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken);
-
- event EventHandler? Disconnected;
+ Task InvokeAsync(InvokeOptions options, CancellationToken cancellationToken);
}
diff --git a/src/JSSoft.Communication/IAdaptorProvider.cs b/src/JSSoft.Communication/IAdaptorProvider.cs
index 92c4eda..8b55ae9 100644
--- a/src/JSSoft.Communication/IAdaptorProvider.cs
+++ b/src/JSSoft.Communication/IAdaptorProvider.cs
@@ -1,30 +1,16 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
public interface IAdaptorProvider
{
- IAdaptor Create(IServiceContext serviceContext, IInstanceContext instanceContext, ServiceToken serviceToken);
-
string Name { get; }
+
+ IAdaptor Create(
+ IServiceContext serviceContext,
+ IInstanceContext instanceContext,
+ ServiceToken serviceToken);
}
diff --git a/src/JSSoft.Communication/IInstanceContext.cs b/src/JSSoft.Communication/IInstanceContext.cs
index 011375e..6419a62 100644
--- a/src/JSSoft.Communication/IInstanceContext.cs
+++ b/src/JSSoft.Communication/IInstanceContext.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
diff --git a/src/JSSoft.Communication/ILGeneratorExtensions.cs b/src/JSSoft.Communication/ILGeneratorExtensions.cs
index 3d927e5..47a2512 100644
--- a/src/JSSoft.Communication/ILGeneratorExtensions.cs
+++ b/src/JSSoft.Communication/ILGeneratorExtensions.cs
@@ -1,40 +1,30 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System.Reflection.Emit;
namespace JSSoft.Communication;
-static class ILGeneratorExtensions
+internal static class ILGeneratorExtensions
{
- private static readonly OpCode[] Ldc_I4 = [ OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3,
- OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8 ];
- private static readonly OpCode[] Ldarg = [OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3];
+ private static readonly OpCode[] LdcI4 =
+ [
+ OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3,
+ OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8,
+ ];
+
+ private static readonly OpCode[] Ldarg =
+ [
+ OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3,
+ ];
public static void EmitLdc_I4(this ILGenerator il, int n)
{
- if (n < Ldc_I4.Length)
+ if (n < LdcI4.Length)
{
- il.Emit(Ldc_I4[n]);
+ il.Emit(LdcI4[n]);
}
else
{
diff --git a/src/JSSoft.Communication/IPeer.cs b/src/JSSoft.Communication/IPeer.cs
index 6b49611..d8ec6c3 100644
--- a/src/JSSoft.Communication/IPeer.cs
+++ b/src/JSSoft.Communication/IPeer.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
diff --git a/src/JSSoft.Communication/ISerializer.cs b/src/JSSoft.Communication/ISerializer.cs
index 0ea92f7..f35bf2c 100644
--- a/src/JSSoft.Communication/ISerializer.cs
+++ b/src/JSSoft.Communication/ISerializer.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
diff --git a/src/JSSoft.Communication/ISerializerProvider.cs b/src/JSSoft.Communication/ISerializerProvider.cs
index e994335..058308f 100644
--- a/src/JSSoft.Communication/ISerializerProvider.cs
+++ b/src/JSSoft.Communication/ISerializerProvider.cs
@@ -1,30 +1,13 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
public interface ISerializerProvider
{
- ISerializer Create(IServiceContext serviceContext);
-
string Name { get; }
+
+ ISerializer Create(IServiceContext serviceContext);
}
diff --git a/src/JSSoft.Communication/IService.cs b/src/JSSoft.Communication/IService.cs
index bfb77df..b8f624e 100644
--- a/src/JSSoft.Communication/IService.cs
+++ b/src/JSSoft.Communication/IService.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
diff --git a/src/JSSoft.Communication/IServiceContext.cs b/src/JSSoft.Communication/IServiceContext.cs
index 20c4658..39eb5ae 100644
--- a/src/JSSoft.Communication/IServiceContext.cs
+++ b/src/JSSoft.Communication/IServiceContext.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Generic;
@@ -30,6 +13,16 @@ namespace JSSoft.Communication;
public interface IServiceContext : IServiceProvider
{
+ event EventHandler? Opened;
+
+ event EventHandler? Closed;
+
+ event EventHandler? Faulted;
+
+ event EventHandler? Disconnected;
+
+ event EventHandler? ServiceStateChanged;
+
IReadOnlyDictionary Services { get; }
EndPoint EndPoint { get; set; }
@@ -43,14 +36,4 @@ public interface IServiceContext : IServiceProvider
Task CloseAsync(Guid token, CancellationToken cancellationToken);
Task AbortAsync();
-
- event EventHandler? Opened;
-
- event EventHandler? Closed;
-
- event EventHandler? Faulted;
-
- event EventHandler? Disconnected;
-
- event EventHandler? ServiceStateChanged;
}
diff --git a/src/JSSoft.Communication/InstanceBase.cs b/src/JSSoft.Communication/InstanceBase.cs
index bcd4bdb..dbf3259 100644
--- a/src/JSSoft.Communication/InstanceBase.cs
+++ b/src/JSSoft.Communication/InstanceBase.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Reflection;
@@ -41,6 +24,8 @@ public abstract class InstanceBase
private IService? _service;
private IPeer? _peer;
+ internal static InstanceBase Empty { get; } = new Instance();
+
internal IAdaptor Adaptor
{
get => _adaptor ?? throw new InvalidOperationException("adaptor is not set.");
@@ -61,60 +46,118 @@ internal IPeer Peer
set => _peer = value;
}
- internal static InstanceBase Empty { get; } = new Instance();
-
[InstanceMethod(InvokeMethod)]
protected void Invoke(string name, Type[] types, object?[] args)
- => Adaptor.Invoke(this, name, types, args);
-
- protected void Invoke((string name, Type[] types, object?[] args) info)
- => Adaptor.Invoke(this, info.name, info.types, info.args);
-
- [InstanceMethod(InvokeOneWayMethod)]
- protected void InvokeOneWay(string name, Type[] types, object?[] args)
- => Adaptor.InvokeOneWay(this, name, types, args);
-
- protected void InvokeOneWay((string name, Type[] types, object?[] args) info)
- => Adaptor.Invoke(this, info.name, info.types, info.args);
+ => Adaptor.Invoke(new InvokeOptions
+ {
+ Instance = this,
+ Name = name,
+ Types = types,
+ Args = args,
+ });
+
+ protected void Invoke((string Name, Type[] Types, object?[] Args) info)
+ => Adaptor.Invoke(new InvokeOptions
+ {
+ Instance = this,
+ Name = info.Name,
+ Types = info.Types,
+ Args = info.Args,
+ });
[InstanceMethod(InvokeGenericMethod)]
protected T Invoke(string name, Type[] types, object?[] args)
- => Adaptor.Invoke(this, name, types, args);
+ => Adaptor.Invoke(new InvokeOptions
+ {
+ Instance = this,
+ Name = name,
+ Types = types,
+ Args = args,
+ });
+
+ protected T Invoke((string Name, Type[] Types, object?[] Args) info)
+ => Adaptor.Invoke(new InvokeOptions
+ {
+ Instance = this,
+ Name = info.Name,
+ Types = info.Types,
+ Args = info.Args,
+ });
- protected T Invoke((string name, Type[] types, object?[] args) info)
- => Adaptor.Invoke(this, info.name, info.types, info.args);
+ [InstanceMethod(InvokeOneWayMethod)]
+ protected void InvokeOneWay(string name, Type[] types, object?[] args)
+ => Adaptor.InvokeOneWay(new InvokeOptions
+ {
+ Instance = this,
+ Name = name,
+ Types = types,
+ Args = args,
+ });
+
+ protected void InvokeOneWay((string Name, Type[] Types, object?[] Args) info)
+ => Adaptor.Invoke(new InvokeOptions
+ {
+ Instance = this,
+ Name = info.Name,
+ Types = info.Types,
+ Args = info.Args,
+ });
[InstanceMethod(InvokeAsyncMethod)]
- protected Task InvokeAsync(string name, Type[] types, object?[] args, CancellationToken cancellationToken)
- => Adaptor.InvokeAsync(this, name, types, args, cancellationToken);
+ protected Task InvokeAsync(
+ string name, Type[] types, object?[] args, CancellationToken cancellationToken)
+ {
+ var options = new InvokeOptions
+ {
+ Instance = this,
+ Name = name,
+ Types = types,
+ Args = args,
+ };
+ return Adaptor.InvokeAsync(options, cancellationToken);
+ }
- protected Task InvokeAsync((string name, Type[] types, object?[] args) info, CancellationToken cancellationToken)
- => Adaptor.InvokeAsync(this, info.name, info.types, info.args, cancellationToken);
+ protected Task InvokeAsync(
+ (string Name, Type[] Types, object?[] Args) info, CancellationToken cancellationToken)
+ {
+ var options = new InvokeOptions
+ {
+ Instance = this,
+ Name = info.Name,
+ Types = info.Types,
+ Args = info.Args,
+ };
+ return Adaptor.InvokeAsync(options, cancellationToken);
+ }
[InstanceMethod(InvokeGenericAsyncMethod)]
- protected Task InvokeAsync(string name, Type[] types, object?[] args, CancellationToken cancellationToken)
- => Adaptor.InvokeAsync(this, name, types, args, cancellationToken);
-
- protected Task InvokeAsync((string name, Type[] types, object?[] args) info, CancellationToken cancellationToken)
- => Adaptor.InvokeAsync(this, info.name, info.types, info.args, cancellationToken);
-
- protected static (string, Type[], object?[]) Info
(MethodInfo methodInfo, Type serviceType, P arg)
- => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P)], [arg]);
-
- protected static (string, Type[], object?[]) Info(MethodInfo methodInfo, Type serviceType, P1 arg1, P2 arg2)
- => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P1), typeof(P2)], [arg1, arg2]);
-
- protected static (string, Type[], object?[]) Info(MethodInfo methodInfo, Type serviceType, P1 arg1, P2 arg2, P3 arg3)
- => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P1), typeof(P2), typeof(P3)], [arg1, arg2, arg3]);
-
- protected static (string, Type[], object?[]) Info(MethodInfo methodInfo, Type serviceType, P1 arg1, P2 arg2, P3 arg3, P4 arg4)
- => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P1), typeof(P2), typeof(P3), typeof(P4)], [arg1, arg2, arg3, arg4]);
-
- #region Instance
+ protected Task InvokeAsync(
+ string name, Type[] types, object?[] args, CancellationToken cancellationToken)
+ {
+ var options = new InvokeOptions
+ {
+ Instance = this,
+ Name = name,
+ Types = types,
+ Args = args,
+ };
+ return Adaptor.InvokeAsync(options, cancellationToken);
+ }
- sealed class Instance : InstanceBase
+ protected Task InvokeAsync(
+ (string Name, Type[] Types, object?[] Args) info, CancellationToken cancellationToken)
{
+ var options = new InvokeOptions
+ {
+ Instance = this,
+ Name = info.Name,
+ Types = info.Types,
+ Args = info.Args,
+ };
+ return Adaptor.InvokeAsync(options, cancellationToken);
}
- #endregion
+ private sealed class Instance : InstanceBase
+ {
+ }
}
diff --git a/src/JSSoft.Communication/InstanceCollection.cs b/src/JSSoft.Communication/InstanceCollection.cs
index 112465a..5a52769 100644
--- a/src/JSSoft.Communication/InstanceCollection.cs
+++ b/src/JSSoft.Communication/InstanceCollection.cs
@@ -1,29 +1,12 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System.Collections.Generic;
namespace JSSoft.Communication;
-sealed class InstanceCollection : Dictionary
+internal sealed class InstanceCollection : Dictionary
{
}
diff --git a/src/JSSoft.Communication/InstanceContext.cs b/src/JSSoft.Communication/InstanceContext.cs
index a509d99..279dc4d 100644
--- a/src/JSSoft.Communication/InstanceContext.cs
+++ b/src/JSSoft.Communication/InstanceContext.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Concurrent;
@@ -26,7 +9,7 @@
namespace JSSoft.Communication;
-sealed class InstanceContext(ServiceContextBase serviceContext)
+internal sealed class InstanceContext(ServiceContextBase serviceContext)
: IInstanceContext, IPeer
{
private readonly ConcurrentDictionary _descriptorByPeer = new();
@@ -49,7 +32,6 @@ where ServiceContextBase.IsPerPeer(_serviceContext, item) != true
public void ReleaseInstance()
{
- var isServer = ServiceContextBase.IsServer(_serviceContext);
var query = from item in _serviceContext.Services.Values.Reverse()
where ServiceContextBase.IsPerPeer(_serviceContext, item) != true
select item;
@@ -73,10 +55,12 @@ public PeerDescriptor CreateInstance(IPeer peer)
}
else
{
- var (service, callback) = (_descriptor.ServerInstances[item], _descriptor.ClientInstances[item]);
+ var service = _descriptor.ServerInstances[item];
+ var callback = _descriptor.ClientInstances[item];
peerDescriptor.AddInstance(item, service, callback);
}
}
+
_descriptorByPeer.TryAdd(peer, peerDescriptor);
return peerDescriptor;
}
@@ -84,7 +68,10 @@ public PeerDescriptor CreateInstance(IPeer peer)
public void DestroyInstance(IPeer peer)
{
if (_descriptorByPeer.TryRemove(peer, out var peerDescriptor) == false)
+ {
return;
+ }
+
foreach (var item in _serviceContext.Services.Values.Reverse())
{
var isPerPeer = ServiceContextBase.IsPerPeer(_serviceContext, item);
@@ -98,15 +85,15 @@ public void DestroyInstance(IPeer peer)
peerDescriptor.RemoveInstance(item);
}
}
- peerDescriptor.Dispose();
+ peerDescriptor.Dispose();
}
public object? GetService(Type serviceType)
{
var query = from descriptor in _descriptorByPeer.Values
from service in descriptor.ServerInstances.Values
- where serviceType.IsAssignableFrom(service.GetType()) == true
+ where serviceType.IsInstanceOfType(service) == true
select service;
return query.SingleOrDefault();
}
diff --git a/src/JSSoft.Communication/InstanceMethodAttribute.cs b/src/JSSoft.Communication/InstanceMethodAttribute.cs
index d200b29..e170909 100644
--- a/src/JSSoft.Communication/InstanceMethodAttribute.cs
+++ b/src/JSSoft.Communication/InstanceMethodAttribute.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
diff --git a/src/JSSoft.Communication/InvokeOptions.cs b/src/JSSoft.Communication/InvokeOptions.cs
new file mode 100644
index 0000000..7763754
--- /dev/null
+++ b/src/JSSoft.Communication/InvokeOptions.cs
@@ -0,0 +1,19 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+
+namespace JSSoft.Communication;
+
+public sealed record class InvokeOptions
+{
+ public required InstanceBase Instance { get; init; }
+
+ public string Name { get; init; } = string.Empty;
+
+ public Type[] Types { get; init; } = [];
+
+ public object?[] Args { get; init; } = [];
+}
diff --git a/src/JSSoft.Communication/InvokeResult.cs b/src/JSSoft.Communication/InvokeResult.cs
new file mode 100644
index 0000000..1c1f275
--- /dev/null
+++ b/src/JSSoft.Communication/InvokeResult.cs
@@ -0,0 +1,17 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+
+namespace JSSoft.Communication;
+
+public sealed record class InvokeResult
+{
+ public required string AssemblyQualifiedName { get; init; }
+
+ public required Type ValueType { get; init; }
+
+ public object? Value { get; init; }
+}
diff --git a/src/JSSoft.Communication/JsonSerializer.cs b/src/JSSoft.Communication/JsonSerializer.cs
index c787228..86b66b8 100644
--- a/src/JSSoft.Communication/JsonSerializer.cs
+++ b/src/JSSoft.Communication/JsonSerializer.cs
@@ -1,37 +1,20 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
-using Newtonsoft.Json;
using System;
+using Newtonsoft.Json;
namespace JSSoft.Communication;
-sealed class JsonSerializer : ISerializer
+internal sealed class JsonSerializer : ISerializer
{
- private static readonly JsonSerializerSettings settings = new();
+ private static readonly JsonSerializerSettings Settings = new();
public string Serialize(Type type, object? data)
- => JsonConvert.SerializeObject(data, type, settings);
+ => JsonConvert.SerializeObject(data, type, Settings);
public object? Deserialize(Type type, string text)
- => JsonConvert.DeserializeObject(text, type, settings);
+ => JsonConvert.DeserializeObject(text, type, Settings);
}
diff --git a/src/JSSoft.Communication/JsonSerializerProvider.cs b/src/JSSoft.Communication/JsonSerializerProvider.cs
index f795181..fadb296 100644
--- a/src/JSSoft.Communication/JsonSerializerProvider.cs
+++ b/src/JSSoft.Communication/JsonSerializerProvider.cs
@@ -1,35 +1,18 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
-sealed class JsonSerializerProvider : ISerializerProvider
+internal sealed class JsonSerializerProvider : ISerializerProvider
{
public const string DefaultName = "json";
- public ISerializer Create(IServiceContext serviceContext)
- => new JsonSerializer();
+ public static readonly JsonSerializerProvider Default = new();
public string Name => DefaultName;
- public static readonly JsonSerializerProvider Default = new();
+ public ISerializer Create(IServiceContext serviceContext)
+ => new JsonSerializer();
}
diff --git a/src/JSSoft.Communication/Logging/ConsoleLogger.cs b/src/JSSoft.Communication/Logging/ConsoleLogger.cs
index 1f0fddb..a17b431 100644
--- a/src/JSSoft.Communication/Logging/ConsoleLogger.cs
+++ b/src/JSSoft.Communication/Logging/ConsoleLogger.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
@@ -26,6 +9,8 @@ namespace JSSoft.Communication.Logging;
public sealed class ConsoleLogger : ILogger
{
+ public static readonly ConsoleLogger Default = new();
+
public void Debug(object message) => Console.WriteLine(message);
public void Info(object message) => Console.WriteLine(message);
@@ -35,6 +20,4 @@ public sealed class ConsoleLogger : ILogger
public void Warn(object message) => Console.WriteLine(message);
public void Fatal(object message) => Console.Error.WriteLine(message);
-
- public static readonly ConsoleLogger Default = new();
}
diff --git a/src/JSSoft.Communication/Logging/EmptyLogger.cs b/src/JSSoft.Communication/Logging/EmptyLogger.cs
index 922c411..3bb334e 100644
--- a/src/JSSoft.Communication/Logging/EmptyLogger.cs
+++ b/src/JSSoft.Communication/Logging/EmptyLogger.cs
@@ -1,29 +1,14 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication.Logging;
public class EmptyLogger : ILogger
{
+ public static readonly EmptyLogger Default = new();
+
public void Debug(object message)
{
}
@@ -43,6 +28,4 @@ public void Warn(object message)
public void Fatal(object message)
{
}
-
- public static readonly EmptyLogger Default = new();
}
diff --git a/src/JSSoft.Communication/Logging/ILogger.cs b/src/JSSoft.Communication/Logging/ILogger.cs
index 2081175..3d8cb67 100644
--- a/src/JSSoft.Communication/Logging/ILogger.cs
+++ b/src/JSSoft.Communication/Logging/ILogger.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication.Logging;
diff --git a/src/JSSoft.Communication/Logging/ILoggerExtensions.cs b/src/JSSoft.Communication/Logging/ILoggerExtensions.cs
index e24b65d..6f8b66b 100644
--- a/src/JSSoft.Communication/Logging/ILoggerExtensions.cs
+++ b/src/JSSoft.Communication/Logging/ILoggerExtensions.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
diff --git a/src/JSSoft.Communication/Logging/LogLevel.cs b/src/JSSoft.Communication/Logging/LogLevel.cs
index b1df4fc..1981a1a 100644
--- a/src/JSSoft.Communication/Logging/LogLevel.cs
+++ b/src/JSSoft.Communication/Logging/LogLevel.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication.Logging;
diff --git a/src/JSSoft.Communication/Logging/LogUtility.cs b/src/JSSoft.Communication/Logging/LogUtility.cs
index f8cccd4..f224fae 100644
--- a/src/JSSoft.Communication/Logging/LogUtility.cs
+++ b/src/JSSoft.Communication/Logging/LogUtility.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication.Logging;
@@ -28,35 +11,45 @@ public static class LogUtility
public static LogLevel LogLevel { get; set; } = LogLevel.Fatal;
+ private static ILogger LoggerInternal => Logger ?? EmptyLogger.Default;
+
public static void Debug(object message)
{
if (LogLevel >= LogLevel.Debug)
+ {
LoggerInternal.Debug(message);
+ }
}
public static void Info(object message)
{
if (LogLevel >= LogLevel.Info)
+ {
LoggerInternal.Info(message);
+ }
}
public static void Error(object message)
{
if (LogLevel >= LogLevel.Error)
+ {
LoggerInternal.Error(message);
+ }
}
public static void Warn(object message)
{
if (LogLevel >= LogLevel.Warn)
+ {
LoggerInternal.Warn(message);
+ }
}
public static void Fatal(object message)
{
if (LogLevel >= LogLevel.Fatal)
+ {
LoggerInternal.Fatal(message);
+ }
}
-
- private static ILogger LoggerInternal => Logger ?? EmptyLogger.Default;
}
diff --git a/src/JSSoft.Communication/Logging/TraceLogger.cs b/src/JSSoft.Communication/Logging/TraceLogger.cs
index e9ea38c..5476dc2 100644
--- a/src/JSSoft.Communication/Logging/TraceLogger.cs
+++ b/src/JSSoft.Communication/Logging/TraceLogger.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System.Diagnostics;
@@ -26,7 +9,9 @@ namespace JSSoft.Communication.Logging;
public sealed class TraceLogger : ILogger
{
- public void Debug(object message) => Trace.WriteLine(message);
+ public static readonly TraceLogger Default = new();
+
+ public void Debug(object message) => Trace.TraceInformation($"{message}");
public void Info(object message) => Trace.TraceInformation($"{message}");
@@ -35,6 +20,4 @@ public sealed class TraceLogger : ILogger
public void Warn(object message) => Trace.TraceWarning($"{message}");
public void Fatal(object message) => Trace.TraceError($"{message}");
-
- public static readonly TraceLogger Default = new();
}
diff --git a/src/JSSoft.Communication/MethodDescriptor.cs b/src/JSSoft.Communication/MethodDescriptor.cs
index 585cbbd..b80a9a1 100644
--- a/src/JSSoft.Communication/MethodDescriptor.cs
+++ b/src/JSSoft.Communication/MethodDescriptor.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Linq;
@@ -43,9 +26,10 @@ public MethodDescriptor(MethodInfo methodInfo)
}
else if (ReturnType.IsSubclassOf(typeof(Task)) == true)
{
- ReturnType = ReturnType.GetGenericArguments().First();
+ ReturnType = ReturnType.GetGenericArguments()[0];
IsAsync = true;
}
+
Name = GenerateName(methodInfo);
ShortName = methodInfo.Name;
IsOneWay = IsMethodOneWay(methodInfo);
@@ -69,59 +53,103 @@ public MethodDescriptor(MethodInfo methodInfo)
public MethodInfo MethodInfo { get; }
- private static void Verify(MethodInfo methodInfo)
+ // "async" methods should not return "void"
+#pragma warning disable S3168
+ internal async void InvokeOneWay(
+ IServiceProvider serviceProvider, object instance, object?[] args)
{
- var parameterInfos = methodInfo.GetParameters();
- var isAsync = typeof(Task).IsAssignableFrom(methodInfo.ReturnType);
- if (isAsync == true)
- {
- if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) > 1)
- throw new InvalidOperationException($"In method '{methodInfo}', only one parameter of type {nameof(CancellationToken)} should be defined.");
- if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) == 1 &&
- parameterInfos.Last().ParameterType != typeof(CancellationToken))
- throw new InvalidOperationException($"The last parameter of method '{methodInfo}' must be of type {nameof(CancellationToken)}.");
- }
- else if (methodInfo.ReturnType == typeof(void))
+ try
{
- if (parameterInfos.Any(item => item.ParameterType == typeof(CancellationToken)) == true)
- throw new InvalidOperationException($"The {nameof(CancellationToken)} type cannot be used in method '{methodInfo}'.");
+ await InvokeAsync(instance, args);
}
- else
+ catch
{
- throw new InvalidOperationException($"The return type of method '{methodInfo}' must be '{typeof(Task)}' or '{typeof(void)}'.");
+ // do nothing
}
}
+#pragma warning restore S3168
- internal async void InvokeOneWay(IServiceProvider serviceProvider, object instance, object?[] args)
+ internal async Task InvokeAsync(
+ IServiceProvider serviceProvider, object instance, object?[] args)
{
try
{
- await InvokeAsync(instance, args);
+ var (valueType, value) = await InvokeAsync(instance, args);
+ return new InvokeResult
+ {
+ AssemblyQualifiedName = string.Empty,
+ ValueType = valueType,
+ Value = value,
+ };
}
- catch
+ catch (TargetInvocationException e)
{
+ var exception = e.InnerException ?? e;
+ return new InvokeResult
+ {
+ AssemblyQualifiedName = exception.GetType().AssemblyQualifiedName!,
+ ValueType = exception.GetType(),
+ Value = exception,
+ };
+ }
+ catch (Exception e)
+ {
+ return new InvokeResult
+ {
+ AssemblyQualifiedName = e.GetType().AssemblyQualifiedName!,
+ ValueType = e.GetType(),
+ Value = e,
+ };
}
}
- internal async Task<(string, Type, object?)> InvokeAsync(IServiceProvider serviceProvider, object instance, object?[] args)
+ private static void Verify(MethodInfo methodInfo)
{
- try
+ var parameterInfos = methodInfo.GetParameters();
+ var isAsync = typeof(Task).IsAssignableFrom(methodInfo.ReturnType);
+ if (isAsync == true)
{
- var (type, value) = await InvokeAsync(instance, args);
- return (string.Empty, type, value);
+ if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) > 1)
+ {
+ var message = $"""
+ In method '{methodInfo}', only one parameter of type
+ {nameof(CancellationToken)} should be defined.
+ """;
+ throw new InvalidOperationException(message);
+ }
+
+ if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) == 1
+ && parameterInfos.Last().ParameterType != typeof(CancellationToken))
+ {
+ var message = $"""
+ The last parameter of method '{methodInfo}' must be of
+ type {nameof(CancellationToken)}.
+ """;
+ throw new InvalidOperationException(message);
+ }
}
- catch (TargetInvocationException e)
+ else if (methodInfo.ReturnType == typeof(void))
{
- var exception = e.InnerException ?? e;
- return (exception.GetType().AssemblyQualifiedName!, exception.GetType(), exception);
+ if (parameterInfos.Any(item => item.ParameterType == typeof(CancellationToken)) == true)
+ {
+ var message = $"""
+ The {nameof(CancellationToken)} type cannot be used in method '{methodInfo}'.
+ """;
+ throw new InvalidOperationException(message);
+ }
}
- catch (Exception e)
+ else
{
- return (e.GetType().AssemblyQualifiedName!, e.GetType(), e);
+ var message = $"""
+ The return type of method '{methodInfo}' must be
+ '{typeof(Task)}' or '{typeof(void)}'.
+ """;
+ throw new InvalidOperationException(message);
}
}
- private async Task<(Type, object?)> InvokeAsync(object? instance, object?[] args)
+ private async Task<(Type ValueType, object? Value)> InvokeAsync(
+ object? instance, object?[] args)
{
var value = await Task.Run(() => MethodInfo.Invoke(instance, args));
var valueType = MethodInfo.ReturnType;
diff --git a/src/JSSoft.Communication/MethodDescriptorCollection.cs b/src/JSSoft.Communication/MethodDescriptorCollection.cs
index 3b4a78f..bd98b86 100644
--- a/src/JSSoft.Communication/MethodDescriptorCollection.cs
+++ b/src/JSSoft.Communication/MethodDescriptorCollection.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections;
@@ -52,17 +35,13 @@ internal MethodDescriptorCollection(Type type)
public int Count => _discriptorByName.Count;
- public bool Contains(string name) => _discriptorByName.ContainsKey(name);
-
public MethodDescriptor this[string name] => _discriptorByName[name];
- #region IEnumerator
+ public bool Contains(string name) => _discriptorByName.ContainsKey(name);
public IEnumerator GetEnumerator()
=> _discriptorByName.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _discriptorByName.Values.GetEnumerator();
-
- #endregion
}
diff --git a/src/JSSoft.Communication/MethodUtility.cs b/src/JSSoft.Communication/MethodUtility.cs
index 646f519..071fd82 100644
--- a/src/JSSoft.Communication/MethodUtility.cs
+++ b/src/JSSoft.Communication/MethodUtility.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Linq;
@@ -31,17 +14,28 @@ public static class MethodUtility
{
public static string GenerateName(MethodInfo methodInfo)
{
- var parameterTypes = methodInfo.GetParameters().Select(item => item.ParameterType).ToArray();
- return GenerateName(methodInfo.ReturnType, methodInfo.ReflectedType!, methodInfo.Name, parameterTypes);
+ var returnType = methodInfo.ReturnType;
+ var reflectedType = methodInfo.ReflectedType
+ ?? throw new ArgumentException("reflected type is null", nameof(methodInfo));
+ var name = methodInfo.Name;
+ var parameterTypes = methodInfo.GetParameters()
+ .Select(item => item.ParameterType)
+ .ToArray();
+ return GenerateName(returnType, reflectedType, name, parameterTypes);
}
public static string GenerateName(MethodInfo methodInfo, Type serviceType)
{
- var parameterTypes = methodInfo.GetParameters().Select(item => item.ParameterType).ToArray();
- return GenerateName(methodInfo.ReturnType, serviceType, methodInfo.Name, parameterTypes);
+ var returnType = methodInfo.ReturnType;
+ var name = methodInfo.Name;
+ var parameterTypes = methodInfo.GetParameters()
+ .Select(item => item.ParameterType)
+ .ToArray();
+ return GenerateName(returnType, serviceType, name, parameterTypes);
}
- public static string GenerateName(Type returnType, Type reflectedType, string methodName, params Type[] parameterTypes)
+ public static string GenerateName(
+ Type returnType, Type reflectedType, string methodName, params Type[] parameterTypes)
{
var parameterTypeNames = string.Join(", ", parameterTypes);
return $"{returnType} {reflectedType}.{methodName}({parameterTypeNames})";
@@ -55,8 +49,12 @@ public static bool IsMethodOneWay(MethodInfo methodInfo)
public static bool IsMethodCancelable(MethodInfo methodInfo)
{
var parameterInfos = methodInfo.GetParameters();
- if (parameterInfos.Length > 0 && parameterInfos[parameterInfos.Length - 1].ParameterType == typeof(CancellationToken))
+ if (parameterInfos.Length > 0
+ && parameterInfos[^1].ParameterType == typeof(CancellationToken))
+ {
return true;
+ }
+
return false;
}
diff --git a/src/JSSoft.Communication/PeerDescriptor.cs b/src/JSSoft.Communication/PeerDescriptor.cs
index 1d04ef8..f4a058f 100644
--- a/src/JSSoft.Communication/PeerDescriptor.cs
+++ b/src/JSSoft.Communication/PeerDescriptor.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Generic;
@@ -36,30 +19,34 @@ public sealed class PeerDescriptor : IDisposable
public void Dispose()
{
- if (_isDisposed == true)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_isDisposed, this);
var items = ClientInstances.Values.OfType().ToArray();
foreach (var item in items)
{
item.Dispose();
}
+
_isDisposed = true;
}
public void AddInstance(IService service, object serverInstance, object clientInstance)
{
if (_isDisposed == true)
+ {
throw new ObjectDisposedException($"{this}");
+ }
ServerInstances.Add(service, serverInstance);
ClientInstances.Add(service, clientInstance);
}
- public (object serverInstance, object clientInstance) RemoveInstance(IService service)
+ public (object ServerInstance, object ClientInstance) RemoveInstance(IService service)
{
if (_isDisposed == true)
+ {
throw new ObjectDisposedException($"{this}");
+ }
var value = (ServerInstances[service], ClientInstances[service]);
ServerInstances.Remove(service);
diff --git a/src/JSSoft.Communication/ServerContext.cs b/src/JSSoft.Communication/ServerContext.cs
index 6777716..c0de9c5 100644
--- a/src/JSSoft.Communication/ServerContext.cs
+++ b/src/JSSoft.Communication/ServerContext.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
diff --git a/src/JSSoft.Communication/ServerService.cs b/src/JSSoft.Communication/ServerService.cs
index e7d9e0a..a9a23e0 100644
--- a/src/JSSoft.Communication/ServerService.cs
+++ b/src/JSSoft.Communication/ServerService.cs
@@ -1,24 +1,10 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+// File may only contain a single type
+#pragma warning disable SA1402
using System;
@@ -40,18 +26,20 @@ public ServerService(TServer server)
public ServerService()
: base(typeof(TServer), typeof(TClient))
{
- if (this is TServer server)
+ var obj = this;
+ if (obj is TServer server)
{
_server = server;
-
}
else
{
- throw new InvalidOperationException($"'{GetType()}' must be implemented by '{typeof(TServer)}'.");
+ throw new InvalidOperationException(
+ $"'{GetType()}' must be implemented by '{typeof(TServer)}'.");
}
}
- public TClient Client => _client ?? throw new InvalidOperationException("Client is not created.");
+ public TClient Client
+ => _client ?? throw new InvalidOperationException("Client is not created.");
protected virtual TServer CreateServer(IPeer peer) => _server;
@@ -85,13 +73,15 @@ public ServerService(TServer server)
public ServerService()
: base(serverType: typeof(TServer), clientType: typeof(void))
{
- if (this is TServer server)
+ var obj = this;
+ if (obj is TServer server)
{
_server = server;
}
else
{
- throw new InvalidOperationException($"'{GetType()}' must be implemented by '{typeof(TServer)}'.");
+ throw new InvalidOperationException(
+ $"'{GetType()}' must be implemented by '{typeof(TServer)}'.");
}
}
diff --git a/src/JSSoft.Communication/ServiceAttribute.cs b/src/JSSoft.Communication/ServiceAttribute.cs
index 619fc43..70b04eb 100644
--- a/src/JSSoft.Communication/ServiceAttribute.cs
+++ b/src/JSSoft.Communication/ServiceAttribute.cs
@@ -1,31 +1,14 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
namespace JSSoft.Communication;
[AttributeUsage(AttributeTargets.Class)]
-sealed class ServiceAttribute : Attribute
+internal sealed class ServiceAttribute : Attribute
{
public bool IsServer { get; set; }
}
diff --git a/src/JSSoft.Communication/ServiceBase.cs b/src/JSSoft.Communication/ServiceBase.cs
index e4aaa81..78deca2 100644
--- a/src/JSSoft.Communication/ServiceBase.cs
+++ b/src/JSSoft.Communication/ServiceBase.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
@@ -32,15 +15,11 @@ public abstract class ServiceBase(Type serverType, Type clientType) : IService
public string Name { get; } = serverType.Name;
- protected abstract object CreateInstance(IPeer peer, object obj);
-
- protected abstract void DestroyInstance(IPeer peer, object obj);
-
- #region IService
-
object IService.CreateInstance(IPeer peer, object obj) => CreateInstance(peer, obj);
void IService.DestroyInstance(IPeer peer, object obj) => DestroyInstance(peer, obj);
- #endregion
+ protected abstract object CreateInstance(IPeer peer, object obj);
+
+ protected abstract void DestroyInstance(IPeer peer, object obj);
}
diff --git a/src/JSSoft.Communication/ServiceCollection.cs b/src/JSSoft.Communication/ServiceCollection.cs
index 3ce341e..416e064 100644
--- a/src/JSSoft.Communication/ServiceCollection.cs
+++ b/src/JSSoft.Communication/ServiceCollection.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System.Collections;
using System.Collections.Generic;
@@ -27,11 +10,11 @@
namespace JSSoft.Communication;
-public class ServiceCollection(IEnumerable services) : IReadOnlyDictionary
+public class ServiceCollection(IEnumerable services)
+ : IReadOnlyDictionary
{
- private readonly Dictionary _serviceByName = services.ToDictionary(item => item.Name);
-
- public IService this[string key] => _serviceByName[key];
+ private readonly Dictionary _serviceByName
+ = services.ToDictionary(item => item.Name);
public IEnumerable Keys => _serviceByName.Keys;
@@ -39,6 +22,8 @@ public class ServiceCollection(IEnumerable services) : IReadOnlyDictio
public int Count => _serviceByName.Count;
+ public IService this[string key] => _serviceByName[key];
+
public bool ContainsKey(string key) => _serviceByName.ContainsKey(key);
#if NETSTANDARD
@@ -49,13 +34,9 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out IService value)
=> _serviceByName.TryGetValue(key, out value);
#endif
- #region IEnumerable
-
- IEnumerator> IEnumerable>.GetEnumerator()
+ public IEnumerator> GetEnumerator()
=> _serviceByName.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _serviceByName.GetEnumerator();
-
- #endregion
}
diff --git a/src/JSSoft.Communication/ServiceContextAttribute.cs b/src/JSSoft.Communication/ServiceContextAttribute.cs
index 07e88f9..3a81e83 100644
--- a/src/JSSoft.Communication/ServiceContextAttribute.cs
+++ b/src/JSSoft.Communication/ServiceContextAttribute.cs
@@ -1,31 +1,14 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
namespace JSSoft.Communication;
[AttributeUsage(AttributeTargets.Class)]
-sealed class ServiceContextAttribute : Attribute
+internal sealed class ServiceContextAttribute : Attribute
{
public bool IsServer { get; set; }
}
diff --git a/src/JSSoft.Communication/ServiceContextBase.cs b/src/JSSoft.Communication/ServiceContextBase.cs
index 373eb51..90f36f4 100644
--- a/src/JSSoft.Communication/ServiceContextBase.cs
+++ b/src/JSSoft.Communication/ServiceContextBase.cs
@@ -1,32 +1,15 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
-using JSSoft.Communication.Logging;
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using JSSoft.Communication.Logging;
namespace JSSoft.Communication;
@@ -53,6 +36,19 @@ protected ServiceContextBase(IService[] services)
_instanceContext = new InstanceContext(this);
}
+ public event EventHandler? Opened;
+
+ public event EventHandler? Closed;
+
+ public event EventHandler? Faulted;
+
+ ///
+ /// Disconnected event is not used on the server side.
+ ///
+ public event EventHandler? Disconnected;
+
+ public event EventHandler? ServiceStateChanged;
+
public virtual IAdaptorProvider AdaptorProvider => Communication.AdaptorProvider.Default;
public virtual ISerializerProvider SerializerProvider => JsonSerializerProvider.Default;
@@ -66,9 +62,9 @@ private set
{
if (_serviceState != value)
{
- var _ = _serviceState;
+ var serviceState = _serviceState;
_serviceState = value;
- Debug($"{nameof(ServiceState)}.{_} => {nameof(ServiceState)}.{value}");
+ Debug($"{nameof(ServiceState)}.{serviceState} => {nameof(ServiceState)}.{value}");
OnServiceStateChanged(EventArgs.Empty);
}
}
@@ -80,19 +76,30 @@ public EndPoint EndPoint
set
{
if (ServiceState != ServiceState.None)
- throw new InvalidOperationException($"Cannot set '{nameof(EndPoint)}'. service state is '{ServiceState.None}'.");
+ {
+ var message = $"""
+ Cannot set '{nameof(EndPoint)}'. service state is '{ServiceState.None}'.
+ """;
+ throw new InvalidOperationException(message);
+ }
+
_endPoint = value;
}
}
public Guid Id { get; } = Guid.NewGuid();
+ IReadOnlyDictionary IServiceContext.Services => Services;
+
public override string ToString() => $"{_t}[{Id}]";
public async Task OpenAsync(CancellationToken cancellationToken)
{
if (ServiceState != ServiceState.None)
- throw new InvalidOperationException($"Service can only be open if the state is '{ServiceState.None}'.");
+ {
+ var message = $"Service can only be open if the state is '{ServiceState.None}'.";
+ throw new InvalidOperationException(message);
+ }
try
{
@@ -124,9 +131,15 @@ public async Task OpenAsync(CancellationToken cancellationToken)
public async Task CloseAsync(Guid token, CancellationToken cancellationToken)
{
if (ServiceState != ServiceState.Open)
- throw new InvalidOperationException($"Service can only be closed if the state is '{ServiceState.Open}'.");
+ {
+ throw new InvalidOperationException(
+ $"Service can only be closed if the state is '{ServiceState.Open}'.");
+ }
+
if (token == Guid.Empty || _token!.Guid != token)
+ {
throw new ArgumentException($"'{token}' is an invalid token.", nameof(token));
+ }
try
{
@@ -158,7 +171,10 @@ public async Task CloseAsync(Guid token, CancellationToken cancellationToken)
public async Task AbortAsync()
{
if (ServiceState != ServiceState.Faulted)
- throw new InvalidOperationException($"Service can only be aborted if the state is '{ServiceState.Faulted}'.");
+ {
+ throw new InvalidOperationException(
+ $"Service can only be aborted if the state is '{ServiceState.Faulted}'.");
+ }
_token = null;
_serializer = null;
@@ -170,6 +186,7 @@ public async Task AbortAsync()
Debug($"{nameof(IAdaptor)} ({AdaptorProvider.Name}) disposed.");
_adaptor = null;
}
+
ServiceState = ServiceState.None;
OnClosed(EventArgs.Empty);
}
@@ -177,57 +194,27 @@ public async Task AbortAsync()
public virtual object? GetService(Type serviceType)
{
if (serviceType == typeof(ISerializer))
+ {
return _serializer;
+ }
+
if (_instanceContext.GetService(serviceType) is { } instanceService)
+ {
return instanceService;
- return null;
- }
-
- public event EventHandler? Opened;
-
- public event EventHandler? Closed;
-
- public event EventHandler? Faulted;
-
- ///
- /// Disconnected event is not used on the server side.
- ///
- public event EventHandler? Disconnected;
-
- public event EventHandler? ServiceStateChanged;
+ }
- protected virtual InstanceBase CreateInstance(Type type)
- {
- if (_instanceBuilder == null)
- throw new InvalidOperationException($"Cannot create instance of {type}");
- if (type == typeof(void))
- return InstanceBase.Empty;
- var typeName = $"{type.Name}Impl";
- var instanceType = _instanceBuilder.CreateType(typeName, typeof(InstanceBase), type);
- return (InstanceBase)Activator.CreateInstance(instanceType)!;
+ return null;
}
- protected virtual void OnOpened(EventArgs e)
- => Opened?.Invoke(this, e);
-
- protected virtual void OnClosed(EventArgs e)
- => Closed?.Invoke(this, e);
-
- protected virtual void OnFaulted(EventArgs e)
- => Faulted?.Invoke(this, e);
-
- protected virtual void OnDisconnected(EventArgs e)
- => Disconnected?.Invoke(this, e);
-
- protected virtual void OnServiceStateChanged(EventArgs e)
- => ServiceStateChanged?.Invoke(this, e);
-
internal static bool IsServer(ServiceContextBase serviceContext)
{
- if (serviceContext.GetType().GetCustomAttribute(typeof(ServiceContextAttribute)) is ServiceContextAttribute attribute)
+ var serviceContextType = serviceContext.GetType();
+ var attribute = serviceContextType.GetCustomAttribute(typeof(ServiceContextAttribute));
+ if (attribute is ServiceContextAttribute serviceContextAttribute)
{
- return attribute.IsServer;
+ return serviceContextAttribute.IsServer;
}
+
return false;
}
@@ -238,31 +225,37 @@ internal static Type GetInstanceType(ServiceContextBase serviceContext, IService
{
return service.ClientType;
}
+
return service.ServerType;
}
internal static bool IsPerPeer(ServiceContextBase serviceContext, IService service)
{
if (IsServer(serviceContext) != true)
+ {
return false;
+ }
+
var serviceType = service.ServerType;
- if (serviceType.GetCustomAttribute(typeof(ServiceContractAttribute)) is ServiceContractAttribute attribute)
+ var attribute = serviceType.GetCustomAttribute(typeof(ServiceContractAttribute));
+ if (attribute is ServiceContractAttribute serviceContractAttribute)
{
- return attribute.PerPeer;
+ return serviceContractAttribute.PerPeer;
}
+
return false;
}
- internal (object, object) CreateInstance(IService service, IPeer peer)
+ internal (object ServiceInstance, object ClientInstance) CreateInstance(
+ IService service, IPeer peer)
{
var adaptor = _adaptor!;
var baseType = GetInstanceType(this, service);
var instance = CreateInstance(baseType);
- {
- instance.Service = service;
- instance.Adaptor = adaptor;
- instance.Peer = peer;
- }
+
+ instance.Service = service;
+ instance.Adaptor = adaptor;
+ instance.Peer = peer;
var impl = service.CreateInstance(peer, instance);
var serverInstance = _isServer ? impl : instance;
@@ -270,7 +263,8 @@ internal static bool IsPerPeer(ServiceContextBase serviceContext, IService servi
return (serverInstance, clientInstance);
}
- internal void DestroyInstance(IService service, IPeer peer, object serverInstance, object clientInstance)
+ internal void DestroyInstance(
+ IService service, IPeer peer, object serverInstance, object clientInstance)
{
if (_isServer == true)
{
@@ -282,6 +276,38 @@ internal void DestroyInstance(IService service, IPeer peer, object serverInstanc
}
}
+ protected virtual InstanceBase CreateInstance(Type type)
+ {
+ if (_instanceBuilder == null)
+ {
+ throw new InvalidOperationException($"Cannot create instance of {type}");
+ }
+
+ if (type == typeof(void))
+ {
+ return InstanceBase.Empty;
+ }
+
+ var typeName = $"{type.Name}Impl";
+ var instanceType = _instanceBuilder.CreateType(typeName, typeof(InstanceBase), type);
+ return (InstanceBase)Activator.CreateInstance(instanceType)!;
+ }
+
+ protected virtual void OnOpened(EventArgs e)
+ => Opened?.Invoke(this, e);
+
+ protected virtual void OnClosed(EventArgs e)
+ => Closed?.Invoke(this, e);
+
+ protected virtual void OnFaulted(EventArgs e)
+ => Faulted?.Invoke(this, e);
+
+ protected virtual void OnDisconnected(EventArgs e)
+ => Disconnected?.Invoke(this, e);
+
+ protected virtual void OnServiceStateChanged(EventArgs e)
+ => ServiceStateChanged?.Invoke(this, e);
+
private void Debug(string message)
=> LogUtility.Debug($"{this} {message}");
@@ -301,10 +327,4 @@ private void Adaptor_Disconnected(object? sender, EventArgs e)
Debug($"{nameof(IServiceContext)} ({this.GetType()}) closed.");
OnClosed(EventArgs.Empty);
}
-
- #region IService
-
- IReadOnlyDictionary IServiceContext.Services => Services;
-
- #endregion
}
diff --git a/src/JSSoft.Communication/ServiceContractAttribute.cs b/src/JSSoft.Communication/ServiceContractAttribute.cs
index e7df73b..6a2c9be 100644
--- a/src/JSSoft.Communication/ServiceContractAttribute.cs
+++ b/src/JSSoft.Communication/ServiceContractAttribute.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
diff --git a/src/JSSoft.Communication/ServiceInstanceBuilder.cs b/src/JSSoft.Communication/ServiceInstanceBuilder.cs
index f124f8b..571330e 100644
--- a/src/JSSoft.Communication/ServiceInstanceBuilder.cs
+++ b/src/JSSoft.Communication/ServiceInstanceBuilder.cs
@@ -1,24 +1,10 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+// Reflection should not be used to increase accessibility of classes, methods, or fields
+#pragma warning disable S3011
using System;
using System.Collections.Generic;
@@ -30,18 +16,34 @@
namespace JSSoft.Communication;
-sealed class ServiceInstanceBuilder
+internal sealed class ServiceInstanceBuilder
{
- private const string ns = "JSSoft.Communication.Runtime";
+ private const string Namespace = "JSSoft.Communication.Runtime";
private readonly Dictionary _typeByName = [];
- private readonly AssemblyBuilder _assemblyBuilder;
private readonly ModuleBuilder _moduleBuilder;
internal ServiceInstanceBuilder()
{
- AssemblyName = new AssemblyName(ns);
- _assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.RunAndCollect);
- _moduleBuilder = _assemblyBuilder.DefineDynamicModule(AssemblyName.Name!);
+ var assemblyName = new AssemblyName(Namespace);
+ var access = AssemblyBuilderAccess.RunAndCollect;
+ var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, access);
+ _moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name!);
+ AssemblyName = assemblyName;
+ }
+
+ public AssemblyName AssemblyName { get; }
+
+ public Type CreateType(string name, Type baseType, Type interfaceType)
+ {
+ var fullName = $"{AssemblyName}.{name}";
+ if (_typeByName.TryGetValue(fullName, out var value) != true)
+ {
+ var type = CreateType(_moduleBuilder, name, baseType, interfaceType);
+ _typeByName.Add(fullName, type);
+ value = type;
+ }
+
+ return value;
}
internal static ServiceInstanceBuilder? Create()
@@ -56,22 +58,11 @@ internal ServiceInstanceBuilder()
}
}
- public AssemblyName AssemblyName { get; }
-
- public Type CreateType(string name, Type baseType, Type interfaceType)
- {
- var fullName = $"{AssemblyName}.{name}";
- if (_typeByName.ContainsKey(fullName) != true)
- {
- var type = CreateType(_moduleBuilder, name, baseType, interfaceType);
- _typeByName.Add(fullName, type);
- }
- return _typeByName[fullName];
- }
-
- private static Type CreateType(ModuleBuilder moduleBuilder, string typeName, Type baseType, Type interfaceType)
+ private static Type CreateType(
+ ModuleBuilder moduleBuilder, string typeName, Type baseType, Type interfaceType)
{
- var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, baseType, [interfaceType]);
+ var typeAttrs = TypeAttributes.Class | TypeAttributes.Public;
+ var typeBuilder = moduleBuilder.DefineType(typeName, typeAttrs, baseType, [interfaceType]);
var methodInfos = interfaceType.GetMethods();
foreach (var methodInfo in methodInfos)
{
@@ -88,9 +79,13 @@ private static Type CreateType(ModuleBuilder moduleBuilder, string typeName, Typ
else if (returnType == typeof(void))
{
if (isOneWay == true)
+ {
CreateInvoke(typeBuilder, methodInfo, InstanceBase.InvokeOneWayMethod);
+ }
else
+ {
CreateInvoke(typeBuilder, methodInfo, InstanceBase.InvokeMethod);
+ }
}
else
{
@@ -101,19 +96,26 @@ private static Type CreateType(ModuleBuilder moduleBuilder, string typeName, Typ
return typeBuilder.CreateType();
}
- private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName)
+ private static void CreateInvoke(
+ TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName)
{
var parameterInfos = methodInfo.GetParameters();
var parameterTypes = parameterInfos.Select(i => i.ParameterType).ToArray();
var returnType = methodInfo.ReturnType;
- var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig;
- var methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, methodAttributes, CallingConventions.Standard, returnType, parameterTypes);
+ var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual
+ | MethodAttributes.Final | MethodAttributes.HideBySig;
+ var methodBuilder = typeBuilder.DefineMethod(
+ name: methodInfo.Name,
+ attributes: methodAttributes,
+ callingConvention: CallingConventions.Standard,
+ returnType: returnType,
+ parameterTypes: parameterTypes);
var invokeMethod = FindInvokeMethod(typeBuilder.BaseType!, methodName, returnType);
var typeofMethod = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!;
for (var i = 0; i < parameterInfos.Length; i++)
{
- var pb = methodBuilder.DefineParameter(i, ParameterAttributes.Lcid, parameterInfos[i].Name);
+ methodBuilder.DefineParameter(i, ParameterAttributes.Lcid, parameterInfos[i].Name);
}
var il = methodBuilder.GetILGenerator();
@@ -123,18 +125,19 @@ private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo,
{
il.DeclareLocal(returnType);
}
+
il.Emit(OpCodes.Nop);
il.EmitLdc_I4(parameterInfos.Length);
il.Emit(OpCodes.Newarr, typeof(Type));
for (var i = 0; i < parameterInfos.Length; i++)
{
- var item = parameterInfos[i];
il.Emit(OpCodes.Dup);
il.EmitLdc_I4(i);
il.Emit(OpCodes.Ldtoken, parameterTypes[i]);
il.Emit(OpCodes.Call, typeofMethod);
il.Emit(OpCodes.Stelem_Ref);
}
+
il.Emit(OpCodes.Stloc_0);
il.EmitLdc_I4(parameterInfos.Length);
il.Emit(OpCodes.Newarr, typeof(object));
@@ -148,8 +151,10 @@ private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo,
{
il.Emit(OpCodes.Box, parameterTypes[i]);
}
+
il.Emit(OpCodes.Stelem_Ref);
}
+
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, MethodUtility.GenerateName(methodInfo));
@@ -160,14 +165,20 @@ private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo,
il.Emit(OpCodes.Ret);
}
- private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName)
+ private static void CreateInvokeAsync(
+ TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName)
{
var isCancelable = MethodUtility.IsMethodCancelable(methodInfo);
var parameterInfos = methodInfo.GetParameters();
var parameterTypes = parameterInfos.Select(i => i.ParameterType).ToArray();
var returnType = methodInfo.ReturnType;
- var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig;
- var methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, methodAttributes, returnType, parameterTypes);
+ var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual
+ | MethodAttributes.Final | MethodAttributes.HideBySig;
+ var methodBuilder = typeBuilder.DefineMethod(
+ name: methodInfo.Name,
+ attributes: methodAttributes,
+ returnType: returnType,
+ parameterTypes: parameterTypes);
var invokeMethod = FindInvokeMethod(typeBuilder.BaseType!, methodName, returnType);
var typeofMethod = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!;
@@ -176,7 +187,9 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method
methodBuilder.DefineParameter(i, ParameterAttributes.None, parameterInfos[i].Name);
}
- var parameterLength = isCancelable == true ? parameterInfos.Length - 1 : parameterInfos.Length;
+ var parameterLength = isCancelable == true
+ ? parameterInfos.Length - 1
+ : parameterInfos.Length;
var il = methodBuilder.GetILGenerator();
il.DeclareLocal(typeof(Type[]));
il.DeclareLocal(typeof(object[]));
@@ -186,13 +199,13 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method
il.Emit(OpCodes.Newarr, typeof(Type));
for (var i = 0; i < parameterLength; i++)
{
- var item = parameterInfos[i];
il.Emit(OpCodes.Dup);
il.EmitLdc_I4(i);
il.Emit(OpCodes.Ldtoken, parameterTypes[i]);
il.Emit(OpCodes.Call, typeofMethod);
il.Emit(OpCodes.Stelem_Ref);
}
+
il.Emit(OpCodes.Stloc_0);
il.EmitLdc_I4(parameterLength);
il.Emit(OpCodes.Newarr, typeof(object));
@@ -206,8 +219,10 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method
{
il.Emit(OpCodes.Box, parameterTypes[i]);
}
+
il.Emit(OpCodes.Stelem_Ref);
}
+
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, MethodUtility.GenerateName(methodInfo));
@@ -219,8 +234,11 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method
}
else
{
- il.Emit(OpCodes.Call, typeof(CancellationToken).GetMethod("get_None", BindingFlags.Public | BindingFlags.Static)!);
+ var bindingFlags = BindingFlags.Public | BindingFlags.Static;
+ var meth = typeof(CancellationToken).GetMethod("get_None", bindingFlags)!;
+ il.Emit(OpCodes.Call, meth);
}
+
il.Emit(OpCodes.Call, invokeMethod);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
@@ -231,28 +249,22 @@ private static MethodInfo FindInvokeMethod(Type baseType, string methodName, Typ
var methodInfos = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var item in methodInfos)
{
- if (item.GetCustomAttribute() is InstanceMethodAttribute attr && attr.MethodName == methodName)
+ var attribute = item.GetCustomAttribute();
+ if (attribute is InstanceMethodAttribute instanceMethodAttribute
+ && instanceMethodAttribute.MethodName == methodName)
{
if (item.IsGenericMethod == true)
{
if (returnType.IsGenericType == true)
+ {
return item.MakeGenericMethod(returnType.GetGenericArguments());
+ }
else
+ {
return item.MakeGenericMethod(returnType);
+ }
}
- return item;
- }
- }
-
- throw new NotSupportedException($"'{methodName}' method is not found.");
- }
- private static MethodInfo FindInvokeMethod(MethodInfo[] methodInfos, string methodName)
- {
- foreach (var item in methodInfos)
- {
- if (item.GetCustomAttribute() is InstanceMethodAttribute attr && attr.MethodName == methodName)
- {
return item;
}
}
diff --git a/src/JSSoft.Communication/ServiceState.cs b/src/JSSoft.Communication/ServiceState.cs
index b5bd1bd..37ae8ab 100644
--- a/src/JSSoft.Communication/ServiceState.cs
+++ b/src/JSSoft.Communication/ServiceState.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
namespace JSSoft.Communication;
diff --git a/src/JSSoft.Communication/ServiceToken.cs b/src/JSSoft.Communication/ServiceToken.cs
index ca1b8ce..48927d9 100644
--- a/src/JSSoft.Communication/ServiceToken.cs
+++ b/src/JSSoft.Communication/ServiceToken.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
@@ -26,12 +9,11 @@ namespace JSSoft.Communication;
public sealed class ServiceToken
{
+ internal static readonly ServiceToken Empty = new(Guid.Empty);
+
internal ServiceToken(Guid guid) => Guid = guid;
internal Guid Guid { get; }
- internal static ServiceToken NewToken()
- => new(Guid.NewGuid());
-
- internal static readonly ServiceToken Empty = new(Guid.Empty);
+ internal static ServiceToken NewToken() => new(Guid.NewGuid());
}
diff --git a/src/JSSoft.Communication/ServiceUtility.cs b/src/JSSoft.Communication/ServiceUtility.cs
index da02394..cf4c6ec 100644
--- a/src/JSSoft.Communication/ServiceUtility.cs
+++ b/src/JSSoft.Communication/ServiceUtility.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Reflection;
@@ -27,26 +10,43 @@ namespace JSSoft.Communication;
public static class ServiceUtility
{
- public static Type ValidateServerType(Type ServiceType)
+ public static Type ValidateServerType(Type serviceType)
{
- if (ServiceType.IsInterface != true)
+ if (serviceType.IsInterface != true)
+ {
throw new InvalidOperationException("Server type must be interface.");
+ }
+
+ if (IsNestedPublicType(serviceType) != true
+ && IsPublicType(serviceType) != true
+ && IsInternalType(serviceType) != true)
+ {
+ throw new InvalidOperationException(
+ $"'{serviceType.Name}' must be public or internal.");
+ }
- if (IsNestedPublicType(ServiceType) != true && IsPublicType(ServiceType) != true && IsInternalType(ServiceType) != true)
- throw new InvalidOperationException($"'{ServiceType.Name}' must be public or internal.");
- return ServiceType;
+ return serviceType;
}
- public static Type ValidateClientType(Type CallbackType)
+ public static Type ValidateClientType(Type callbackType)
{
- if (CallbackType != typeof(void))
+ if (callbackType != typeof(void))
{
- if (CallbackType.IsInterface != true)
+ if (callbackType.IsInterface != true)
+ {
throw new InvalidOperationException("Client type must be interface.");
- if (IsNestedPublicType(CallbackType) != true && IsPublicType(CallbackType) != true && IsInternalType(CallbackType) != true)
- throw new InvalidOperationException($"'{CallbackType.Name}' must be public or internal.");
+ }
+
+ if (IsNestedPublicType(callbackType) != true
+ && IsPublicType(callbackType) != true
+ && IsInternalType(callbackType) != true)
+ {
+ throw new InvalidOperationException(
+ $"'{callbackType.Name}' must be public or internal.");
+ }
}
- return CallbackType;
+
+ return callbackType;
}
public static bool IsNestedPublicType(Type type)
@@ -60,10 +60,12 @@ public static bool IsInternalType(Type type)
public static bool IsServer(IService service)
{
- if (service.GetType().GetCustomAttribute(typeof(ServiceAttribute)) is ServiceAttribute serviceAttribute)
+ var attribute = service.GetType().GetCustomAttribute(typeof(ServiceAttribute));
+ if (attribute is ServiceAttribute serviceAttribute)
{
return serviceAttribute.IsServer;
}
+
return false;
}
}
diff --git a/src/JSSoft.Communication/TaskUtility.cs b/src/JSSoft.Communication/TaskUtility.cs
index 50f7561..c024ec1 100644
--- a/src/JSSoft.Communication/TaskUtility.cs
+++ b/src/JSSoft.Communication/TaskUtility.cs
@@ -1,33 +1,17 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System.Threading;
using System.Threading.Tasks;
namespace JSSoft.Communication;
-static class TaskUtility
+internal static class TaskUtility
{
- public static async Task TryDelay(int millisecondsDelay, CancellationToken cancellationToken)
+ public static async Task TryDelay(
+ int millisecondsDelay, CancellationToken cancellationToken)
{
try
{
diff --git a/src/JSSoft.Communication/Threading/Dispatcher.cs b/src/JSSoft.Communication/Threading/Dispatcher.cs
index 379d4cd..ec1cb0b 100644
--- a/src/JSSoft.Communication/Threading/Dispatcher.cs
+++ b/src/JSSoft.Communication/Threading/Dispatcher.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Threading;
@@ -33,22 +16,22 @@ public class Dispatcher : IDisposable
private readonly CancellationToken _cancellationToken;
private readonly DispatcherSynchronizationContext _context;
private readonly DispatcherScheduler _scheduler;
- private bool _isDisposed;
-
#if DEBUG
- private readonly System.Diagnostics.StackTrace? _stackTrace;
+ private readonly System.Diagnostics.StackTrace _stackTrace;
#endif
+
public Dispatcher(object owner)
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
- _scheduler = new DispatcherScheduler(this, _cancellationToken);
- _factory = new TaskFactory(_cancellationToken, TaskCreationOptions.None, TaskContinuationOptions.None, _scheduler);
+ _scheduler = new DispatcherScheduler(_cancellationToken);
+ _factory = new TaskFactory(
+ _cancellationToken, TaskCreationOptions.None, TaskContinuationOptions.None, _scheduler);
_context = new DispatcherSynchronizationContext(_factory);
Owner = owner;
#if DEBUG
_stackTrace = new System.Diagnostics.StackTrace(true);
-#endif
+#endif
Thread = new Thread(_scheduler.Run)
{
Name = $"{owner}: {owner.GetHashCode()}",
@@ -65,6 +48,10 @@ public Dispatcher(object owner)
public SynchronizationContext SynchronizationContext => _context;
+#if DEBUG
+ internal string StackTrace => $"{_stackTrace}";
+#endif
+
public override string ToString() => $"{Owner}";
public void VerifyAccess()
@@ -79,22 +66,35 @@ public void VerifyAccess()
public void Invoke(Action action)
{
- if (_cancellationToken.IsCancellationRequested == true)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this);
if (CheckAccess() == true)
{
action();
return;
}
+
var task = _factory.StartNew(action, _cancellationToken);
task.Wait(_cancellationToken);
}
+ public TResult Invoke(Func func)
+ {
+ ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this);
+
+ if (CheckAccess() == true)
+ {
+ return func();
+ }
+
+ var task = _factory.StartNew(func, _cancellationToken);
+ task.Wait(_cancellationToken);
+ return task.Result;
+ }
+
public void Post(Action action)
{
- if (_cancellationToken.IsCancellationRequested == true)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this);
_factory.StartNew(action, _cancellationToken);
}
@@ -107,69 +107,25 @@ public async Task InvokeAsync(Task task)
public Task InvokeAsync(Action action)
{
- if (_cancellationToken.IsCancellationRequested == true)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this);
return _factory.StartNew(action, _cancellationToken);
}
- public TResult Invoke(Func func)
- {
- if (_cancellationTokenSource.IsCancellationRequested == true)
- throw new ObjectDisposedException($"{this}");
-
- if (CheckAccess() == true)
- {
- return func();
- }
- var task = _factory.StartNew(func, _cancellationToken);
- task.Wait(_cancellationToken);
- return task.Result;
- }
-
public async Task InvokeAsync(Func callback)
{
- if (_cancellationTokenSource.IsCancellationRequested == true)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this);
return await _factory.StartNew(callback, _cancellationToken);
}
public void Dispose()
{
- if (_isDisposed == true)
- throw new ObjectDisposedException($"{this}");
- if (_cancellationTokenSource.IsCancellationRequested == true)
- throw new ObjectDisposedException($"{this}");
+ ObjectDisposedException.ThrowIf(_cancellationTokenSource.IsCancellationRequested, this);
_cancellationTokenSource.Cancel();
_scheduler.WaitClose();
_cancellationTokenSource.Dispose();
- _isDisposed = true;
GC.SuppressFinalize(this);
}
-
-#if DEBUG
- internal string StackTrace => $"{_stackTrace}";
-#endif
-}
-
-public sealed class DispatcherSynchronizationContext : SynchronizationContext
-{
- private readonly TaskFactory _factory;
-
- internal DispatcherSynchronizationContext(TaskFactory factory)
- {
- _factory = factory;
- }
-
- public override void Send(SendOrPostCallback d, object? state)
- {
- _factory.StartNew(() => d(state)).Wait();
- }
-
- public override void Post(SendOrPostCallback d, object? state)
- {
- _factory.StartNew(() => d(state));
- }
}
diff --git a/src/JSSoft.Communication/Threading/DispatcherScheduler.cs b/src/JSSoft.Communication/Threading/DispatcherScheduler.cs
index d74c7d2..b183224 100644
--- a/src/JSSoft.Communication/Threading/DispatcherScheduler.cs
+++ b/src/JSSoft.Communication/Threading/DispatcherScheduler.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
using System;
using System.Collections.Concurrent;
@@ -30,39 +13,23 @@ namespace JSSoft.Communication.Threading;
public sealed class DispatcherScheduler : TaskScheduler
{
- private readonly Dispatcher _dispatcher;
private readonly CancellationToken _cancellationToken;
private readonly ConcurrentQueue _taskQueue = [];
private readonly ManualResetEvent _executionEventSet = new(false);
private bool _isRunning = true;
private bool _isClosed;
- internal DispatcherScheduler(Dispatcher dispatcher, CancellationToken cancellationToken)
+ internal DispatcherScheduler(CancellationToken cancellationToken)
{
- _dispatcher = dispatcher;
_cancellationToken = cancellationToken;
}
- protected override IEnumerable GetScheduledTasks() => _taskQueue;
-
- protected override void QueueTask(Task task)
- {
- if (_cancellationToken.IsCancellationRequested != true)
- {
- _taskQueue.Enqueue(task);
- _executionEventSet.Set();
- }
- }
-
- protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
- {
- return false;
- }
-
internal void WaitClose()
{
if (_isClosed == true)
+ {
throw new InvalidOperationException("Dispatcher is already closed.");
+ }
while (_isRunning == true)
{
@@ -74,8 +41,10 @@ internal void WaitClose()
{
_executionEventSet.Close();
}
+
Thread.Sleep(1);
}
+
_executionEventSet.Dispose();
_isClosed = true;
}
@@ -84,11 +53,6 @@ internal void Run()
{
try
{
-#if DEBUG
- var owner = _dispatcher.Owner;
- var stackTrace = _dispatcher.StackTrace;
-#endif
-
while (_cancellationToken.IsCancellationRequested != true)
{
if (_taskQueue.TryDequeue(out var task) == true)
@@ -108,12 +72,31 @@ internal void Run()
}
}
}
+
if (_isClosed == true)
+ {
throw new InvalidOperationException("Dispatcher is already closed.");
+ }
}
finally
{
_isRunning = false;
}
}
+
+ protected override IEnumerable GetScheduledTasks() => _taskQueue;
+
+ protected override void QueueTask(Task task)
+ {
+ if (_cancellationToken.IsCancellationRequested != true)
+ {
+ _taskQueue.Enqueue(task);
+ _executionEventSet.Set();
+ }
+ }
+
+ protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
+ {
+ return false;
+ }
}
diff --git a/src/JSSoft.Communication/Threading/DispatcherSynchronizationContext.cs b/src/JSSoft.Communication/Threading/DispatcherSynchronizationContext.cs
new file mode 100644
index 0000000..92493c2
--- /dev/null
+++ b/src/JSSoft.Communication/Threading/DispatcherSynchronizationContext.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace JSSoft.Communication.Threading;
+
+public sealed class DispatcherSynchronizationContext : SynchronizationContext
+{
+ private readonly TaskFactory _factory;
+
+ internal DispatcherSynchronizationContext(TaskFactory factory)
+ {
+ _factory = factory;
+ }
+
+ public override void Send(SendOrPostCallback d, object? state)
+ {
+ _factory.StartNew(() => d(state)).Wait();
+ }
+
+ public override void Post(SendOrPostCallback d, object? state)
+ {
+ _factory.StartNew(() => d(state));
+ }
+}
diff --git a/src/JSSoft.Communication/UnreachableException.cs b/src/JSSoft.Communication/UnreachableException.cs
index 0218444..5cb2e30 100644
--- a/src/JSSoft.Communication/UnreachableException.cs
+++ b/src/JSSoft.Communication/UnreachableException.cs
@@ -1,24 +1,7 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
#if !NET7_0_OR_GREATER
using System;
diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs
index 3e8f967..287764c 100644
--- a/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs
+++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs
@@ -1,47 +1,35 @@
-// MIT License
-//
-// Copyright (c) 2024 Jeesu Choi
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
-using JSSoft.Communication.Services;
using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Collections;
+using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
-using System.Collections;
+using System.IO;
using System.Linq;
-using System.Collections.Generic;
-using JSSoft.Terminals;
using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using JSSoft.Communication.Services;
+using JSSoft.Terminals;
namespace JSSoft.Communication.ConsoleApp;
-sealed class Application : IApplication, IServiceProvider
+internal sealed class Application : IApplication, IServiceProvider
{
- private static readonly string postfix = TerminalEnvironment.IsWindows() == true ? ">" : "$";
+ private static readonly string Postfix = TerminalEnvironment.IsWindows() == true ? ">" : "$";
private readonly ApplicationOptions _option;
private readonly IServiceContext _serviceContext;
- private readonly INotifyUserService _userServiceNotification;
private readonly CompositionContainer _container;
+#if SERVER
+ private readonly bool _isServer = true;
+#else
+ private readonly bool _isServer = false;
+#endif
+
private bool _isDisposed;
private CancellationTokenSource? _cancellationTokenSource;
private string title = string.Empty;
@@ -51,12 +39,6 @@ static Application()
Logging.LogUtility.Logger = Logging.TraceLogger.Default;
}
-#if SERVER
- private readonly bool _isServer = true;
-#else
- private readonly bool _isServer = false;
-#endif
-
public Application(ApplicationOptions option)
{
_container = new CompositionContainer(new AssemblyCatalog(typeof(Application).Assembly));
@@ -68,11 +50,14 @@ public Application(ApplicationOptions option)
_serviceContext = _container.GetExportedValue();
_serviceContext.Opened += ServiceContext_Opened;
_serviceContext.Closed += ServiceContext_Closed;
- _userServiceNotification = _container.GetExportedValue();
- _userServiceNotification.LoggedIn += UserServiceNotification_LoggedIn;
- _userServiceNotification.LoggedOut += UserServiceNotification_LoggedOut;
- _userServiceNotification.MessageReceived += UserServiceNotification_MessageReceived;
Title = "Server";
+ if (_container.GetExportedValue() is { } userServiceNotification)
+ {
+ userServiceNotification = _container.GetExportedValue();
+ userServiceNotification.LoggedIn += UserServiceNotification_LoggedIn;
+ userServiceNotification.LoggedOut += UserServiceNotification_LoggedOut;
+ userServiceNotification.MessageReceived += UserServiceNotification_MessageReceived;
+ }
}
public bool IsOpened { get; private set; }
@@ -87,6 +72,16 @@ public string Title
}
}
+ internal Guid Token { get; set; }
+
+ internal Guid UserToken { get; private set; }
+
+ internal string UserId { get; private set; } = string.Empty;
+
+ private TextWriter Out => Console.Out;
+
+ private SystemTerminal Terminal => _container.GetExportedValue();
+
public void Dispose()
{
if (_isDisposed != true)
@@ -96,44 +91,115 @@ public void Dispose()
}
}
- internal void Login(string userID, Guid token)
+ public async Task StartAsync()
{
- UserID = userID;
- UserToken = token;
+ if (_cancellationTokenSource != null)
+ {
+ throw new InvalidOperationException("Application is already started.");
+ }
+
+ _cancellationTokenSource = new CancellationTokenSource();
+ _serviceContext.EndPoint = new DnsEndPoint(_option.Host, _option.Port);
+ try
+ {
+ Token = await _serviceContext.OpenAsync(_cancellationTokenSource.Token);
+ }
+ catch
+ {
+ await _serviceContext.AbortAsync();
+ }
+
UpdatePrompt();
- Out.WriteLine("사용자 관련 명령을 수행하려면 'help user' 을(를) 입력하세요.");
+ await Terminal.StartAsync(_cancellationTokenSource.Token);
}
- internal void Logout()
+ public async Task StopAsync(int exitCode)
{
- UserID = string.Empty;
- UserToken = Guid.Empty;
- UpdatePrompt();
+ if (_cancellationTokenSource == null)
+ {
+ throw new InvalidOperationException("Application is not started.");
+ }
+
+ await _cancellationTokenSource.CancelAsync();
+ if (_serviceContext.ServiceState == ServiceState.Open)
+ {
+ _serviceContext.Closed -= ServiceContext_Closed;
+ try
+ {
+ await _serviceContext.CloseAsync(Token, cancellationToken: default);
+ }
+ catch
+ {
+ await _serviceContext.AbortAsync();
+ }
+ }
+
+ _cancellationTokenSource.Dispose();
+ _cancellationTokenSource = null;
}
- internal Guid Token { get; set; }
+ object? IServiceProvider.GetService(Type serviceType)
+ {
+ if (serviceType == typeof(IServiceProvider))
+ {
+ return this;
+ }
- internal Guid UserToken { get; private set; }
+ if (typeof(IEnumerable).IsAssignableFrom(serviceType)
+ && serviceType.GenericTypeArguments.Length == 1)
+ {
+ var itemType = serviceType.GenericTypeArguments.First();
+ var contractName = AttributedModelServices.GetContractName(itemType);
+ var items = _container.GetExportedValues