├── src ├── tcp.md ├── .editorconfig ├── IpcServiceFramework.snk ├── JKang.IpcServiceFramework.Core.Tests │ ├── Fixtures │ │ ├── EnumType.cs │ │ ├── IComplexType.cs │ │ └── ComplexType.cs │ ├── JKang.IpcServiceFramework.Core.Tests.csproj │ └── DefaultValueConverterTest.cs ├── JKang.IpcServiceFramework.NamedPipeTests │ ├── Fixtures │ │ ├── ITestDto.cs │ │ ├── ITestService2.cs │ │ ├── TestDto.cs │ │ ├── UnserializableObject.cs │ │ ├── ITestService.cs │ │ └── XorStream.cs │ ├── JKang.IpcServiceFramework.NamedPipeTests.csproj │ ├── StreamTranslatorTest.cs │ ├── MultipleEndpointTest.cs │ ├── EdgeCaseTest.cs │ ├── SimpleTypeNameContractTest.cs │ ├── ErrorTest.cs │ └── ContractTest.cs ├── JKang.IpcServiceFramework.TcpTests │ ├── Fixtures │ │ └── ITestService.cs │ ├── JKang.IpcServiceFramework.TcpTests.csproj │ ├── HappyPathTest.cs │ └── EdgeCaseTest.cs ├── JKang.IpcServiceFramework.Client.NamedPipe │ ├── NamedPipeIpcClientOptions.cs │ ├── JKang.IpcServiceFramework.Client.NamedPipe.csproj │ ├── NamedPipeIpcClient.cs │ └── NamedPipeIpcClientServiceCollectionExtensions.cs ├── JKang.IpcServiceFramework.Core │ ├── IpcStatus.cs │ ├── Services │ │ ├── IValueConverter.cs │ │ ├── IIpcMessageSerializer.cs │ │ ├── DefaultIpcMessageSerializer.cs │ │ └── DefaultValueConverter.cs │ ├── IpcException.cs │ ├── GlobalSuppressions.cs │ ├── IpcCommunicationException.cs │ ├── IpcSerializationException.cs │ ├── JKang.IpcServiceFramework.Core.csproj │ ├── IpcFaultException.cs │ ├── IpcRequest.cs │ ├── IpcResponse.cs │ └── IO │ │ ├── IpcWriter.cs │ │ └── IpcReader.cs ├── JKang.IpcServiceFramework.Client │ ├── IIpcClientFactory.cs │ ├── JKang.IpcServiceFramework.Client.csproj │ ├── IpcClientServiceCollectionExtensions.cs │ ├── IIpcClient.cs │ ├── IpcStreamWrapper.cs │ ├── IpcClientOptions.cs │ ├── IpcClientRegistration.cs │ ├── IpcClientFactory.cs │ └── IpcClient.cs ├── JKang.IpcServiceFramework.Hosting.NamedPipe │ ├── NamedPipeIpcEndpointOptions.cs │ ├── GlobalSuppressions.cs │ ├── JKang.IpcServiceFramework.Hosting.NamedPipe.csproj │ ├── NamedPipeIpcHostBuilderExtensions.cs │ ├── NamedPipeIpcEndpoint.cs │ └── NamedPipeNative.cs ├── JKang.IpcServiceFramework.Hosting │ ├── IIpcHostBuilder.cs │ ├── IIpcEndpoint.cs │ ├── GlobalSuppressions.cs │ ├── GenericHostBuilderExtensions.cs │ ├── IpcHostingConfigurationException.cs │ ├── JKang.IpcServiceFramework.Hosting.csproj │ ├── IpcEndpointOptions.cs │ ├── IpcHostBuilder.cs │ ├── IpcBackgroundService.cs │ └── IpcEndpoint.cs ├── JKang.IpcServiceFramework.Client.Tcp │ ├── JKang.IpcServiceFramework.Client.Tcp.csproj │ ├── TcpIpcClientOptions.cs │ ├── TcpIpcClientServiceCollectionExtensions.cs │ └── TcpIpcClient.cs ├── JKang.IpcServiceFramework.Hosting.Tcp │ ├── JKang.IpcServiceFramework.Hosting.Tcp.csproj │ ├── GlobalSuppressions.cs │ ├── TcpIpcEndpointOptions.cs │ ├── TcpIpcHostBuilderExtensions.cs │ └── TcpIpcEndpoint.cs ├── JKang.IpcServiceFramework.Testing │ ├── JKang.IpcServiceFramework.Testing.csproj │ ├── IpcApplicationFactory.cs │ └── TestHelpers.cs ├── Directory.Build.props └── IpcServiceFramework.sln ├── global.json ├── samples ├── IpcServiceSample.ServiceContracts │ ├── IInterProcessService.cs │ └── IpcServiceSample.ServiceContracts.csproj ├── IpcServiceSample.Server │ ├── InterProcessService.cs │ ├── IpcServiceSample.Server.csproj │ └── Program.cs ├── IpcServiceSample.Client │ ├── IpcServiceSample.ConsoleClient.csproj │ └── Program.cs └── IpcServiceSample.sln ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── FUNDING.yml └── copilot-instructions.md ├── doc ├── stream-translator.md └── tcp │ └── security.md ├── LICENSE ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md └── .gitignore /src/tcp.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | indent_style = space 3 | indent_size = 4 4 | insert_final_newline = true -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "10.0.101", 4 | "rollForward": "latestFeature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/IpcServiceFramework.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacqueskang/IpcServiceFramework/HEAD/src/IpcServiceFramework.snk -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core.Tests/Fixtures/EnumType.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.Core.Tests.Fixtures 2 | { 3 | public enum EnumType 4 | { 5 | FirstOption, 6 | SecondOption 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/Fixtures/ITestDto.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.NamedPipeTests.Fixtures 2 | { 3 | public interface ITestDto 4 | { 5 | string Value { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/Fixtures/ITestService2.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.NamedPipeTests.Fixtures 2 | { 3 | public interface ITestService2 4 | { 5 | int SomeMethod(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.TcpTests/Fixtures/ITestService.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.TcpTests.Fixtures 2 | { 3 | public interface ITestService 4 | { 5 | string StringType(string input); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.ServiceContracts/IInterProcessService.cs: -------------------------------------------------------------------------------- 1 | namespace IpcServiceSample.ServiceContracts 2 | { 3 | public interface IInterProcessService 4 | { 5 | string ReverseString(string input); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.ServiceContracts/IpcServiceSample.ServiceContracts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/Fixtures/TestDto.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.NamedPipeTests.Fixtures 2 | { 3 | public class TestDto : ITestDto 4 | { 5 | public string Value { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core.Tests/Fixtures/IComplexType.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.Core.Tests.Fixtures 2 | { 3 | public interface IComplexType 4 | { 5 | int Int32Value { get; } 6 | string StringValue { get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.NamedPipe/NamedPipeIpcClientOptions.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.Client.NamedPipe 2 | { 3 | public class NamedPipeIpcClientOptions : IpcClientOptions 4 | { 5 | public string PipeName { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IpcStatus.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework 2 | { 3 | public enum IpcStatus: int 4 | { 5 | Unknown = 0, 6 | Ok = 200, 7 | BadRequest = 400, 8 | InternalServerError = 500, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IIpcClientFactory.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.Client 2 | { 3 | public interface IIpcClientFactory 4 | where TContract: class 5 | { 6 | IIpcClient CreateClient(string name); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.NamedPipe/NamedPipeIpcEndpointOptions.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.Hosting.NamedPipe 2 | { 3 | public class NamedPipeIpcEndpointOptions : IpcEndpointOptions 4 | { 5 | public string PipeName { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/Services/IValueConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework.Services 4 | { 5 | public interface IValueConverter 6 | { 7 | bool TryConvert(object origValue, Type destType, out object destValue); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/IIpcHostBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework.Hosting 4 | { 5 | public interface IIpcHostBuilder 6 | { 7 | IIpcHostBuilder AddIpcEndpoint(Func factory); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core.Tests/Fixtures/ComplexType.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.Core.Tests.Fixtures 2 | { 3 | public class ComplexType : IComplexType 4 | { 5 | public int Int32Value { get; set; } 6 | public string StringValue { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/IIpcEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace JKang.IpcServiceFramework.Hosting 6 | { 7 | public interface IIpcEndpoint: IDisposable 8 | { 9 | Task ExecuteAsync(CancellationToken stoppingToken); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.Server/InterProcessService.cs: -------------------------------------------------------------------------------- 1 | using IpcServiceSample.ServiceContracts; 2 | using System; 3 | 4 | namespace IpcServiceSample.Server 5 | { 6 | public class InterProcessService : IInterProcessService 7 | { 8 | public string ReverseString(string input) 9 | { 10 | char[] charArray = input.ToCharArray(); 11 | Array.Reverse(charArray); 12 | return new string(charArray); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.Tcp/JKang.IpcServiceFramework.Client.Tcp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | ipc,interprocess,communication,wcf,client,tcp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/Fixtures/UnserializableObject.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace JKang.IpcServiceFramework.NamedPipeTests.Fixtures 4 | { 5 | public class UnserializableObject : IPAddress 6 | { 7 | public static UnserializableObject Create() 8 | => new UnserializableObject(Loopback.GetAddressBytes()); 9 | 10 | private UnserializableObject(byte[] address) 11 | : base(address) 12 | { } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.Tcp/JKang.IpcServiceFramework.Hosting.Tcp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | ipc,interprocess,communication,wcf,hosting,tcp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IpcException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework 4 | { 5 | public abstract class IpcException : Exception 6 | { 7 | protected IpcException() 8 | { } 9 | 10 | protected IpcException(string message) 11 | : base(message) 12 | { } 13 | 14 | protected IpcException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.NamedPipe/JKang.IpcServiceFramework.Client.NamedPipe.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | ipc,interprocess,communication,wcf,client,namedpipe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Globalization", 9 | "CA1303:Do not pass literals as localized parameters", 10 | Justification = "")] 11 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Globalization support is not planned.")] 9 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.Tcp/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Globalization support is not planned.")] 9 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.Tcp/TcpIpcEndpointOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Security.Cryptography.X509Certificates; 3 | 4 | namespace JKang.IpcServiceFramework.Hosting.Tcp 5 | { 6 | public class TcpIpcEndpointOptions : IpcEndpointOptions 7 | { 8 | public IPAddress IpEndpoint { get; set; } = IPAddress.Loopback; 9 | public int Port { get; set; } 10 | public bool EnableSsl { get; set; } 11 | public X509Certificate SslCertificate { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.NamedPipe/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Globalization support is not planned.")] 9 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IpcCommunicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework 4 | { 5 | public class IpcCommunicationException : IpcException 6 | { 7 | public IpcCommunicationException() 8 | { } 9 | 10 | public IpcCommunicationException(string message) 11 | : base(message) 12 | { } 13 | 14 | public IpcCommunicationException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IpcSerializationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework 4 | { 5 | public class IpcSerializationException : IpcException 6 | { 7 | public IpcSerializationException() 8 | { } 9 | 10 | public IpcSerializationException(string message) 11 | : base(message) 12 | { } 13 | 14 | public IpcSerializationException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/GenericHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Hosting; 2 | using System; 3 | 4 | namespace Microsoft.Extensions.Hosting 5 | { 6 | public static class GenericHostBuilderExtensions 7 | { 8 | public static IHostBuilder ConfigureIpcHost(this IHostBuilder builder, Action configure) 9 | { 10 | var ipcHostBuilder = new IpcHostBuilder(builder); 11 | configure?.Invoke(ipcHostBuilder); 12 | return builder; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.Tcp/TcpIpcClientOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Security; 3 | 4 | namespace JKang.IpcServiceFramework.Client.Tcp 5 | { 6 | public class TcpIpcClientOptions : IpcClientOptions 7 | { 8 | public IPAddress ServerIp { get; set; } = IPAddress.Loopback; 9 | public int ServerPort { get; set; } = 11843; 10 | public bool EnableSsl { get; set; } 11 | public string SslServerIdentity { get; set; } 12 | public RemoteCertificateValidationCallback SslValidationCallback { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/JKang.IpcServiceFramework.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | JKang.IpcServiceFramework 6 | ipc,interprocess,communication,wcf 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/IpcHostingConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework.Hosting 4 | { 5 | public class IpcHostingConfigurationException : Exception 6 | { 7 | public IpcHostingConfigurationException() 8 | { } 9 | 10 | public IpcHostingConfigurationException(string message) 11 | : base(message) 12 | { } 13 | 14 | public IpcHostingConfigurationException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/JKang.IpcServiceFramework.Hosting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | ipc,interprocess,communication,wcf,hosting 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.Server/IpcServiceSample.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/JKang.IpcServiceFramework.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | ipc,interprocess,communication,wcf,client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.Client/IpcServiceSample.ConsoleClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/IpcEndpointOptions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Services; 2 | using System; 3 | using System.IO; 4 | 5 | namespace JKang.IpcServiceFramework.Hosting 6 | { 7 | public class IpcEndpointOptions 8 | { 9 | public int MaxConcurrentCalls { get; set; } = 4; 10 | 11 | public bool IncludeFailureDetailsInResponse { get; set; } 12 | 13 | public Func StreamTranslator { get; set; } 14 | 15 | public IIpcMessageSerializer Serializer { get; set; } = new DefaultIpcMessageSerializer(); 16 | 17 | public IValueConverter ValueConverter { get; set; } = new DefaultValueConverter(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jacqueskang 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/Services/IIpcMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace JKang.IpcServiceFramework.Services 2 | { 3 | public interface IIpcMessageSerializer 4 | { 5 | /// 6 | byte[] SerializeRequest(IpcRequest request); 7 | 8 | /// 9 | IpcResponse DeserializeResponse(byte[] binary); 10 | 11 | /// 12 | IpcRequest DeserializeRequest(byte[] binary); 13 | 14 | /// 15 | byte[] SerializeResponse(IpcResponse response); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.NamedPipe/JKang.IpcServiceFramework.Hosting.NamedPipe.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | ipc,interprocess,communication,wcf,hosting,namedpipe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Testing/JKang.IpcServiceFramework.Testing.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /doc/stream-translator.md: -------------------------------------------------------------------------------- 1 | ## Stream translators 2 | 3 | If you want to process the binary data after serialisation or before deserialisation, for example to add a custom handshake when the connection begins, you can do so using a stream translator. Host and client classes allow you to pass a `Func` stream translation callback in their constructors, which can be used to "wrap" a custom stream around the network stream. This is supported on TCP communications both with and without SSL enabled. See the `XorStream` class in the IpcServiceSample.ServiceContracts project for an example of a stream translator. 4 | 5 | Stream translators are also useful for logging packets for debugging. See the `LoggingStream` class in the IpcServiceSample.ServiceContracts project for an example of using a stream translator to log traffic. 6 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jacques Kang and other GitHub contributors 5 | 6 | https://github.com/jacqueskang/IpcServiceFramework/blob/develop/LICENSE 7 | https://github.com/jacqueskang/IpcServiceFramework 8 | https://github.com/jacqueskang/IpcServiceFramework 9 | 3.0.0 10 | true 11 | ..\IpcServiceFramework.snk 12 | 13 | 14 | 15 | DISABLE_DYNAMIC_CODE_GENERATION 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IpcFaultException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework 4 | { 5 | /// 6 | /// An exception that can be transfered from server to client 7 | /// 8 | public class IpcFaultException : IpcException 9 | { 10 | public IpcFaultException(IpcStatus status) 11 | { 12 | Status = status; 13 | } 14 | 15 | public IpcFaultException(IpcStatus status, string message) 16 | : base(message) 17 | { 18 | Status = status; 19 | } 20 | 21 | public IpcFaultException(IpcStatus status, string message, Exception innerException) 22 | : base(message, innerException) 23 | { 24 | Status = status; 25 | } 26 | 27 | public IpcStatus Status { get; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IpcClientServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Client; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection 5 | { 6 | public static class IpcClientServiceCollectionExtensions 7 | { 8 | public static IServiceCollection AddIpcClient( 9 | this IServiceCollection services, 10 | IpcClientRegistration registration) 11 | where TContract : class 12 | where TIpcClientOptions : IpcClientOptions 13 | { 14 | services 15 | .TryAddScoped, IpcClientFactory>(); 16 | 17 | services.AddSingleton(registration); 18 | 19 | return services; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.NamedPipe/NamedPipeIpcClient.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Pipes; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace JKang.IpcServiceFramework.Client.NamedPipe 7 | { 8 | internal class NamedPipeIpcClient : IpcClient 9 | where TInterface : class 10 | { 11 | private readonly NamedPipeIpcClientOptions _options; 12 | 13 | public NamedPipeIpcClient( 14 | string name, 15 | NamedPipeIpcClientOptions options) 16 | : base(name, options) 17 | { 18 | _options = options; 19 | } 20 | 21 | protected override async Task ConnectToServerAsync(CancellationToken cancellationToken) 22 | { 23 | var stream = new NamedPipeClientStream(".", _options.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous); 24 | await stream.ConnectAsync(_options.ConnectionTimeout, cancellationToken).ConfigureAwait(false); 25 | return new IpcStreamWrapper(stream); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jacques Kang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/Fixtures/ITestService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Net; 5 | using System.Numerics; 6 | using System.Threading.Tasks; 7 | 8 | namespace JKang.IpcServiceFramework.NamedPipeTests.Fixtures 9 | { 10 | public interface ITestService 11 | { 12 | int PrimitiveTypes(bool a, byte b, sbyte c, char d, decimal e, double f, float g, int h, uint i, long j, 13 | ulong k, short l, ushort m); 14 | string StringType(string input); 15 | Complex ComplexType(Complex input); 16 | IEnumerable ComplexTypeArray(IEnumerable input); 17 | void ReturnVoid(); 18 | DateTime DateTime(DateTime input); 19 | DateTimeStyles EnumType(DateTimeStyles input); 20 | byte[] ByteArray(byte[] input); 21 | T GenericMethod(T input); 22 | Task AsyncMethod(); 23 | void ThrowException(); 24 | ITestDto Abstraction(ITestDto input); 25 | void UnserializableInput(UnserializableObject input); 26 | UnserializableObject UnserializableOutput(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using IpcServiceSample.Server; 2 | using IpcServiceSample.ServiceContracts; 3 | using JKang.IpcServiceFramework.Hosting; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace IpcServiceSample.ConsoleServer 9 | { 10 | class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | CreateHostBuilder(args).Build().Run(); 15 | } 16 | 17 | public static IHostBuilder CreateHostBuilder(string[] args) => 18 | Host.CreateDefaultBuilder(args) 19 | .ConfigureServices(services => 20 | { 21 | services.AddScoped(); 22 | }) 23 | .ConfigureIpcHost(builder => 24 | { 25 | builder.AddNamedPipeEndpoint("pipeinternal"); 26 | }) 27 | .ConfigureLogging(builder => 28 | { 29 | builder.SetMinimumLevel(LogLevel.Debug); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core.Tests/JKang.IpcServiceFramework.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/IpcHostBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using System; 4 | 5 | namespace JKang.IpcServiceFramework.Hosting 6 | { 7 | internal class IpcHostBuilder : IIpcHostBuilder 8 | { 9 | private readonly IHostBuilder _hostBuilder; 10 | 11 | public IpcHostBuilder(IHostBuilder hostBuilder) 12 | { 13 | _hostBuilder = hostBuilder ?? throw new ArgumentNullException(nameof(hostBuilder)); 14 | 15 | _hostBuilder.ConfigureServices((_, services) => 16 | { 17 | services 18 | .AddHostedService(); 19 | }); 20 | } 21 | 22 | public IIpcHostBuilder AddIpcEndpoint(Func endpointFactory) 23 | { 24 | if (endpointFactory is null) 25 | { 26 | throw new ArgumentNullException(nameof(endpointFactory)); 27 | } 28 | 29 | _hostBuilder.ConfigureServices((_, services) => 30 | { 31 | services.AddSingleton(endpointFactory); 32 | }); 33 | 34 | return this; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/JKang.IpcServiceFramework.NamedPipeTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.NamedPipe/NamedPipeIpcClientServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Client; 2 | using JKang.IpcServiceFramework.Client.NamedPipe; 3 | using System; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection 6 | { 7 | public static class NamedPipeIpcClientServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddNamedPipeIpcClient( 10 | this IServiceCollection services, string name, string pipeName) 11 | where TContract : class 12 | { 13 | return services.AddNamedPipeIpcClient(name, (_, options) => 14 | { 15 | options.PipeName = pipeName; 16 | }); 17 | } 18 | 19 | public static IServiceCollection AddNamedPipeIpcClient( 20 | this IServiceCollection services, string name, 21 | Action configureOptions) 22 | where TContract : class 23 | { 24 | services.AddIpcClient(new IpcClientRegistration(name, 25 | (_, options) => new NamedPipeIpcClient(name, options), configureOptions)); 26 | 27 | return services; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.Tcp/TcpIpcClientServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Client; 2 | using JKang.IpcServiceFramework.Client.Tcp; 3 | using System; 4 | using System.Net; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static class TcpIpcClientServiceCollectionExtensions 9 | { 10 | public static IServiceCollection AddTcpIpcClient( 11 | this IServiceCollection services, string name, IPAddress serverIp, int serverPort) 12 | where TContract : class 13 | { 14 | return services.AddTcpIpcClient(name, (_, options) => 15 | { 16 | options.ServerIp = serverIp; 17 | options.ServerPort = serverPort; 18 | }); 19 | } 20 | 21 | public static IServiceCollection AddTcpIpcClient( 22 | this IServiceCollection services, string name, 23 | Action configureOptions) 24 | where TContract : class 25 | { 26 | services.AddIpcClient(new IpcClientRegistration(name, 27 | (_, options) => new TcpIpcClient(name, options), configureOptions)); 28 | 29 | return services; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IIpcClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace JKang.IpcServiceFramework.Client 7 | { 8 | public interface IIpcClient 9 | where TInterface : class 10 | { 11 | string Name { get; } 12 | 13 | #if !DISABLE_DYNAMIC_CODE_GENERATION 14 | Task InvokeAsync( 15 | Expression> exp, 16 | CancellationToken cancellationToken = default); 17 | 18 | Task InvokeAsync( 19 | Expression> exp, 20 | CancellationToken cancellationToken = default); 21 | 22 | Task InvokeAsync( 23 | Expression> exp, 24 | CancellationToken cancellationToken = default); 25 | 26 | Task InvokeAsync( 27 | Expression>> exp, 28 | CancellationToken cancellationToken = default); 29 | #endif 30 | 31 | Task InvokeAsync(IpcRequest request, 32 | CancellationToken cancellationToken = default(CancellationToken)); 33 | 34 | Task InvokeAsync(IpcRequest request, 35 | CancellationToken cancellationToken = default(CancellationToken)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.TcpTests/JKang.IpcServiceFramework.TcpTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | #Contributing 2 | 3 | ## Git workflow 4 | 5 | Follow [Gitflow](https://datasift.github.io/gitflow/IntroducingGitFlow.html). 6 | 7 | *Notes:* 8 | - Use **Rebase and merge** to complete PR merging to develop branch too have a clean and linear history. 9 | - Use **Create a merge commit** to complete PR merging to master branch so that "first-parent" commits matches the versioning history. 10 | 11 | ## Commit syntax 12 | 13 | Follow [Conventional Commits 1.0](https://www.conventionalcommits.org/en/v1.0.0/) 14 | 15 | ## Versioning 16 | 17 | Follow [Semantic Versioning 2.0](https://semver.org/). 18 | 19 | Currently all JKang.IpcServiceFramework.* packages share a same version fixed in [version.yml](/build/version.yml). You should thus update this file when starting working on a new milestone. 20 | 21 | ## CI/CD 22 | 23 | - A PR build is triggered when any PR is created, which checks the changes included by executing all tests. 24 | - A CI build is triggered when any change is commited in `develop` branch, which generates CI packages (e.g., *.3.0.0-ci-20200612.1.nupkg) 25 | - A preview release build is triggered when any change is commited in `master` branch, which generates and publishes preview packages (e.g., *.3.0.0-preview-20200612.1.nupkg) to nuget.org 26 | - To publish a stable release repository owner manually trigger a stable release build in Azure DevOps which generates stable packages and publishes to nuget.org (e.g., *.3.0.0.nupkg) 27 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/IpcBackgroundService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace JKang.IpcServiceFramework.Hosting 9 | { 10 | public sealed class IpcBackgroundService : BackgroundService 11 | { 12 | private readonly IEnumerable _endpoints; 13 | private readonly ILogger _logger; 14 | 15 | public IpcBackgroundService( 16 | IEnumerable endpoints, 17 | ILogger logger) 18 | { 19 | _endpoints = endpoints ?? throw new System.ArgumentNullException(nameof(endpoints)); 20 | _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); 21 | } 22 | 23 | protected override Task ExecuteAsync(CancellationToken stoppingToken) 24 | { 25 | return Task.WhenAll(_endpoints.Select(x => x.ExecuteAsync(stoppingToken))); 26 | } 27 | 28 | public override void Dispose() 29 | { 30 | foreach (IIpcEndpoint endpoint in _endpoints) 31 | { 32 | endpoint.Dispose(); 33 | } 34 | 35 | base.Dispose(); 36 | _logger.LogInformation("IPC background service disposed."); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using IpcServiceSample.ServiceContracts; 2 | using JKang.IpcServiceFramework.Client; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace IpcServiceSample.ConsoleClient 8 | { 9 | class Program 10 | { 11 | private static async Task Main(string[] args) 12 | { 13 | while (true) 14 | { 15 | Console.WriteLine("Type a phrase and press enter or press Ctrl+C to exit:"); 16 | string input = Console.ReadLine(); 17 | 18 | // register IPC clients 19 | ServiceProvider serviceProvider = new ServiceCollection() 20 | .AddNamedPipeIpcClient("client1", pipeName: "pipeinternal") 21 | .BuildServiceProvider(); 22 | 23 | // resolve IPC client factory 24 | IIpcClientFactory clientFactory = serviceProvider 25 | .GetRequiredService>(); 26 | 27 | // create client 28 | IIpcClient client = clientFactory.CreateClient("client1"); 29 | 30 | string output = await client.InvokeAsync(x => x.ReverseString(input)); 31 | 32 | Console.WriteLine($"Result from server: '{output}'.\n"); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IpcStreamWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace JKang.IpcServiceFramework.Client 8 | { 9 | /// 10 | /// Tcp clients depend on both a TcpClient object and a Stream object. 11 | /// This wrapper class ensures they are simultaneously disposed of. 12 | /// 13 | /// 14 | public class IpcStreamWrapper : IDisposable 15 | { 16 | private IDisposable _context; 17 | bool _disposed = false; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The stream. 23 | /// The IDisposable context the Stream depends on. 24 | public IpcStreamWrapper(Stream stream, IDisposable context = null) 25 | { 26 | Stream = stream; 27 | _context = context; 28 | } 29 | 30 | public Stream Stream { get; private set; } 31 | 32 | public void Dispose() 33 | { 34 | if (!_disposed) 35 | { 36 | _disposed = true; 37 | Stream?.Dispose(); 38 | _context?.Dispose(); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IpcClientOptions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Services; 2 | using System; 3 | using System.IO; 4 | 5 | namespace JKang.IpcServiceFramework.Client 6 | { 7 | public class IpcClientOptions 8 | { 9 | public Func StreamTranslator { get; set; } 10 | 11 | /// 12 | /// The number of milliseconds to wait for the server to respond before 13 | /// the connection times out. Default value is 60000. 14 | /// 15 | public int ConnectionTimeout { get; set; } = 60000; 16 | 17 | /// 18 | /// Indicates the method that will be used during deserialization on the server for locating and loading assemblies. 19 | /// If false, the assembly used during deserialization must match exactly the assembly used during serialization. 20 | /// 21 | /// If true, the assembly used during deserialization need not match exactly the assembly used during serialization. 22 | /// Specifically, the version numbers need not match. 23 | /// 24 | /// Default is false. 25 | /// 26 | public bool UseSimpleTypeNameAssemblyFormatHandling { get; set; } = false; 27 | 28 | public IIpcMessageSerializer Serializer { get; set; } = new DefaultIpcMessageSerializer(); 29 | 30 | public IValueConverter ValueConverter { get; set; } = new DefaultValueConverter(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.NamedPipe/NamedPipeIpcHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Hosting.NamedPipe; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | 6 | namespace JKang.IpcServiceFramework.Hosting 7 | { 8 | public static class NamedPipeIpcHostBuilderExtensions 9 | { 10 | public static IIpcHostBuilder AddNamedPipeEndpoint(this IIpcHostBuilder builder, 11 | string pipeName) 12 | where TContract : class 13 | { 14 | return builder.AddNamedPipeEndpoint(options => 15 | { 16 | options.PipeName = pipeName; 17 | }); 18 | } 19 | 20 | public static IIpcHostBuilder AddNamedPipeEndpoint(this IIpcHostBuilder builder, 21 | Action configure) 22 | where TContract : class 23 | { 24 | var options = new NamedPipeIpcEndpointOptions(); 25 | configure?.Invoke(options); 26 | 27 | builder.AddIpcEndpoint(serviceProvider => 28 | { 29 | ILogger> logger = serviceProvider 30 | .GetRequiredService>>(); 31 | 32 | return new NamedPipeIpcEndpoint(options, logger, serviceProvider); 33 | }); 34 | 35 | return builder; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IpcClientRegistration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JKang.IpcServiceFramework.Client 4 | { 5 | public class IpcClientRegistration 6 | where TContract: class 7 | where TIpcClientOptions: IpcClientOptions 8 | { 9 | private readonly Func> _clientFactory; 10 | private readonly Action _configureOptions; 11 | 12 | public IpcClientRegistration(string name, 13 | Func> clientFactory, 14 | Action configureOptions) 15 | { 16 | Name = name; 17 | _clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); 18 | _configureOptions = configureOptions; 19 | } 20 | 21 | public string Name { get; } 22 | 23 | public IIpcClient CreateClient(IServiceProvider serviceProvider) 24 | { 25 | if (serviceProvider is null) 26 | { 27 | throw new ArgumentNullException(nameof(serviceProvider)); 28 | } 29 | 30 | TIpcClientOptions options = Activator.CreateInstance(); 31 | _configureOptions?.Invoke(serviceProvider, options); 32 | return _clientFactory.Invoke(serviceProvider, options); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.Tcp/TcpIpcHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Hosting.Tcp; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Net; 6 | 7 | namespace JKang.IpcServiceFramework.Hosting 8 | { 9 | public static class TcpIpcHostBuilderExtensions 10 | { 11 | public static IIpcHostBuilder AddTcpEndpoint(this IIpcHostBuilder builder, 12 | IPAddress ipEndpoint, int port) 13 | where TContract : class 14 | { 15 | return builder.AddTcpEndpoint(options => 16 | { 17 | options.IpEndpoint = ipEndpoint; 18 | options.Port = port; 19 | }); 20 | } 21 | 22 | public static IIpcHostBuilder AddTcpEndpoint(this IIpcHostBuilder builder, 23 | Action configure) 24 | where TContract : class 25 | { 26 | var options = new TcpIpcEndpointOptions(); 27 | configure?.Invoke(options); 28 | 29 | builder.AddIpcEndpoint(serviceProvider => 30 | { 31 | ILogger> logger = serviceProvider 32 | .GetRequiredService>>(); 33 | 34 | return new TcpIpcEndpoint(options, logger, serviceProvider); 35 | }); 36 | 37 | return builder; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IpcClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace JKang.IpcServiceFramework.Client 7 | { 8 | internal class IpcClientFactory : IIpcClientFactory 9 | where TContract : class 10 | where TIpcClientOptions : IpcClientOptions 11 | { 12 | private readonly IServiceProvider _serviceProvider; 13 | private readonly IEnumerable> _registrations; 14 | 15 | public IpcClientFactory( 16 | IServiceProvider serviceProvider, 17 | IEnumerable> registrations) 18 | { 19 | _serviceProvider = serviceProvider; 20 | _registrations = registrations; 21 | } 22 | 23 | public IIpcClient CreateClient(string name) 24 | { 25 | IpcClientRegistration registration = _registrations.FirstOrDefault(x => x.Name == name); 26 | if (registration == null) 27 | { 28 | throw new ArgumentException($"IPC client '{name}' is not configured.", nameof(name)); 29 | } 30 | 31 | using (IServiceScope scope = _serviceProvider.CreateScope()) 32 | { 33 | return registration.CreateClient(scope.ServiceProvider); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/Fixtures/XorStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace JKang.IpcServiceFramework.Testing.Fixtures 4 | { 5 | public class XorStream : Stream 6 | { 7 | private readonly Stream _baseStream; 8 | 9 | public XorStream(Stream stream) 10 | { 11 | _baseStream = stream; 12 | } 13 | 14 | public override bool CanRead => _baseStream.CanRead; 15 | 16 | public override bool CanSeek => _baseStream.CanSeek; 17 | 18 | public override bool CanWrite => _baseStream.CanWrite; 19 | 20 | public override long Length => _baseStream.Length; 21 | 22 | public override long Position { get => _baseStream.Position; set => _baseStream.Position = value; } 23 | 24 | public override void Flush() 25 | { 26 | _baseStream.Flush(); 27 | } 28 | 29 | public override int Read(byte[] buffer, int offset, int count) 30 | { 31 | int br = _baseStream.Read(buffer, offset, count); 32 | for (int i = offset; i < offset + br; i++) 33 | { 34 | buffer[i] ^= 0xFF; 35 | } 36 | 37 | return br; 38 | } 39 | 40 | public override long Seek(long offset, SeekOrigin origin) 41 | { 42 | return _baseStream.Seek(offset, origin); 43 | } 44 | 45 | public override void SetLength(long value) 46 | { 47 | _baseStream.SetLength(value); 48 | } 49 | 50 | public override void Write(byte[] buffer, int offset, int count) 51 | { 52 | byte[] xoredBuffer = new byte[count]; 53 | for (int i = 0; i < count; i++) 54 | { 55 | xoredBuffer[i] = (byte)(buffer[offset + i] ^ 0xFF); 56 | } 57 | 58 | _baseStream.Write(xoredBuffer, 0, count); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/Services/DefaultIpcMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Text; 4 | 5 | namespace JKang.IpcServiceFramework.Services 6 | { 7 | public class DefaultIpcMessageSerializer : IIpcMessageSerializer 8 | { 9 | private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings 10 | { 11 | TypeNameHandling = TypeNameHandling.Objects 12 | }; 13 | 14 | public IpcRequest DeserializeRequest(byte[] binary) 15 | { 16 | return Deserialize(binary); 17 | } 18 | 19 | public IpcResponse DeserializeResponse(byte[] binary) 20 | { 21 | return Deserialize(binary); 22 | } 23 | 24 | public byte[] SerializeRequest(IpcRequest request) 25 | { 26 | return Serialize(request); 27 | } 28 | 29 | public byte[] SerializeResponse(IpcResponse response) 30 | { 31 | return Serialize(response); 32 | } 33 | 34 | private T Deserialize(byte[] binary) 35 | { 36 | try 37 | { 38 | string json = Encoding.UTF8.GetString(binary); 39 | return JsonConvert.DeserializeObject(json, _settings); 40 | } 41 | catch (Exception ex) when ( 42 | ex is JsonSerializationException || 43 | ex is ArgumentException || 44 | ex is EncoderFallbackException) 45 | { 46 | throw new IpcSerializationException("Failed to deserialize IPC message", ex); 47 | } 48 | } 49 | 50 | private byte[] Serialize(object obj) 51 | { 52 | try 53 | { 54 | string json = JsonConvert.SerializeObject(obj, _settings); 55 | return Encoding.UTF8.GetBytes(json); 56 | } 57 | catch (Exception ex) when ( 58 | ex is JsonSerializationException || 59 | ex is EncoderFallbackException) 60 | { 61 | throw new IpcSerializationException("Failed to serialize IPC message", ex); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client.Tcp/TcpIpcClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Security; 4 | using System.Net.Sockets; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace JKang.IpcServiceFramework.Client.Tcp 9 | { 10 | internal class TcpIpcClient : IpcClient 11 | where TInterface : class 12 | { 13 | private readonly TcpIpcClientOptions _options; 14 | 15 | public TcpIpcClient(string name, TcpIpcClientOptions options) 16 | : base(name, options) 17 | { 18 | _options = options ?? throw new ArgumentNullException(nameof(options)); 19 | } 20 | 21 | protected override Task ConnectToServerAsync(CancellationToken cancellationToken) 22 | { 23 | cancellationToken.ThrowIfCancellationRequested(); 24 | #pragma warning disable CA2000 // Dispose objects before losing scope. Disposed by IpcStreamWrapper 25 | TcpClient client = new TcpClient(); 26 | #pragma warning restore CA2000 // Dispose objects before losing scope 27 | 28 | if (!client.ConnectAsync(_options.ServerIp, _options.ServerPort) 29 | .Wait(_options.ConnectionTimeout, cancellationToken)) 30 | { 31 | client.Close(); 32 | cancellationToken.ThrowIfCancellationRequested(); 33 | throw new TimeoutException(); 34 | } 35 | 36 | Stream stream = client.GetStream(); 37 | 38 | // if SSL is enabled, wrap the stream in an SslStream in client mode 39 | if (_options.EnableSsl) 40 | { 41 | SslStream ssl; 42 | if (_options.SslValidationCallback == null) 43 | { 44 | ssl = new SslStream(stream, false); 45 | } 46 | else 47 | { 48 | ssl = new SslStream(stream, false, _options.SslValidationCallback); 49 | } 50 | 51 | // set client mode and specify the common name(CN) of the server 52 | if (_options.SslServerIdentity != null) 53 | { 54 | ssl.AuthenticateAsClient(_options.SslServerIdentity); 55 | } 56 | stream = ssl; 57 | } 58 | 59 | return Task.FromResult(new IpcStreamWrapper(stream, client)); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IpcRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Runtime.Serialization; 5 | 6 | namespace JKang.IpcServiceFramework 7 | { 8 | [DataContract] 9 | public class IpcRequest 10 | { 11 | [DataMember] 12 | public string MethodName { get; set; } 13 | 14 | [DataMember] 15 | public IEnumerable Parameters { get; set; } 16 | 17 | [DataMember] 18 | public IEnumerable ParameterTypes { get; set; } 19 | 20 | [DataMember] 21 | public IEnumerable ParameterTypesByName { get; set; } 22 | 23 | [DataMember] 24 | public IEnumerable GenericArguments { get; set; } 25 | 26 | [DataMember] 27 | public IEnumerable GenericArgumentsByName { get; set; } 28 | } 29 | 30 | /// 31 | /// Used to pass ParameterTypes annd GenericArguments by "Name" instead of by an explicit Type object. 32 | /// This allows for ParameterTypes to resolve properly even if the assembly version isn't an exact match on the Client & Host. 33 | /// 34 | [DataContract] 35 | public class IpcRequestParameterType 36 | { 37 | [DataMember] 38 | public string ParameterType { get; private set; } 39 | 40 | [DataMember] 41 | public string AssemblyName { get; private set; } 42 | 43 | /// 44 | /// Initializes a new instance of the class. 45 | /// 46 | public IpcRequestParameterType() 47 | { 48 | ParameterType = null; 49 | AssemblyName = null; 50 | } 51 | 52 | /// 53 | /// Initializes a new instance of the class. 54 | /// 55 | /// The type of parameter. 56 | /// paramType 57 | public IpcRequestParameterType(Type paramType) 58 | { 59 | if (paramType == null) 60 | { 61 | throw new ArgumentNullException(nameof(paramType)); 62 | } 63 | 64 | ParameterType = paramType.FullName; 65 | AssemblyName = paramType.Assembly.GetName().Name; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /doc/tcp/security.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | If you are running IPC channels over TCP on an untrusted network, you should consider using SSL. IpcServiceFramework supports SSL on TCP clients and hosts. 4 | 5 | ### Generate certificates for testing 6 | 7 | **Do not use the provided certificates in the project folder.** These are used for example purposes only. 8 | 9 | For testing, you can generate a self-signed certificate using the following openssl command: 10 | 11 | openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.cer -days 365 12 | 13 | This generates a key and a certificate that can be used for testing. 14 | 15 | ### Setting up the SSL endpoint 16 | 17 | The endpoint requires a PKCS12 file containing both the certificate and a corresponding private key. 18 | 19 | A certificate and key can be combined to a PKCS12 file for use with the server using the following command: 20 | 21 | openssl pkcs12 -export -in cert.cer -inkey key.pem -out server.pfx 22 | 23 | You will be asked for a password. 24 | 25 | You can import the certificate and provide it to the server endpoint using code similar to the following: 26 | 27 | var certificate = new X509Certificate2(@"path\to\server.pfx", "password"); 28 | serviceHostBuilder.AddTcpEndpoint("someEndpoint", ip, port, certificate); 29 | 30 | See the ConsoleServer and WebServer projects for more complete examples. 31 | 32 | Note: for security and maintenance reasons, we do not recommend that you hard-code the certificate password. It should instead be stored in the application configuration file so that it can be easily changed. 33 | 34 | ### Safe usage 35 | 36 | SSL/TLS is only secure if you use it properly. Here are some tips: 37 | 38 | * For production purposes, use a proper server certificate, signed by a real certificate authority (CA) or your organisation's internal CA. Do not use self-signed certificates in production. 39 | * Do not use custom certificate validation callbacks on the client. They are hard to implement correctly and tend to result in security issues. 40 | * Unconditionally returning true in a validation callback provides no security whatsoever against an attacker who can perform man-in-the-middle attacks. 41 | * The callback used in the ConsoleServer project example is not secure. It checks for the correct certificate by hash but does not check its validity, expiry date, revocation status, or other important security properties. 42 | 43 | ### Client certificates 44 | 45 | Client certificates are not currently supported. 46 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/StreamTranslatorTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using JKang.IpcServiceFramework.Client; 3 | using JKang.IpcServiceFramework.Hosting; 4 | using JKang.IpcServiceFramework.NamedPipeTests.Fixtures; 5 | using JKang.IpcServiceFramework.Testing; 6 | using JKang.IpcServiceFramework.Testing.Fixtures; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Moq; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace JKang.IpcServiceFramework.NamedPipeTests 13 | { 14 | public class StreamTranslatorTest : IClassFixture> 15 | { 16 | private readonly Mock _serviceMock = new Mock(); 17 | private readonly IpcApplicationFactory _factory; 18 | 19 | public StreamTranslatorTest(IpcApplicationFactory factory) 20 | { 21 | _factory = factory; 22 | } 23 | 24 | [Theory, AutoData] 25 | public async Task StreamTranslator_HappyPath(string pipeName, string input, string expected) 26 | { 27 | _serviceMock 28 | .Setup(x => x.StringType(input)) 29 | .Returns(expected); 30 | 31 | IIpcClient client = _factory 32 | .WithServiceImplementation(_ => _serviceMock.Object) 33 | .WithIpcHostConfiguration(hostBuilder => 34 | { 35 | hostBuilder.AddNamedPipeEndpoint(options => 36 | { 37 | options.PipeName = pipeName; 38 | options.StreamTranslator = x => new XorStream(x); 39 | }); 40 | }) 41 | .CreateClient((name, services) => 42 | { 43 | services.AddNamedPipeIpcClient(name, (_, options) => 44 | { 45 | options.PipeName = pipeName; 46 | options.StreamTranslator = x => new XorStream(x); 47 | }); 48 | }); 49 | 50 | #if !DISABLE_DYNAMIC_CODE_GENERATION 51 | string actual = await client.InvokeAsync(x => x.StringType(input)); 52 | 53 | Assert.Equal(expected, actual); 54 | #endif 55 | 56 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "StringType", new object[] { input }); 57 | var actual2 = await client.InvokeAsync(request); 58 | 59 | Assert.Equal(expected, actual2); 60 | 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /samples/IpcServiceSample.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30011.22 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IpcServiceSample.ServiceContracts", "IpcServiceSample.ServiceContracts\IpcServiceSample.ServiceContracts.csproj", "{75CA748C-4346-4266-B729-ACC4124B5C15}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IpcServiceSample.ConsoleClient", "IpcServiceSample.Client\IpcServiceSample.ConsoleClient.csproj", "{A3F3B3FA-4374-4DDB-B815-427C03B06F69}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IpcServiceSample.Server", "IpcServiceSample.Server\IpcServiceSample.Server.csproj", "{39CFB883-D3E3-43D0-A94C-4F587C45294B}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4F70218C-A4A5-4996-9633-5E0361337DD6}" 13 | ProjectSection(SolutionItems) = preProject 14 | ..\README.md = ..\README.md 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {75CA748C-4346-4266-B729-ACC4124B5C15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {75CA748C-4346-4266-B729-ACC4124B5C15}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {75CA748C-4346-4266-B729-ACC4124B5C15}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {75CA748C-4346-4266-B729-ACC4124B5C15}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {A3F3B3FA-4374-4DDB-B815-427C03B06F69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {A3F3B3FA-4374-4DDB-B815-427C03B06F69}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {A3F3B3FA-4374-4DDB-B815-427C03B06F69}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {A3F3B3FA-4374-4DDB-B815-427C03B06F69}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {39CFB883-D3E3-43D0-A94C-4F587C45294B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {39CFB883-D3E3-43D0-A94C-4F587C45294B}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {39CFB883-D3E3-43D0-A94C-4F587C45294B}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {39CFB883-D3E3-43D0-A94C-4F587C45294B}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {F174C67D-CEF7-4016-8DB6-94ED131FF8E6} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.Tcp/TcpIpcEndpoint.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Services; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.IO; 5 | using System.Net.Security; 6 | using System.Net.Sockets; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace JKang.IpcServiceFramework.Hosting.Tcp 11 | { 12 | public class TcpIpcEndpoint : IpcEndpoint 13 | where TContract : class 14 | { 15 | private readonly TcpIpcEndpointOptions _options; 16 | private readonly TcpListener _listener; 17 | 18 | public TcpIpcEndpoint( 19 | TcpIpcEndpointOptions options, 20 | ILogger> logger, 21 | IServiceProvider serviceProvider) 22 | : base(options, serviceProvider, logger) 23 | { 24 | _options = options ?? throw new ArgumentNullException(nameof(options)); 25 | _listener = new TcpListener(_options.IpEndpoint, _options.Port); 26 | _listener.Start(); 27 | } 28 | 29 | protected override async Task WaitAndProcessAsync( 30 | Func process, 31 | CancellationToken cancellationToken) 32 | { 33 | if (process is null) 34 | { 35 | throw new ArgumentNullException(nameof(process)); 36 | } 37 | 38 | using (TcpClient client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false)) 39 | { 40 | Stream server = client.GetStream(); 41 | 42 | if (_options.StreamTranslator != null) 43 | { 44 | server = _options.StreamTranslator(server); 45 | } 46 | 47 | // if SSL is enabled, wrap the stream in an SslStream in client mode 48 | if (_options.EnableSsl) 49 | { 50 | using (var ssl = new SslStream(server, false)) 51 | { 52 | ssl.AuthenticateAsServer(_options.SslCertificate 53 | ?? throw new IpcHostingConfigurationException("Invalid TCP IPC endpoint configured: SSL enabled without providing certificate.")); 54 | await process(ssl, cancellationToken).ConfigureAwait(false); 55 | } 56 | } 57 | else 58 | { 59 | await process(server, cancellationToken).ConfigureAwait(false); 60 | } 61 | 62 | client.Close(); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.TcpTests/HappyPathTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using JKang.IpcServiceFramework.Client; 3 | using JKang.IpcServiceFramework.Hosting; 4 | using JKang.IpcServiceFramework.TcpTests.Fixtures; 5 | using JKang.IpcServiceFramework.Testing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Moq; 8 | using System; 9 | using System.Net; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | 13 | namespace JKang.IpcServiceFramework.TcpTests 14 | { 15 | public class HappyPathTest : IClassFixture> 16 | { 17 | private static readonly Random _rand = new Random(); 18 | private readonly Mock _serviceMock = new Mock(); 19 | private readonly IIpcClient _client; 20 | 21 | public HappyPathTest(IpcApplicationFactory factory) 22 | { 23 | int port = _rand.Next(10000, 50000); 24 | _client = factory 25 | .WithServiceImplementation(_ => _serviceMock.Object) 26 | .WithIpcHostConfiguration(hostBuilder => 27 | { 28 | hostBuilder.AddTcpEndpoint(IPAddress.Loopback, port); 29 | }) 30 | .CreateClient((name, services) => 31 | { 32 | services.AddTcpIpcClient(name, IPAddress.Loopback, port); 33 | }); 34 | } 35 | 36 | [Theory, AutoData] 37 | public async Task HappyPath(string input, string expected) 38 | { 39 | #if !DISABLE_DYNAMIC_CODE_GENERATION 40 | 41 | _serviceMock 42 | .Setup(x => x.StringType(input)) 43 | .Returns(expected); 44 | 45 | string actual = await _client 46 | .InvokeAsync(x => x.StringType(input)); 47 | 48 | Assert.Equal(expected, actual); 49 | #endif 50 | } 51 | 52 | [Theory, AutoData] 53 | public async Task HappyPath2(string input, string expected) 54 | { 55 | _serviceMock 56 | .Setup(x => x.StringType(input)) 57 | .Returns(expected); 58 | 59 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "StringType", new object[] { input }); 60 | string actual = await _client 61 | .InvokeAsync(request); 62 | 63 | Assert.Equal(expected, actual); 64 | 65 | // Execute the method again to validate the socket isn't improperly disposed 66 | actual = await _client 67 | .InvokeAsync(x => x.StringType(input)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IpcResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace JKang.IpcServiceFramework 5 | { 6 | [DataContract] 7 | public class IpcResponse 8 | { 9 | public static IpcResponse Success(object data) 10 | => new IpcResponse(IpcStatus.Ok, data, null, null); 11 | 12 | public static IpcResponse BadRequest() 13 | => new IpcResponse(IpcStatus.BadRequest, null, null, null); 14 | 15 | public static IpcResponse BadRequest(string errorDetails) 16 | => new IpcResponse(IpcStatus.BadRequest, null, errorDetails, null); 17 | 18 | public static IpcResponse BadRequest(string errorDetails, Exception innerException) 19 | => new IpcResponse(IpcStatus.BadRequest, null, errorDetails, innerException); 20 | 21 | public static IpcResponse InternalServerError() 22 | => new IpcResponse(IpcStatus.InternalServerError, null, null, null); 23 | 24 | public static IpcResponse InternalServerError(string errorDetails) 25 | => new IpcResponse(IpcStatus.InternalServerError, null, errorDetails, null); 26 | 27 | public static IpcResponse InternalServerError(string errorDetails, Exception innerException) 28 | => new IpcResponse(IpcStatus.InternalServerError, null, errorDetails, innerException); 29 | 30 | public IpcResponse( 31 | IpcStatus status, 32 | object data, 33 | string errorMessage, 34 | Exception innerException) 35 | { 36 | Status = status; 37 | Data = data; 38 | ErrorMessage = errorMessage; 39 | InnerException = innerException; 40 | } 41 | 42 | [DataMember] 43 | public IpcStatus Status { get; } 44 | 45 | [DataMember] 46 | public object Data { get; } 47 | 48 | [DataMember] 49 | public string ErrorMessage { get; set; } 50 | 51 | [DataMember] 52 | public Exception InnerException { get; } 53 | 54 | public bool Succeed() => Status == IpcStatus.Ok; 55 | 56 | /// 57 | /// Create an exception that contains error information 58 | /// 59 | /// 60 | /// If the status is doesn't represent any error 61 | public IpcFaultException CreateFaultException() 62 | { 63 | if (Status <= IpcStatus.Ok) 64 | { 65 | throw new InvalidOperationException("The response doesn't contain any error"); 66 | } 67 | 68 | return new IpcFaultException(Status, ErrorMessage, InnerException); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Testing/IpcApplicationFactory.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Client; 2 | using JKang.IpcServiceFramework.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | using Microsoft.Extensions.Hosting; 6 | using Moq; 7 | using System; 8 | 9 | namespace JKang.IpcServiceFramework.Testing 10 | { 11 | public class IpcApplicationFactory : IDisposable 12 | where TContract : class 13 | { 14 | private Func _serviceFactory = _ => new Mock().Object; 15 | private Action _ipcHostConfig = _ => { }; 16 | private IHost _host = null; 17 | private bool _isDisposed = false; 18 | 19 | public IpcApplicationFactory WithServiceImplementation(TContract serviceInstance) 20 | { 21 | _serviceFactory = _ => serviceInstance; 22 | return this; 23 | } 24 | 25 | public IpcApplicationFactory WithServiceImplementation(Func serviceFactory) 26 | { 27 | _serviceFactory = serviceFactory; 28 | return this; 29 | } 30 | 31 | public IpcApplicationFactory WithIpcHostConfiguration(Action ipcHostConfig) 32 | { 33 | _ipcHostConfig = ipcHostConfig; 34 | return this; 35 | } 36 | 37 | public IIpcClient CreateClient(Action clientConfig) 38 | { 39 | if (clientConfig is null) 40 | { 41 | throw new ArgumentNullException(nameof(clientConfig)); 42 | } 43 | 44 | _host = Host.CreateDefaultBuilder() 45 | .ConfigureServices(x => x.TryAddScoped(_serviceFactory)) 46 | .ConfigureIpcHost(_ipcHostConfig) 47 | .Build(); 48 | 49 | _host.StartAsync().Wait(); 50 | 51 | string clientName = Guid.NewGuid().ToString(); 52 | var services = new ServiceCollection(); 53 | clientConfig.Invoke(clientName, services); 54 | 55 | return services.BuildServiceProvider() 56 | .GetRequiredService>() 57 | .CreateClient(clientName); 58 | } 59 | 60 | public void Dispose() 61 | { 62 | Dispose(true); 63 | } 64 | 65 | protected virtual void Dispose(bool disposing) 66 | { 67 | if (_isDisposed) 68 | { 69 | return; 70 | } 71 | 72 | if (disposing) 73 | { 74 | _host?.Dispose(); 75 | } 76 | 77 | _isDisposed = true; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IO/IpcWriter.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Services; 2 | using System; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace JKang.IpcServiceFramework.IO 8 | { 9 | public class IpcWriter : IDisposable 10 | { 11 | private readonly byte[] _lengthBuffer = new byte[4]; 12 | private readonly Stream _stream; 13 | private readonly IIpcMessageSerializer _serializer; 14 | private readonly bool _leaveOpen; 15 | 16 | public IpcWriter(Stream stream, IIpcMessageSerializer serializer) 17 | : this(stream, serializer, leaveOpen: false) 18 | { } 19 | 20 | public IpcWriter(Stream stream, IIpcMessageSerializer serializer, bool leaveOpen) 21 | { 22 | _stream = stream; 23 | _serializer = serializer; 24 | _leaveOpen = leaveOpen; 25 | } 26 | 27 | public async Task WriteAsync(IpcRequest request, 28 | CancellationToken cancellationToken = default) 29 | { 30 | byte[] binary = _serializer.SerializeRequest(request); 31 | await WriteMessageAsync(binary, cancellationToken).ConfigureAwait(false); 32 | } 33 | 34 | public async Task WriteAsync(IpcResponse response, 35 | CancellationToken cancellationToken = default) 36 | { 37 | byte[] binary = _serializer.SerializeResponse(response); 38 | await WriteMessageAsync(binary, cancellationToken).ConfigureAwait(false); 39 | } 40 | 41 | private async Task WriteMessageAsync(byte[] binary, CancellationToken cancellationToken) 42 | { 43 | int length = binary.Length; 44 | _lengthBuffer[0] = (byte)length; 45 | _lengthBuffer[1] = (byte)(length >> 8); 46 | _lengthBuffer[2] = (byte)(length >> 16); 47 | _lengthBuffer[3] = (byte)(length >> 24); 48 | 49 | await _stream.WriteAsync(_lengthBuffer, 0, _lengthBuffer.Length, cancellationToken).ConfigureAwait(false); 50 | await _stream.WriteAsync(binary, 0, binary.Length, cancellationToken).ConfigureAwait(false); 51 | } 52 | 53 | #region IDisposible 54 | 55 | bool _disposed = false; 56 | 57 | public void Dispose() 58 | { 59 | Dispose(true); 60 | GC.SuppressFinalize(this); 61 | } 62 | 63 | protected virtual void Dispose(bool disposing) 64 | { 65 | if (_disposed) 66 | { 67 | return; 68 | } 69 | 70 | if (disposing) 71 | { 72 | if (!_leaveOpen) 73 | { 74 | _stream.Dispose(); 75 | } 76 | } 77 | 78 | _disposed = true; 79 | } 80 | 81 | #endregion 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.TcpTests/EdgeCaseTest.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Client; 2 | using JKang.IpcServiceFramework.TcpTests.Fixtures; 3 | using JKang.IpcServiceFramework.Testing; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using System.Diagnostics; 7 | using System.Net; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace JKang.IpcServiceFramework.TcpTests 13 | { 14 | public class EdgeCaseTest : IClassFixture> 15 | { 16 | private readonly IpcApplicationFactory _factory; 17 | 18 | public EdgeCaseTest(IpcApplicationFactory factory) 19 | { 20 | _factory = factory; 21 | } 22 | 23 | [Fact] 24 | public async Task ConnectionTimeout_Throw() 25 | { 26 | int timeout = 3000; // 3s 27 | IIpcClient client = _factory 28 | .CreateClient((name, services) => 29 | { 30 | services.AddTcpIpcClient(name, (_, options) => 31 | { 32 | // Connect to a non-routable IP address can trigger timeout 33 | options.ServerIp = IPAddress.Parse("10.0.0.0"); 34 | options.ConnectionTimeout = timeout; 35 | }); 36 | }); 37 | 38 | var sw = Stopwatch.StartNew(); 39 | await Assert.ThrowsAsync(async () => 40 | { 41 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "StringType", new object[] { "abc" }); 42 | string output = await client.InvokeAsync(request); 43 | }); 44 | 45 | Assert.True(sw.ElapsedMilliseconds < timeout * 2); // make sure timeout works with marge 46 | } 47 | 48 | [Fact] 49 | public async Task ConnectionCancelled_Throw() 50 | { 51 | IIpcClient client = _factory 52 | .CreateClient((name, services) => 53 | { 54 | services.AddTcpIpcClient(name, (_, options) => 55 | { 56 | // Connect to a non-routable IP address can trigger timeout 57 | options.ServerIp = IPAddress.Parse("10.0.0.0"); 58 | }); 59 | }); 60 | 61 | using (var cts = new CancellationTokenSource()) 62 | { 63 | cts.CancelAfter(1000); 64 | await Assert.ThrowsAsync(async () => 65 | { 66 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "StringType", new object[] { string.Empty }); 67 | await client.InvokeAsync(request, cts.Token); 68 | }); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Testing/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using Xunit.Sdk; 8 | 9 | namespace JKang.IpcServiceFramework.Testing 10 | { 11 | public static class TestHelpers 12 | { 13 | /// 14 | /// Creates an IPC request for the given method in the given interface. 15 | /// 16 | /// The Type of the interface containing the method. 17 | /// Name of the method. 18 | /// The arguments to the method. 19 | /// IpcRequest object 20 | public static IpcRequest CreateIpcRequest(Type interfaceType, string methodName, params object[] args) 21 | { 22 | MethodInfo method = null; 23 | 24 | // Try to find the matching method based on name and args 25 | if (args.All(x => x != null)) 26 | { 27 | method = interfaceType.GetMethod(methodName, args.Select(x => x.GetType()).ToArray()); 28 | } 29 | 30 | if (method == null) 31 | { 32 | method = interfaceType.GetMethod(methodName); 33 | } 34 | 35 | if (method == null) 36 | { 37 | throw new ArgumentException($"Could not find a valid method in {interfaceType}!"); 38 | } 39 | 40 | if (method.IsGenericMethod) 41 | { 42 | throw new ArgumentException($"{methodName} is generic and not supported!"); 43 | } 44 | 45 | var methodParams = method.GetParameters(); 46 | 47 | var request = new IpcRequest() 48 | { 49 | MethodName = methodName, 50 | Parameters = args 51 | }; 52 | 53 | var parameterTypes = new Type[methodParams.Length]; 54 | for (int i = 0; i < args.Length; i++) 55 | { 56 | parameterTypes[i] = methodParams[i].ParameterType; 57 | } 58 | 59 | request.ParameterTypes = parameterTypes; 60 | 61 | return request; 62 | } 63 | 64 | 65 | /// 66 | /// Creates an IPC request for the given method name which takes no parameters. 67 | /// 68 | /// Name of the method. The method should have no parameters. 69 | /// IpcRequest object 70 | public static IpcRequest CreateIpcRequest(string methodName) 71 | { 72 | return new IpcRequest() 73 | { 74 | MethodName = methodName, 75 | Parameters = Array.Empty(), 76 | ParameterTypes = Array.Empty() 77 | }; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.NamedPipe/NamedPipeIpcEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipes; 4 | using System.Runtime.InteropServices; 5 | using System.Security.Principal; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace JKang.IpcServiceFramework.Hosting.NamedPipe 12 | { 13 | public class NamedPipeIpcEndpoint : IpcEndpoint 14 | where TContract : class 15 | { 16 | private readonly NamedPipeIpcEndpointOptions _options; 17 | 18 | public NamedPipeIpcEndpoint( 19 | NamedPipeIpcEndpointOptions options, 20 | ILogger> logger, 21 | IServiceProvider serviceProvider) 22 | : base(options, serviceProvider, logger) 23 | { 24 | _options = options; 25 | } 26 | 27 | protected override async Task WaitAndProcessAsync( 28 | Func process, 29 | CancellationToken cancellationToken) 30 | { 31 | if (process is null) 32 | { 33 | throw new ArgumentNullException(nameof(process)); 34 | } 35 | 36 | // https://github.com/PowerShell/PowerShellEditorServices/blob/f45c6312a859cde4aa25ea347a345e1d35238350/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs#L38-L67 37 | // Unfortunately, .NET Core does not support passing in a PipeSecurity object into the constructor for 38 | // NamedPipeServerStream so we are creating native Named Pipes and securing them using native APIs. 39 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 40 | { 41 | PipeSecurity pipeSecurity = new PipeSecurity(); 42 | SecurityIdentifier everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null); 43 | PipeAccessRule psRule = new PipeAccessRule(everyone, PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow); 44 | pipeSecurity.AddAccessRule(psRule); 45 | using (var server = NamedPipeNative.CreateNamedPipe(_options.PipeName, (uint) _options.MaxConcurrentCalls, pipeSecurity)) 46 | { 47 | await server.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false); 48 | await process(server, cancellationToken).ConfigureAwait(false); 49 | } 50 | } 51 | 52 | // Use original logic on other platforms. 53 | else 54 | { 55 | using (var server = new NamedPipeServerStream(_options.PipeName, PipeDirection.InOut, _options.MaxConcurrentCalls, 56 | PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) 57 | { 58 | await server.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false); 59 | await process(server, cancellationToken).ConfigureAwait(false); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/MultipleEndpointTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using Castle.DynamicProxy.Generators.Emitters.SimpleAST; 3 | using JKang.IpcServiceFramework.Client; 4 | using JKang.IpcServiceFramework.Hosting; 5 | using JKang.IpcServiceFramework.NamedPipeTests.Fixtures; 6 | using JKang.IpcServiceFramework.Testing; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Moq; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | 13 | namespace JKang.IpcServiceFramework.NamedPipeTests 14 | { 15 | public class MultipleEndpointTest 16 | { 17 | [Theory, AutoData] 18 | public async Task MultipleEndpoints( 19 | Mock service1, 20 | Mock service2) 21 | { 22 | IHost host = Host.CreateDefaultBuilder() 23 | .ConfigureServices(services => 24 | { 25 | services 26 | .AddScoped(x => service1.Object) 27 | .AddScoped(x => service2.Object); 28 | }) 29 | .ConfigureIpcHost(builder => 30 | { 31 | builder 32 | .AddNamedPipeEndpoint("pipe1") 33 | .AddNamedPipeEndpoint("pipe2"); 34 | }) 35 | .Build(); 36 | 37 | await host.StartAsync(); 38 | 39 | ServiceProvider clientServiceProvider = new ServiceCollection() 40 | .AddNamedPipeIpcClient("client1", "pipe1") 41 | .AddNamedPipeIpcClient("client2", "pipe2") 42 | .BuildServiceProvider(); 43 | 44 | IIpcClient client1 = clientServiceProvider 45 | .GetRequiredService>() 46 | .CreateClient("client1"); 47 | 48 | #if !DISABLE_DYNAMIC_CODE_GENERATION 49 | await client1.InvokeAsync(x => x.ReturnVoid()); 50 | service1.Verify(x => x.ReturnVoid(), Times.Once); 51 | #endif 52 | 53 | var request = TestHelpers.CreateIpcRequest("ReturnVoid"); 54 | await client1.InvokeAsync(request); 55 | #if !DISABLE_DYNAMIC_CODE_GENERATION 56 | service1.Verify(x => x.ReturnVoid(), Times.Exactly(2)); 57 | #else 58 | service1.Verify(x => x.ReturnVoid(), Times.Once); 59 | #endif 60 | 61 | IIpcClient client2 = clientServiceProvider 62 | .GetRequiredService>() 63 | .CreateClient("client2"); 64 | 65 | #if !DISABLE_DYNAMIC_CODE_GENERATION 66 | await client2.InvokeAsync(x => x.SomeMethod()); 67 | service2.Verify(x => x.SomeMethod(), Times.Once); 68 | #endif 69 | 70 | request = TestHelpers.CreateIpcRequest("SomeMethod"); 71 | await client2.InvokeAsync(request); 72 | 73 | #if !DISABLE_DYNAMIC_CODE_GENERATION 74 | service2.Verify(x => x.SomeMethod(), Times.Exactly(2)); 75 | #else 76 | service2.Verify(x => x.SomeMethod(), Times.Once); 77 | #endif 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jkang.perso@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/EdgeCaseTest.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Client; 2 | using JKang.IpcServiceFramework.NamedPipeTests.Fixtures; 3 | using JKang.IpcServiceFramework.Testing; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using System.Diagnostics; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | 12 | namespace JKang.IpcServiceFramework.NamedPipeTests 13 | { 14 | public class EdgeCaseTest : IClassFixture> 15 | { 16 | private readonly IpcApplicationFactory _factory; 17 | 18 | public EdgeCaseTest(IpcApplicationFactory factory) 19 | { 20 | _factory = factory; 21 | } 22 | 23 | [Fact] 24 | public async Task ServerIsOff_Timeout() 25 | { 26 | int timeout = 1000; // 1s 27 | IIpcClient client = _factory 28 | .CreateClient((name, services) => 29 | { 30 | services.AddNamedPipeIpcClient(name, (_, options) => 31 | { 32 | options.PipeName = "inexisted-pipe"; 33 | options.ConnectionTimeout = timeout; 34 | }); 35 | }); 36 | 37 | #if !DISABLE_DYNAMIC_CODE_GENERATION 38 | var sw = Stopwatch.StartNew(); 39 | await Assert.ThrowsAsync(async () => 40 | { 41 | string output = await client.InvokeAsync(x => x.StringType("abc")); 42 | }); 43 | 44 | Assert.True(sw.ElapsedMilliseconds < timeout * 2); // makesure timeout works with marge 45 | #endif 46 | 47 | var sw2 = Stopwatch.StartNew(); 48 | await Assert.ThrowsAsync(async () => 49 | { 50 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "StringType", new object[] { "abc" }); 51 | string output = await client.InvokeAsync(request); 52 | }); 53 | 54 | Assert.True(sw2.ElapsedMilliseconds < timeout * 2); // makesure timeout works with marge 55 | } 56 | 57 | [Fact] 58 | public async Task ConnectionCancelled_Throw() 59 | { 60 | IIpcClient client = _factory 61 | .CreateClient((name, services) => 62 | { 63 | services.AddNamedPipeIpcClient(name, (_, options) => 64 | { 65 | options.PipeName = "inexisted-pipe"; 66 | options.ConnectionTimeout = Timeout.Infinite; 67 | }); 68 | }); 69 | 70 | #if !DISABLE_DYNAMIC_CODE_GENERATION 71 | using (var cts = new CancellationTokenSource()) 72 | { 73 | cts.CancelAfter(1000); 74 | await Assert.ThrowsAsync(async () => 75 | { 76 | await client.InvokeAsync(x => x.ReturnVoid(), cts.Token); 77 | }); 78 | } 79 | #endif 80 | using (var cts = new CancellationTokenSource()) 81 | { 82 | cts.CancelAfter(1000); 83 | await Assert.ThrowsAsync(async () => 84 | { 85 | var request = TestHelpers.CreateIpcRequest("ReturnVoid"); 86 | await client.InvokeAsync(request, cts.Token); 87 | }); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/IO/IpcReader.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.Services; 2 | using System; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace JKang.IpcServiceFramework.IO 8 | { 9 | public class IpcReader : IDisposable 10 | { 11 | private readonly Stream _stream; 12 | private readonly IIpcMessageSerializer _serializer; 13 | private readonly bool _leaveOpen; 14 | 15 | public IpcReader(Stream stream, IIpcMessageSerializer serializer) 16 | : this(stream, serializer, leaveOpen: false) 17 | { } 18 | 19 | public IpcReader(Stream stream, IIpcMessageSerializer serializer, bool leaveOpen) 20 | { 21 | _stream = stream; 22 | _serializer = serializer; 23 | _leaveOpen = leaveOpen; 24 | } 25 | 26 | /// 27 | /// 28 | public async Task ReadIpcRequestAsync(CancellationToken cancellationToken = default) 29 | { 30 | byte[] binary = await ReadMessageAsync(cancellationToken).ConfigureAwait(false); 31 | return _serializer.DeserializeRequest(binary); 32 | } 33 | 34 | /// 35 | /// 36 | public async Task ReadIpcResponseAsync(CancellationToken cancellationToken = default) 37 | { 38 | byte[] binary = await ReadMessageAsync(cancellationToken).ConfigureAwait(false); 39 | return _serializer.DeserializeResponse(binary); 40 | } 41 | 42 | private async Task ReadMessageAsync(CancellationToken cancellationToken) 43 | { 44 | byte[] lengthBuffer = new byte[4]; 45 | int headerLength = await _stream 46 | .ReadAsync(lengthBuffer, 0, lengthBuffer.Length, cancellationToken) 47 | .ConfigureAwait(false); 48 | 49 | if (headerLength != 4) 50 | { 51 | throw new IpcCommunicationException($"Invalid message header length must be 4 but was {headerLength}"); 52 | } 53 | 54 | int remainingBytes = lengthBuffer[0] | lengthBuffer[1] << 8 | lengthBuffer[2] << 16 | lengthBuffer[3] << 24; 55 | 56 | byte[] buffer = new byte[65536]; 57 | int offset = 0; 58 | 59 | using (var ms = new MemoryStream()) 60 | { 61 | while (remainingBytes > 0) 62 | { 63 | int count = Math.Min(buffer.Length, remainingBytes); 64 | int actualCount = await _stream 65 | .ReadAsync(buffer, offset, count, cancellationToken) 66 | .ConfigureAwait(false); 67 | 68 | if (actualCount == 0) 69 | { 70 | throw new IpcCommunicationException("Stream closed unexpectedly."); 71 | } 72 | 73 | ms.Write(buffer, 0, actualCount); 74 | remainingBytes -= actualCount; 75 | } 76 | return ms.ToArray(); 77 | } 78 | } 79 | 80 | #region IDisposible 81 | 82 | bool _disposed = false; 83 | 84 | public void Dispose() 85 | { 86 | Dispose(true); 87 | GC.SuppressFinalize(this); 88 | } 89 | 90 | protected virtual void Dispose(bool disposing) 91 | { 92 | if (_disposed) 93 | { 94 | return; 95 | } 96 | 97 | if (disposing) 98 | { 99 | if (!_leaveOpen) 100 | { 101 | _stream.Dispose(); 102 | } 103 | } 104 | 105 | _disposed = true; 106 | } 107 | 108 | #endregion 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | | CI build | Stable build | 2 | |----------|--------------| 3 | |[![Build Status](https://dev.azure.com/jacques-kang/IpcServiceFramework/_apis/build/status/IpcServiceFramework%20CI?branchName=develop)](https://dev.azure.com/jacques-kang/IpcServiceFramework/_build/latest?definitionId=9&branchName=develop)|[![Build Status](https://dev.azure.com/jacques-kang/IpcServiceFramework/_apis/build/status/IpcServiceFramework?branchName=master)](https://dev.azure.com/jacques-kang/IpcServiceFramework/_build/latest?definitionId=14&branchName=master)| 4 | 5 | # IpcServiceFramework 6 | 7 | A .NET Core 3.1 based lightweight framework for efficient inter-process communication. 8 | Named pipeline and TCP support out-of-the-box, extensible with other protocols. 9 | 10 | ## NuGet packages 11 | | Name | Purpose | Status | 12 | | ---- | ------- | ------ | 13 | | JKang.IpcServiceFramework.Client.NamedPipe | Client SDK to consume IPC service over Named pipe | [![NuGet version](https://badge.fury.io/nu/JKang.IpcServiceFramework.Client.NamedPipe.svg)](https://badge.fury.io/nu/JKang.IpcServiceFramework.Client.NamedPipe) | 14 | | JKang.IpcServiceFramework.Client.Tcp | Client SDK to consume IPC service over TCP | [![NuGet version](https://badge.fury.io/nu/JKang.IpcServiceFramework.Client.Tcp.svg)](https://badge.fury.io/nu/JKang.IpcServiceFramework.Client.Tcp) | 15 | | JKang.IpcServiceFramework.Hosting.NamedPipe | Server SDK to run Named pipe IPC service endpoint | [![NuGet version](https://badge.fury.io/nu/JKang.IpcServiceFramework.Hosting.NamedPipe.svg)](https://badge.fury.io/nu/JKang.IpcServiceFramework.Hosting.NamedPipe) | 16 | | JKang.IpcServiceFramework.Hosting.Tcp | Server SDK to run TCP IPC service endpoint | [![NuGet version](https://badge.fury.io/nu/JKang.IpcServiceFramework.Hosting.Tcp.svg)](https://badge.fury.io/nu/JKang.IpcServiceFramework.Hosting.Tcp) | 17 | 18 | 19 | ## Usage 20 | 21 | 1. Create an interface as service contract and package it in an assembly to be referenced by server and client applications, for example: 22 | 23 | ```csharp 24 | public interface IInterProcessService 25 | { 26 | string ReverseString(string input); 27 | } 28 | ``` 29 | 30 | 1. Implement the service in server application, for example: 31 | 32 | ```csharp 33 | class InterProcessService : IInterProcessService 34 | { 35 | public string ReverseString(string input) 36 | { 37 | char[] charArray = input.ToCharArray(); 38 | Array.Reverse(charArray); 39 | return new string(charArray); 40 | } 41 | } 42 | ``` 43 | 44 | 1. Install the following NuGet packages in server application: 45 | 46 | ```powershell 47 | > Install-Package Microsoft.Extensions.Hosting 48 | > Install-Package JKang.IpcServiceFramework.Hosting.NamedPipe 49 | ``` 50 | 51 | 1. Register the service implementation and configure IPC endpoint(s): 52 | 53 | ```csharp 54 | class Program 55 | { 56 | public static void Main(string[] args) 57 | { 58 | CreateHostBuilder(args).Build().Run(); 59 | } 60 | 61 | public static IHostBuilder CreateHostBuilder(string[] args) => 62 | Host.CreateDefaultBuilder(args) 63 | .ConfigureServices(services => 64 | { 65 | services.AddScoped(); 66 | }) 67 | .ConfigureIpcHost(builder => 68 | { 69 | // configure IPC endpoints 70 | builder.AddNamedPipeEndpoint(pipeName: "pipeinternal"); 71 | }) 72 | .ConfigureLogging(builder => 73 | { 74 | // optionally configure logging 75 | builder.SetMinimumLevel(LogLevel.Information); 76 | }); 77 | } 78 | ``` 79 | 80 | 1. Install the following NuGet package in client application: 81 | 82 | ```powershell 83 | > Install-Package JKang.IpcServiceFramework.Client.NamedPipe 84 | ``` 85 | 86 | 1. Invoke the server 87 | 88 | ```csharp 89 | // register IPC clients 90 | ServiceProvider serviceProvider = new ServiceCollection() 91 | .AddNamedPipeIpcClient("client1", pipeName: "pipeinternal") 92 | .BuildServiceProvider(); 93 | 94 | // resolve IPC client factory 95 | IIpcClientFactory clientFactory = serviceProvider 96 | .GetRequiredService>(); 97 | 98 | // create client 99 | IIpcClient client = clientFactory.CreateClient("client1"); 100 | 101 | string output = await client.InvokeAsync(x => x.ReverseString(input)); 102 | ``` 103 | 104 | ## FAQs 105 | 106 | 107 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Copilot instructions for IpcServiceFramework 2 | 3 | Purpose: Provide targeted, actionable guidance so an AI coding agent can be immediately productive in this repo. 4 | 5 | ## Quick summary (big picture) 6 | - This repo implements a small .NET Core IPC framework that exposes: client libraries, server hosting, and protocol-specific transports (Named Pipe, TCP). 7 | - Key components: 8 | - `src/JKang.IpcServiceFramework.Core` — core message types (`IpcRequest`, `IpcResponse`, `IpcStatus`), serializer interface (`IIpcMessageSerializer`), IO abstractions (`IpcReader`/`IpcWriter`). 9 | - `src/*Client*` — client SDKs + DI registration helpers (e.g., `AddNamedPipeIpcClient`). 10 | - `src/*Hosting*` — host-side endpoint abstractions and `ConfigureIpcHost`/`IIpcHostBuilder` extensions (e.g., `AddNamedPipeEndpoint`). 11 | - `samples/` — runnable examples (server and client) demonstrating DI registration and usage (`samples/*Program.cs`). 12 | 13 | ## When you edit code 14 | - Preserve public API shapes: many projects are shipped as NuGet packages; avoid breaking changes without a version bump in `build/version.yml`. 15 | - Add tests for behavior changes; integration tests typically use `IpcApplicationFactory` (see `src/JKang.IpcServiceFramework.Testing`) to spin up host+client in-proc. 16 | 17 | ## Build / Test / CI 18 | - Local build: `dotnet build src/*.sln --configuration Release` ✅ 19 | - Run tests: `dotnet test src/*Tests/*.csproj --configuration Release` ✅ 20 | - CI: Azure Pipelines is used (`build/azure-pipelines-ci.yml`); build/template runs `dotnet build` then `dotnet test` and packages artifacts. 21 | - Versioning: package versions are controlled by `build/version.yml` — update that file when preparing a new release. 22 | - Signing: assemblies are strong-named (`SignAssembly` true in `src/Directory.Build.props`) using `IpcServiceFramework.snk`. If missing, builds may fail. 23 | 24 | ## Important conventions & patterns (project-specific) 25 | - DI registration: 26 | - Server: `Host.CreateDefaultBuilder(...).ConfigureIpcHost(builder => builder.AddNamedPipeEndpoint(...))` (see `samples/` and `NamedPipeIpcHostBuilderExtensions.cs`). 27 | - Client: use `IServiceCollection.AddNamedPipeIpcClient("name", pipeName)` then `IIpcClientFactory.CreateClient("name")`. 28 | - Request generation: 29 | - Clients can invoke via expression proxies (DispatchProxy) `client.InvokeAsync(x => x.Method(...))` or by building `IpcRequest` directly (`TestHelpers.CreateIpcRequest(...)`). Use the proxy when allowed; dynamic codegen can be disabled via the `DisableDynamicCodeGeneration` build define. 30 | - Serialization: 31 | - Default serializer: `DefaultIpcMessageSerializer` uses `Newtonsoft.Json` with `TypeNameHandling.Objects`. Be careful: this affects type resolution and has security implications. The client option `UseSimpleTypeNameAssemblyFormatHandling` toggles how type assembly names are handled (see `IpcClientOptions.UseSimpleTypeNameAssemblyFormatHandling`). 32 | - Error handling: 33 | - Server maps exceptions to `IpcResponse` with `IpcStatus` and the client throws `IpcFaultException` for non-OK statuses. Use `IpcEndpointOptions.IncludeFailureDetailsInResponse` to include exception messages in responses (useful for tests). 34 | - Streams & translators: 35 | - You can wrap streams for custom handshakes/logging with `IpcClientOptions.StreamTranslator` (see `doc/stream-translator.md` and `samples` examples). 36 | 37 | ## Testing notes & patterns 38 | - Tests use xUnit, Moq, AutoFixture and the `IpcApplicationFactory` pattern to create host+client pairs. 39 | - Many tests verify both dynamic proxy invocation and static `IpcRequest` invocation — ensure you add test coverage for both when relevant. 40 | - The test projects target `netcoreapp3.1`; library projects target `netstandard2.0`. 41 | - To test environments where dynamic generation is unavailable, set `DisableDynamicCodeGeneration=true` (either in a build property or CI matrix). 42 | 43 | ## Security & operational notes 44 | - TCP over untrusted networks: prefer SSL/TLS (see `doc/tcp/security.md`) and do not use example certs in production. 45 | - Serializer uses `TypeNameHandling.Objects`: when changing serializer behavior, ensure tests cover scenarios with mismatched assemblies and type name handling. 46 | - Be cautious when enabling `IncludeFailureDetailsInResponse` in production — it can leak server exception details. 47 | 48 | ## Good first tasks for an AI agent 49 | - Small bugfixes: add unit tests under `src/*Tests`, follow existing test patterns, use `IpcApplicationFactory` for integration tests. 50 | - Feature work: register DI helpers (follow `*ServiceCollectionExtensions.cs` structure) and add corresponding tests (contract tests and error cases). 51 | - Refactors: keep backward compatibility; update `build/version.yml` when the public API changes. 52 | 53 | --- 54 | 55 | If anything above is unclear or you want more detail for any section (examples, tests, or CI matrix samples), tell me which parts to expand and I will iterate. ✅ -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core/Services/DefaultValueConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Globalization; 5 | 6 | namespace JKang.IpcServiceFramework.Services 7 | { 8 | public class DefaultValueConverter : IValueConverter 9 | { 10 | public bool TryConvert(object origValue, Type destType, out object destValue) 11 | { 12 | if (destType is null) 13 | { 14 | throw new ArgumentNullException(nameof(destType)); 15 | } 16 | 17 | Type destConcreteType = Nullable.GetUnderlyingType(destType); 18 | 19 | if (origValue == null) 20 | { 21 | destValue = null; 22 | return destType.IsClass || (destConcreteType != null); 23 | } 24 | 25 | if (destConcreteType != null) 26 | { 27 | destType = destConcreteType; 28 | } 29 | 30 | if (destType.IsAssignableFrom(origValue.GetType())) 31 | { 32 | // copy value directly if it can be assigned to destType 33 | destValue = origValue; 34 | return true; 35 | } 36 | 37 | if (destType.IsEnum) 38 | { 39 | if (origValue is string str) 40 | { 41 | try 42 | { 43 | destValue = Enum.Parse(destType, str, ignoreCase: true); 44 | return true; 45 | } 46 | catch (Exception ex) when ( 47 | ex is ArgumentNullException || 48 | ex is ArgumentException || 49 | ex is OverflowException 50 | ) 51 | { } 52 | } 53 | else 54 | { 55 | try 56 | { 57 | destValue = Enum.ToObject(destType, origValue); 58 | return true; 59 | } 60 | catch (Exception ex) when ( 61 | ex is ArgumentNullException || 62 | ex is ArgumentException 63 | ) 64 | { } 65 | } 66 | } 67 | 68 | if (origValue is string origStringValue) 69 | { 70 | if ((destType == typeof(Guid)) && Guid.TryParse(origStringValue, out Guid guidResult)) 71 | { 72 | destValue = guidResult; 73 | return true; 74 | } 75 | 76 | if ((destType == typeof(TimeSpan)) && TimeSpan.TryParse(origStringValue, CultureInfo.InvariantCulture, out TimeSpan timeSpanResult)) 77 | { 78 | destValue = timeSpanResult; 79 | return true; 80 | } 81 | 82 | if ((destType == typeof(DateTime)) && DateTime.TryParse(origStringValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTimeResult)) 83 | { 84 | destValue = dateTimeResult; 85 | return true; 86 | } 87 | } 88 | 89 | if ((origValue is TimeSpan timeSpan) && (destType == typeof(string))) 90 | { 91 | destValue = timeSpan.ToString("c", CultureInfo.InvariantCulture); 92 | return true; 93 | } 94 | 95 | if ((origValue is DateTime dateTime) && (destType == typeof(string))) 96 | { 97 | destValue = dateTime.ToString("o", CultureInfo.InvariantCulture); 98 | return true; 99 | } 100 | 101 | if (origValue is JObject jObj) 102 | { 103 | // rely on JSON.Net to convert complexe type 104 | destValue = jObj.ToObject(destType); 105 | // TODO: handle error 106 | return true; 107 | } 108 | 109 | if (origValue is JArray jArray) 110 | { 111 | destValue = jArray.ToObject(destType); 112 | return true; 113 | } 114 | 115 | try 116 | { 117 | destValue = Convert.ChangeType(origValue, destType, CultureInfo.InvariantCulture); 118 | return true; 119 | } 120 | catch (Exception ex) when ( 121 | ex is InvalidCastException || 122 | ex is FormatException || 123 | ex is OverflowException || 124 | ex is ArgumentNullException) 125 | { } 126 | 127 | try 128 | { 129 | destValue = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(origValue), destType); 130 | return true; 131 | } 132 | catch (JsonException) 133 | { } 134 | 135 | destValue = null; 136 | return false; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting.NamedPipe/NamedPipeNative.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipes; 3 | using System.Runtime.InteropServices; 4 | using System.Security.AccessControl; 5 | 6 | using Microsoft.Win32.SafeHandles; 7 | 8 | namespace JKang.IpcServiceFramework.Hosting.NamedPipe 9 | { 10 | /// 11 | /// Native API for Named Pipes 12 | /// https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs#L124-L256 13 | /// 14 | internal static class NamedPipeNative 15 | { 16 | #region Pipe constants 17 | 18 | // Pipe open mode 19 | internal const uint PIPE_ACCESS_DUPLEX = 0x00000003; 20 | 21 | // Pipe modes 22 | internal const uint PIPE_TYPE_BYTE = 0x00000000; 23 | internal const uint FILE_FLAG_OVERLAPPED = 0x40000000; 24 | internal const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000; 25 | internal const uint PIPE_READMODE_BYTE = 0x00000000; 26 | 27 | #endregion 28 | 29 | #region Data structures 30 | 31 | [StructLayout(LayoutKind.Sequential)] 32 | internal class SECURITY_ATTRIBUTES 33 | { 34 | /// 35 | /// The size, in bytes, of this structure. Set this value to the size of the SECURITY_ATTRIBUTES structure. 36 | /// 37 | public int NLength; 38 | 39 | /// 40 | /// A pointer to a security descriptor for the object that controls the sharing of it. 41 | /// 42 | public IntPtr LPSecurityDescriptor = IntPtr.Zero; 43 | 44 | /// 45 | /// A Boolean value that specifies whether the returned handle is inherited when a new process is created. 46 | /// 47 | public bool InheritHandle; 48 | 49 | /// 50 | /// Initializes a new instance of the SECURITY_ATTRIBUTES class 51 | /// 52 | public SECURITY_ATTRIBUTES() 53 | { 54 | this.NLength = 12; 55 | } 56 | } 57 | 58 | #endregion 59 | 60 | #region Pipe methods 61 | 62 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 63 | internal static extern SafePipeHandle CreateNamedPipe( 64 | string lpName, 65 | uint dwOpenMode, 66 | uint dwPipeMode, 67 | uint nMaxInstances, 68 | uint nOutBufferSize, 69 | uint nInBufferSize, 70 | uint nDefaultTimeOut, 71 | SECURITY_ATTRIBUTES securityAttributes); 72 | 73 | internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle, bool inheritHandle = false) 74 | { 75 | SECURITY_ATTRIBUTES securityAttributes = new NamedPipeNative.SECURITY_ATTRIBUTES(); 76 | securityAttributes.InheritHandle = inheritHandle; 77 | securityAttributes.NLength = (int)Marshal.SizeOf(securityAttributes); 78 | securityAttributes.LPSecurityDescriptor = securityDescriptorPinnedHandle.AddrOfPinnedObject(); 79 | return securityAttributes; 80 | } 81 | 82 | /// 83 | /// Helper method to create a PowerShell transport named pipe via native API, along 84 | /// with a returned .Net NamedPipeServerStream object wrapping the named pipe. 85 | /// 86 | /// Named pipe core name. 87 | /// 88 | /// NamedPipeServerStream 89 | internal static NamedPipeServerStream CreateNamedPipe( 90 | string pipeName, 91 | uint maxNumberOfServerInstances, 92 | PipeSecurity pipeSecurity) 93 | 94 | { 95 | string fullPipeName = @"\\.\pipe\" + pipeName; 96 | 97 | CommonSecurityDescriptor securityDesc = new CommonSecurityDescriptor(false, false, pipeSecurity.GetSecurityDescriptorBinaryForm(), 0); 98 | 99 | // Create optional security attributes based on provided PipeSecurity. 100 | NamedPipeNative.SECURITY_ATTRIBUTES securityAttributes = null; 101 | GCHandle? securityDescHandle = null; 102 | if (securityDesc != null) 103 | { 104 | byte[] securityDescBuffer = new byte[securityDesc.BinaryLength]; 105 | securityDesc.GetBinaryForm(securityDescBuffer, 0); 106 | 107 | securityDescHandle = GCHandle.Alloc(securityDescBuffer, GCHandleType.Pinned); 108 | securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value); 109 | } 110 | 111 | uint openMode = NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_OVERLAPPED; 112 | if (maxNumberOfServerInstances == 1) 113 | { 114 | openMode |= NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE; 115 | } 116 | 117 | // Create named pipe. 118 | SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe( 119 | fullPipeName, 120 | openMode, 121 | NamedPipeNative.PIPE_TYPE_BYTE | NamedPipeNative.PIPE_READMODE_BYTE, 122 | maxNumberOfServerInstances, 123 | 1, 124 | 1, 125 | 0, 126 | securityAttributes); 127 | 128 | int lastError = Marshal.GetLastWin32Error(); 129 | if (securityDescHandle != null) 130 | { 131 | securityDescHandle.Value.Free(); 132 | } 133 | 134 | if (pipeHandle.IsInvalid) 135 | { 136 | throw new InvalidOperationException(); 137 | } 138 | 139 | // Create the .Net NamedPipeServerStream wrapper. 140 | try 141 | { 142 | return new NamedPipeServerStream( 143 | PipeDirection.InOut, 144 | true, // IsAsync 145 | false, // IsConnected 146 | pipeHandle); 147 | } 148 | catch (Exception) 149 | { 150 | pipeHandle.Dispose(); 151 | throw; 152 | } 153 | } 154 | #endregion 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/SimpleTypeNameContractTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using JKang.IpcServiceFramework.Client; 3 | using JKang.IpcServiceFramework.Hosting; 4 | using JKang.IpcServiceFramework.NamedPipeTests.Fixtures; 5 | using JKang.IpcServiceFramework.Testing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Moq; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Globalization; 11 | using System.Linq; 12 | using System.Numerics; 13 | using System.Threading.Tasks; 14 | using Xunit; 15 | 16 | namespace JKang.IpcServiceFramework.NamedPipeTests 17 | { 18 | /// 19 | /// Validates the IPC pipeline is working end-to-end for a variety of method types. 20 | /// Tests both dynamically generated IpcRequests (via DispatchProxy) and statically generated ones. 21 | /// Tests using simple parameter types (IpcClentOptions.UseSimpleTypeNameAssemblyFormatHandling == true). 22 | /// 23 | /// 24 | public class SimpleTypeNameContractTest : IClassFixture> 25 | { 26 | private readonly Mock _serviceMock = new Mock(); 27 | private readonly IIpcClient _client; 28 | 29 | public SimpleTypeNameContractTest(IpcApplicationFactory factory) 30 | { 31 | string pipeName = Guid.NewGuid().ToString(); 32 | _client = factory 33 | .WithServiceImplementation(_ => _serviceMock.Object) 34 | .WithIpcHostConfiguration(hostBuilder => 35 | { 36 | hostBuilder.AddNamedPipeEndpoint(options => 37 | { 38 | options.PipeName = pipeName; 39 | options.IncludeFailureDetailsInResponse = true; 40 | }); 41 | }) 42 | .CreateClient((name, services) => 43 | { 44 | services.AddNamedPipeIpcClient(name, (_, options) => 45 | { 46 | options.UseSimpleTypeNameAssemblyFormatHandling = true; 47 | options.PipeName = pipeName; 48 | } 49 | ); 50 | }); 51 | } 52 | 53 | [Theory, AutoData] 54 | public async Task PrimitiveTypes(bool a, byte b, sbyte c, char d, decimal e, double f, float g, int h, uint i, 55 | long j, ulong k, short l, ushort m, int expected) 56 | { 57 | _serviceMock 58 | .Setup(x => x.PrimitiveTypes(a, b, c, d, e, f, g, h, i, j, k, l, m)) 59 | .Returns(expected); 60 | 61 | int actual = await _client 62 | .InvokeAsync(x => x.PrimitiveTypes(a, b, c, d, e, f, g, h, i, j, k, l, m)); 63 | 64 | Assert.Equal(expected, actual); 65 | } 66 | 67 | [Theory, AutoData] 68 | public async Task StringType(string input, string expected) 69 | { 70 | _serviceMock 71 | .Setup(x => x.StringType(input)) 72 | .Returns(expected); 73 | 74 | string actual = await _client 75 | .InvokeAsync(x => x.StringType(input)); 76 | 77 | Assert.Equal(expected, actual); 78 | } 79 | 80 | [Theory, AutoData] 81 | public async Task ComplexType(Complex input, Complex expected) 82 | { 83 | _serviceMock.Setup(x => x.ComplexType(input)).Returns(expected); 84 | 85 | Complex actual = await _client 86 | .InvokeAsync(x => x.ComplexType(input)); 87 | 88 | Assert.Equal(expected, actual); 89 | } 90 | 91 | [Theory, AutoData] 92 | public async Task ComplexTypeArray(IEnumerable input, IEnumerable expected) 93 | { 94 | _serviceMock 95 | .Setup(x => x.ComplexTypeArray(input)) 96 | .Returns(expected); 97 | 98 | IEnumerable actual = await _client 99 | .InvokeAsync(x => x.ComplexTypeArray(input)); 100 | 101 | Assert.Equal(expected, actual); 102 | } 103 | 104 | [Theory, AutoData] 105 | public async Task LargeComplexTypeArray(Complex input, Complex expected) 106 | { 107 | IEnumerable largeInput = Enumerable.Repeat(input, 1000); 108 | IEnumerable largeExpected = Enumerable.Repeat(expected, 100); 109 | 110 | _serviceMock 111 | .Setup(x => x.ComplexTypeArray(largeInput)) 112 | .Returns(largeExpected); 113 | 114 | IEnumerable actual = await _client 115 | .InvokeAsync(x => x.ComplexTypeArray(largeInput)); 116 | 117 | Assert.Equal(largeExpected, actual); 118 | } 119 | 120 | [Fact] 121 | public async Task ReturnVoid() 122 | { 123 | await _client.InvokeAsync(x => x.ReturnVoid()); 124 | } 125 | 126 | [Theory, AutoData] 127 | public async Task DateTime(DateTime input, DateTime expected) 128 | { 129 | _serviceMock.Setup(x => x.DateTime(input)).Returns(expected); 130 | 131 | DateTime actual = await _client 132 | .InvokeAsync(x => x.DateTime(input)); 133 | 134 | Assert.Equal(expected, actual); 135 | } 136 | 137 | [Theory, AutoData] 138 | public async Task EnumType(DateTimeStyles input, DateTimeStyles expected) 139 | { 140 | _serviceMock.Setup(x => x.EnumType(input)).Returns(expected); 141 | 142 | DateTimeStyles actual = await _client 143 | .InvokeAsync(x => x.EnumType(input)); 144 | 145 | Assert.Equal(expected, actual); 146 | } 147 | 148 | [Theory, AutoData] 149 | public async Task ByteArray(byte[] input, byte[] expected) 150 | { 151 | _serviceMock.Setup(x => x.ByteArray(input)).Returns(expected); 152 | 153 | byte[] actual = await _client 154 | .InvokeAsync(x => x.ByteArray(input)); 155 | 156 | Assert.Equal(expected, actual); 157 | } 158 | 159 | [Theory, AutoData] 160 | public async Task GenericMethod(decimal input, decimal expected) 161 | { 162 | _serviceMock 163 | .Setup(x => x.GenericMethod(input)) 164 | .Returns(expected); 165 | 166 | decimal actual = await _client 167 | .InvokeAsync(x => x.GenericMethod(input)); 168 | 169 | Assert.Equal(expected, actual); 170 | } 171 | 172 | [Theory, AutoData] 173 | public async Task Abstraction(TestDto input, TestDto expected) 174 | { 175 | _serviceMock 176 | .Setup(x => x.Abstraction(It.Is(o => o.Value == input.Value))) 177 | .Returns(expected); 178 | 179 | ITestDto actual = await _client.InvokeAsync(x => x.Abstraction(input)); 180 | 181 | Assert.Equal(expected.Value, actual.Value); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/ErrorTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using JKang.IpcServiceFramework.Client; 3 | using JKang.IpcServiceFramework.Hosting; 4 | using JKang.IpcServiceFramework.NamedPipeTests.Fixtures; 5 | using JKang.IpcServiceFramework.Testing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Moq; 8 | using System; 9 | using System.Net; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | 13 | 14 | namespace JKang.IpcServiceFramework.NamedPipeTests 15 | { 16 | public class ErrorTest : IClassFixture> 17 | { 18 | private readonly Mock _serviceMock = new Mock(); 19 | private readonly IpcApplicationFactory _factory; 20 | 21 | public ErrorTest(IpcApplicationFactory factory) 22 | { 23 | _factory = factory 24 | .WithServiceImplementation(_ => _serviceMock.Object); 25 | } 26 | 27 | [Theory, AutoData] 28 | public async Task Exception_ThrowWithoutDetails(string pipeName, string details) 29 | { 30 | _serviceMock.Setup(x => x.ThrowException()) 31 | .Throws(new Exception(details)); 32 | 33 | IIpcClient client = _factory 34 | .WithIpcHostConfiguration(hostBuilder => 35 | { 36 | hostBuilder.AddNamedPipeEndpoint(options => 37 | { 38 | options.PipeName = pipeName; 39 | options.IncludeFailureDetailsInResponse = false; 40 | }); 41 | }) 42 | .CreateClient((name, services) => 43 | { 44 | services.AddNamedPipeIpcClient(name, (_, options) => 45 | { 46 | options.PipeName = pipeName; 47 | }); 48 | }); 49 | 50 | #if !DISABLE_DYNAMIC_CODE_GENERATION 51 | IpcFaultException actual = await Assert.ThrowsAsync(async () => 52 | { 53 | await client.InvokeAsync(x => x.ThrowException()); 54 | }); 55 | 56 | Assert.Null(actual.InnerException); 57 | #endif 58 | 59 | IpcFaultException actual2 = await Assert.ThrowsAsync(async () => 60 | { 61 | var request = TestHelpers.CreateIpcRequest("ThrowException"); 62 | await client.InvokeAsync(request); 63 | }); 64 | 65 | Assert.Null(actual2.InnerException); 66 | } 67 | 68 | [Theory, AutoData] 69 | public async Task Exception_ThrowWithDetails(string pipeName, string details) 70 | { 71 | _serviceMock.Setup(x => x.ThrowException()) 72 | .Throws(new Exception(details)); 73 | 74 | IIpcClient client = _factory 75 | .WithIpcHostConfiguration(hostBuilder => 76 | { 77 | hostBuilder.AddNamedPipeEndpoint(options => 78 | { 79 | options.PipeName = pipeName; 80 | options.IncludeFailureDetailsInResponse = true; 81 | }); 82 | }) 83 | .CreateClient((name, services) => 84 | { 85 | services.AddNamedPipeIpcClient(name, (_, options) => 86 | { 87 | options.PipeName = pipeName; 88 | }); 89 | }); 90 | 91 | #if !DISABLE_DYNAMIC_CODE_GENERATION 92 | IpcFaultException actual = await Assert.ThrowsAsync(async () => 93 | { 94 | await client.InvokeAsync(x => x.ThrowException()); 95 | }); 96 | 97 | Assert.NotNull(actual.InnerException); 98 | Assert.NotNull(actual.InnerException.InnerException); 99 | Assert.Equal(details, actual.InnerException.InnerException.Message); 100 | #endif 101 | 102 | IpcFaultException actual2 = await Assert.ThrowsAsync(async () => 103 | { 104 | var request = TestHelpers.CreateIpcRequest("ThrowException"); 105 | await client.InvokeAsync(request); 106 | }); 107 | 108 | Assert.NotNull(actual2.InnerException); 109 | Assert.NotNull(actual2.InnerException.InnerException); 110 | Assert.Equal(details, actual2.InnerException.InnerException.Message); 111 | } 112 | 113 | [Theory, AutoData] 114 | public async Task UnserializableInput_ThrowSerializationException(string pipeName) 115 | { 116 | IIpcClient client = _factory 117 | .WithIpcHostConfiguration(hostBuilder => 118 | { 119 | hostBuilder.AddNamedPipeEndpoint(pipeName); 120 | }) 121 | .CreateClient((name, services) => 122 | { 123 | services.AddNamedPipeIpcClient(name, pipeName); 124 | }); 125 | 126 | #if !DISABLE_DYNAMIC_CODE_GENERATION 127 | await Assert.ThrowsAnyAsync(async () => 128 | { 129 | await client.InvokeAsync(x => x.UnserializableInput(UnserializableObject.Create())); 130 | }); 131 | #endif 132 | 133 | await Assert.ThrowsAnyAsync(async () => 134 | { 135 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "UnserializableInput", UnserializableObject.Create()); 136 | await client.InvokeAsync(request); 137 | }); 138 | } 139 | 140 | [Theory, AutoData] 141 | public async Task UnserializableOutput_ThrowFaultException(string pipeName) 142 | { 143 | _serviceMock 144 | .Setup(x => x.UnserializableOutput()) 145 | .Returns(UnserializableObject.Create()); 146 | 147 | IIpcClient client = _factory 148 | .WithIpcHostConfiguration(hostBuilder => 149 | { 150 | hostBuilder.AddNamedPipeEndpoint(pipeName); 151 | }) 152 | .CreateClient((name, services) => 153 | { 154 | services.AddNamedPipeIpcClient(name, pipeName); 155 | }); 156 | 157 | #if !DISABLE_DYNAMIC_CODE_GENERATION 158 | IpcFaultException exception = await Assert.ThrowsAnyAsync(async () => 159 | { 160 | await client.InvokeAsync(x => x.UnserializableOutput()); 161 | }); 162 | 163 | Assert.Equal(IpcStatus.InternalServerError, exception.Status); 164 | #endif 165 | 166 | IpcFaultException exception2 = await Assert.ThrowsAnyAsync(async () => 167 | { 168 | var request = TestHelpers.CreateIpcRequest("UnserializableOutput"); 169 | await client.InvokeAsync(request); 170 | }); 171 | 172 | Assert.Equal(IpcStatus.InternalServerError, exception2.Status); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/IpcServiceFramework.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20913218-C740-42E9-9D17-CAD973B676D0}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | Directory.Build.props = Directory.Build.props 10 | IpcServiceFramework.snk = IpcServiceFramework.snk 11 | ..\build\version.yml = ..\build\version.yml 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Core.Tests", "JKang.IpcServiceFramework.Core.Tests\JKang.IpcServiceFramework.Core.Tests.csproj", "{1EC81913-883B-487C-A3FD-98A80EDE3225}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Hosting.NamedPipe", "JKang.IpcServiceFramework.Hosting.NamedPipe\JKang.IpcServiceFramework.Hosting.NamedPipe.csproj", "{42A743C1-2E99-4D3B-BA77-ACDF8309DC74}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NamedPipe", "NamedPipe", "{7ED117EC-C390-4D2E-94D3-C00010B63535}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Client.NamedPipe", "JKang.IpcServiceFramework.Client.NamedPipe\JKang.IpcServiceFramework.Client.NamedPipe.csproj", "{3BCFFC74-2722-43DC-B910-92A34997FA2B}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tcp", "Tcp", "{7A7B0FCA-ADE5-4C7A-9640-9E10821D251C}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Hosting.Tcp", "JKang.IpcServiceFramework.Hosting.Tcp\JKang.IpcServiceFramework.Hosting.Tcp.csproj", "{2783640E-5E3D-447A-BEC2-C1A74E09089C}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Client.Tcp", "JKang.IpcServiceFramework.Client.Tcp\JKang.IpcServiceFramework.Client.Tcp.csproj", "{E1EE30C2-EF31-47C9-B9FC-D55A94911C40}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Client", "JKang.IpcServiceFramework.Client\JKang.IpcServiceFramework.Client.csproj", "{46465A56-CF19-4403-A78C-9871BFD8B49E}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Core", "JKang.IpcServiceFramework.Core\JKang.IpcServiceFramework.Core.csproj", "{0713BDCA-E193-4333-988E-B8EE196BA0E6}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Hosting", "JKang.IpcServiceFramework.Hosting\JKang.IpcServiceFramework.Hosting.csproj", "{5C70C051-D841-4F4C-8C11-E34B74674187}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.Testing", "JKang.IpcServiceFramework.Testing\JKang.IpcServiceFramework.Testing.csproj", "{915000BD-9D84-4554-9A91-428923060EC5}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.TcpTests", "JKang.IpcServiceFramework.TcpTests\JKang.IpcServiceFramework.TcpTests.csproj", "{C6625102-AC1B-4D86-8BF8-B2096F701AC2}" 37 | EndProject 38 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JKang.IpcServiceFramework.NamedPipeTests", "JKang.IpcServiceFramework.NamedPipeTests\JKang.IpcServiceFramework.NamedPipeTests.csproj", "{62A037D0-4FC0-4EF8-A87F-ABC93209EB46}" 39 | EndProject 40 | Global 41 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 42 | Debug|Any CPU = Debug|Any CPU 43 | Release|Any CPU = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 46 | {1EC81913-883B-487C-A3FD-98A80EDE3225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {1EC81913-883B-487C-A3FD-98A80EDE3225}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {1EC81913-883B-487C-A3FD-98A80EDE3225}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {1EC81913-883B-487C-A3FD-98A80EDE3225}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {42A743C1-2E99-4D3B-BA77-ACDF8309DC74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {42A743C1-2E99-4D3B-BA77-ACDF8309DC74}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {42A743C1-2E99-4D3B-BA77-ACDF8309DC74}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {42A743C1-2E99-4D3B-BA77-ACDF8309DC74}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {3BCFFC74-2722-43DC-B910-92A34997FA2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {3BCFFC74-2722-43DC-B910-92A34997FA2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {3BCFFC74-2722-43DC-B910-92A34997FA2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {3BCFFC74-2722-43DC-B910-92A34997FA2B}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {2783640E-5E3D-447A-BEC2-C1A74E09089C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {2783640E-5E3D-447A-BEC2-C1A74E09089C}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {2783640E-5E3D-447A-BEC2-C1A74E09089C}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {2783640E-5E3D-447A-BEC2-C1A74E09089C}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {E1EE30C2-EF31-47C9-B9FC-D55A94911C40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {E1EE30C2-EF31-47C9-B9FC-D55A94911C40}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {E1EE30C2-EF31-47C9-B9FC-D55A94911C40}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {E1EE30C2-EF31-47C9-B9FC-D55A94911C40}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {46465A56-CF19-4403-A78C-9871BFD8B49E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {46465A56-CF19-4403-A78C-9871BFD8B49E}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {46465A56-CF19-4403-A78C-9871BFD8B49E}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {46465A56-CF19-4403-A78C-9871BFD8B49E}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {0713BDCA-E193-4333-988E-B8EE196BA0E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {0713BDCA-E193-4333-988E-B8EE196BA0E6}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {0713BDCA-E193-4333-988E-B8EE196BA0E6}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {0713BDCA-E193-4333-988E-B8EE196BA0E6}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {5C70C051-D841-4F4C-8C11-E34B74674187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {5C70C051-D841-4F4C-8C11-E34B74674187}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {5C70C051-D841-4F4C-8C11-E34B74674187}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {5C70C051-D841-4F4C-8C11-E34B74674187}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {915000BD-9D84-4554-9A91-428923060EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {915000BD-9D84-4554-9A91-428923060EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {915000BD-9D84-4554-9A91-428923060EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {915000BD-9D84-4554-9A91-428923060EC5}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {C6625102-AC1B-4D86-8BF8-B2096F701AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 83 | {C6625102-AC1B-4D86-8BF8-B2096F701AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU 84 | {C6625102-AC1B-4D86-8BF8-B2096F701AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {C6625102-AC1B-4D86-8BF8-B2096F701AC2}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {62A037D0-4FC0-4EF8-A87F-ABC93209EB46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {62A037D0-4FC0-4EF8-A87F-ABC93209EB46}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {62A037D0-4FC0-4EF8-A87F-ABC93209EB46}.Release|Any CPU.ActiveCfg = Release|Any CPU 89 | {62A037D0-4FC0-4EF8-A87F-ABC93209EB46}.Release|Any CPU.Build.0 = Release|Any CPU 90 | EndGlobalSection 91 | GlobalSection(SolutionProperties) = preSolution 92 | HideSolutionNode = FALSE 93 | EndGlobalSection 94 | GlobalSection(NestedProjects) = preSolution 95 | {42A743C1-2E99-4D3B-BA77-ACDF8309DC74} = {7ED117EC-C390-4D2E-94D3-C00010B63535} 96 | {3BCFFC74-2722-43DC-B910-92A34997FA2B} = {7ED117EC-C390-4D2E-94D3-C00010B63535} 97 | {2783640E-5E3D-447A-BEC2-C1A74E09089C} = {7A7B0FCA-ADE5-4C7A-9640-9E10821D251C} 98 | {E1EE30C2-EF31-47C9-B9FC-D55A94911C40} = {7A7B0FCA-ADE5-4C7A-9640-9E10821D251C} 99 | {C6625102-AC1B-4D86-8BF8-B2096F701AC2} = {7A7B0FCA-ADE5-4C7A-9640-9E10821D251C} 100 | {62A037D0-4FC0-4EF8-A87F-ABC93209EB46} = {7ED117EC-C390-4D2E-94D3-C00010B63535} 101 | EndGlobalSection 102 | GlobalSection(ExtensibilityGlobals) = postSolution 103 | SolutionGuid = {F87E0D46-F461-4E41-9A3B-64710A6DFB2F} 104 | EndGlobalSection 105 | EndGlobal 106 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Core.Tests/DefaultValueConverterTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using JKang.IpcServiceFramework.Core.Tests.Fixtures; 3 | using JKang.IpcServiceFramework.Services; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using Xunit; 10 | 11 | namespace JKang.IpcServiceFramework.Core.Tests 12 | { 13 | public class DefaultValueConverterTest 14 | { 15 | private readonly DefaultValueConverter _sut = new DefaultValueConverter(); 16 | 17 | [Theory, AutoData] 18 | public void TryConvert_FloatToDouble(float expected) 19 | { 20 | bool succeed = _sut.TryConvert(expected, typeof(double), out object actual); 21 | 22 | Assert.True(succeed); 23 | Assert.IsType(actual); 24 | Assert.Equal((double)expected, actual); 25 | } 26 | 27 | [Theory, AutoData] 28 | public void TryConvert_Int32ToInt64(int expected) 29 | { 30 | bool succeed = _sut.TryConvert(expected, typeof(long), out object actual); 31 | 32 | Assert.True(succeed); 33 | Assert.IsType(actual); 34 | Assert.Equal((long)expected, actual); 35 | } 36 | 37 | [Theory, AutoData] 38 | public void TryConvert_SameType(DateTime expected) 39 | { 40 | bool succeed = _sut.TryConvert(expected, typeof(DateTime), out object actual); 41 | 42 | Assert.True(succeed); 43 | Assert.IsType(actual); 44 | Assert.Equal(expected, actual); 45 | } 46 | 47 | [Theory, AutoData] 48 | public void TryConvert_JObjectToComplexType(ComplexType expected) 49 | { 50 | object jObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(expected)); 51 | 52 | bool succeed = _sut.TryConvert(jObj, typeof(ComplexType), out object actual); 53 | 54 | Assert.True(succeed); 55 | Assert.IsType(actual); 56 | Assert.Equal(expected.Int32Value, ((ComplexType)actual).Int32Value); 57 | Assert.Equal(expected.StringValue, ((ComplexType)actual).StringValue); 58 | } 59 | 60 | [Theory, AutoData] 61 | public void TryConvert_Int32Array(int[] expected) 62 | { 63 | object jObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(expected)); 64 | 65 | bool succeed = _sut.TryConvert(jObj, typeof(int[]), out object actual); 66 | 67 | Assert.True(succeed); 68 | Assert.IsType(actual); 69 | Assert.Equal(expected, actual as int[]); 70 | } 71 | 72 | [Theory, AutoData] 73 | public void TryConvert_Int32List(List expected) 74 | { 75 | object jObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(expected)); 76 | 77 | bool succeed = _sut.TryConvert(jObj, typeof(List), out object actual); 78 | 79 | Assert.True(succeed); 80 | Assert.IsType>(actual); 81 | Assert.Equal(expected, actual as List); 82 | } 83 | 84 | [Theory, AutoData] 85 | public void TryConvert_ComplexTypeArray(ComplexType[] expected) 86 | { 87 | object jObj = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(expected)); 88 | 89 | bool succeed = _sut.TryConvert(jObj, typeof(ComplexType[]), out object actual); 90 | 91 | Assert.True(succeed); 92 | Assert.IsType(actual); 93 | var actualArray = actual as ComplexType[]; 94 | Assert.Equal(expected.Length, actualArray.Length); 95 | for (int i = 0; i < expected.Length; i++) 96 | { 97 | Assert.NotNull(actualArray[i]); 98 | Assert.Equal(expected[i].Int32Value, actualArray[i].Int32Value); 99 | Assert.Equal(expected[i].StringValue, actualArray[i].StringValue); 100 | } 101 | } 102 | 103 | [Fact] 104 | public void TryConvert_DerivedTypeToBaseType() 105 | { 106 | bool succeed = _sut.TryConvert(new ComplexType(), typeof(IComplexType), out object actual); 107 | 108 | Assert.True(succeed); 109 | Assert.IsType(actual); 110 | } 111 | 112 | [Theory, AutoData] 113 | public void TryConvert_StringToEnum(EnumType expected) 114 | { 115 | bool succeed = _sut.TryConvert(expected.ToString(), typeof(EnumType), out object actual); 116 | 117 | Assert.True(succeed); 118 | Assert.IsType(actual); 119 | Assert.Equal(expected, actual); 120 | } 121 | 122 | [Theory, AutoData] 123 | public void TryConvert_Int32ToEnum(EnumType expected) 124 | { 125 | bool succeed = _sut.TryConvert((int)expected, typeof(EnumType), out object actual); 126 | 127 | Assert.True(succeed); 128 | Assert.IsType(actual); 129 | Assert.Equal(expected, actual); 130 | } 131 | 132 | [Theory, AutoData] 133 | public void TryConvert_StringToGuid(Guid expected) 134 | { 135 | bool succeed = _sut.TryConvert(expected.ToString(), typeof(Guid), out object actual); 136 | 137 | Assert.True(succeed); 138 | Assert.IsType(actual); 139 | Assert.Equal(expected, actual); 140 | } 141 | 142 | private T ParseTestData(string valueData) 143 | { 144 | if (typeof(T).GetMember(valueData).FirstOrDefault() is MemberInfo member) 145 | { 146 | if (member is FieldInfo field) 147 | return (T)field.GetValue(null); 148 | else if (member is PropertyInfo property) 149 | return (T)property.GetValue(null); 150 | else if (member is MethodInfo method) 151 | return (T)method.Invoke(null, null); 152 | } 153 | 154 | var parseMethod = typeof(T).GetMethod("Parse", new[] { typeof(string) }); 155 | 156 | return (T)parseMethod.Invoke(null, new[] { valueData }); 157 | } 158 | 159 | private void PerformRoundTripTest(T value, Action assertAreEqual = null) 160 | { 161 | // Act 162 | bool succeed = _sut.TryConvert(value, typeof(string), out object intermediate); 163 | bool succeed2 = _sut.TryConvert(intermediate, typeof(T), out object final); 164 | 165 | // Assert 166 | Assert.True(succeed); 167 | Assert.True(succeed2); 168 | 169 | Assert.IsType(final); 170 | 171 | if (assertAreEqual != null) 172 | assertAreEqual(value, (T)final); 173 | else 174 | Assert.Equal(value, final); 175 | } 176 | 177 | [Theory] 178 | [InlineData(nameof(Guid.Empty))] 179 | [InlineData(nameof(Guid.NewGuid))] 180 | public void TryConvert_RoundTripGuid(string valueData) 181 | { 182 | PerformRoundTripTest(ParseTestData(valueData)); 183 | } 184 | 185 | [Theory] 186 | [InlineData(nameof(TimeSpan.Zero))] 187 | [InlineData(nameof(TimeSpan.MinValue))] 188 | [InlineData(nameof(TimeSpan.MaxValue))] 189 | [InlineData("-00:00:05.9167374")] 190 | public void TryConvert_RoundTripTimeSpan(string valueData) 191 | { 192 | PerformRoundTripTest(ParseTestData(valueData)); 193 | } 194 | 195 | [Theory] 196 | [InlineData(nameof(DateTime.Now))] 197 | [InlineData(nameof(DateTime.Today))] 198 | [InlineData(nameof(DateTime.MinValue))] 199 | [InlineData(nameof(DateTime.MaxValue))] 200 | [InlineData("2020-02-05 3:10:27 PM")] 201 | public void TryConvert_RoundTripDateTime(string valueData) 202 | { 203 | PerformRoundTripTest(ParseTestData(valueData), assertAreEqual: (x, y) => Assert.Equal(DateTime.SpecifyKind(x, DateTimeKind.Unspecified), DateTime.SpecifyKind(y, DateTimeKind.Unspecified))); 204 | } 205 | 206 | public interface IComplexType 207 | { 208 | int Int32Value { get; } 209 | string StringValue { get; } 210 | } 211 | 212 | public class ComplexType : IComplexType 213 | { 214 | public int Int32Value { get; set; } 215 | public string StringValue { get; set; } 216 | } 217 | 218 | public enum EnumType 219 | { 220 | FirstOption, 221 | SecondOption 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.NamedPipeTests/ContractTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using JKang.IpcServiceFramework.Client; 3 | using JKang.IpcServiceFramework.Hosting; 4 | using JKang.IpcServiceFramework.NamedPipeTests.Fixtures; 5 | using JKang.IpcServiceFramework.Testing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Moq; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Globalization; 11 | using System.Linq; 12 | using System.Numerics; 13 | using System.Threading.Tasks; 14 | using Xunit; 15 | 16 | namespace JKang.IpcServiceFramework.NamedPipeTests 17 | { 18 | /// 19 | /// Validates the IPC pipeline is working end-to-end for a variety of method types. 20 | /// Tests both dynamically generated IpcRequests (via DispatchProxy) and statically generated ones. 21 | /// Tests using full parameter types (UseSimpleTypeNameAssemblyFormatHandling == false). 22 | /// 23 | /// 24 | public class ContractTest : IClassFixture> 25 | { 26 | private readonly Mock _serviceMock = new Mock(); 27 | private readonly IIpcClient _client; 28 | 29 | public ContractTest(IpcApplicationFactory factory) 30 | { 31 | string pipeName = Guid.NewGuid().ToString(); 32 | _client = factory 33 | .WithServiceImplementation(_ => _serviceMock.Object) 34 | .WithIpcHostConfiguration(hostBuilder => 35 | { 36 | hostBuilder.AddNamedPipeEndpoint(options => 37 | { 38 | options.PipeName = pipeName; 39 | options.IncludeFailureDetailsInResponse = true; 40 | }); 41 | }) 42 | .CreateClient((name, services) => 43 | { 44 | services.AddNamedPipeIpcClient(name, pipeName); 45 | }); 46 | } 47 | 48 | [Theory, AutoData] 49 | public async Task PrimitiveTypes(bool a, byte b, sbyte c, char d, decimal e, double f, float g, int h, uint i, 50 | long j, ulong k, short l, ushort m, int expected) 51 | { 52 | _serviceMock 53 | .Setup(x => x.PrimitiveTypes(a, b, c, d, e, f, g, h, i, j, k, l, m)) 54 | .Returns(expected); 55 | 56 | #if !DISABLE_DYNAMIC_CODE_GENERATION 57 | int actual = await _client 58 | .InvokeAsync(x => x.PrimitiveTypes(a, b, c, d, e, f, g, h, i, j, k, l, m)); 59 | 60 | Assert.Equal(expected, actual); 61 | #endif 62 | 63 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "PrimitiveTypes", a, b, c, d, e, f, g, h, i, j, k, l, m); 64 | int actual2 = await _client.InvokeAsync(request); 65 | 66 | Assert.Equal(expected, actual2); 67 | } 68 | 69 | [Theory, AutoData] 70 | public async Task StringType(string input, string expected) 71 | { 72 | _serviceMock 73 | .Setup(x => x.StringType(input)) 74 | .Returns(expected); 75 | 76 | #if !DISABLE_DYNAMIC_CODE_GENERATION 77 | string actual = await _client 78 | .InvokeAsync(x => x.StringType(input)); 79 | 80 | Assert.Equal(expected, actual); 81 | #endif 82 | 83 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "StringType", input); 84 | string actual2 = await _client.InvokeAsync(request); 85 | 86 | Assert.Equal(expected, actual2); 87 | } 88 | 89 | [Theory, AutoData] 90 | public async Task ComplexType(Complex input, Complex expected) 91 | { 92 | _serviceMock.Setup(x => x.ComplexType(input)).Returns(expected); 93 | 94 | #if !DISABLE_DYNAMIC_CODE_GENERATION 95 | Complex actual = await _client 96 | .InvokeAsync(x => x.ComplexType(input)); 97 | 98 | Assert.Equal(expected, actual); 99 | #endif 100 | 101 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "ComplexType", input); 102 | var actual2 = await _client.InvokeAsync(request); 103 | 104 | Assert.Equal(expected, actual2); 105 | 106 | } 107 | 108 | [Theory, AutoData] 109 | public async Task ComplexTypeArray(IEnumerable input, IEnumerable expected) 110 | { 111 | _serviceMock 112 | .Setup(x => x.ComplexTypeArray(input)) 113 | .Returns(expected); 114 | 115 | #if !DISABLE_DYNAMIC_CODE_GENERATION 116 | IEnumerable actual = await _client 117 | .InvokeAsync(x => x.ComplexTypeArray(input)); 118 | 119 | Assert.Equal(expected, actual); 120 | #endif 121 | 122 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "ComplexTypeArray", input); 123 | var actual2 = await _client.InvokeAsync>(request); 124 | 125 | Assert.Equal(expected, actual2); 126 | } 127 | 128 | [Theory, AutoData] 129 | public async Task LargeComplexTypeArray(Complex input, Complex expected) 130 | { 131 | IEnumerable largeInput = Enumerable.Repeat(input, 1000); 132 | IEnumerable largeExpected = Enumerable.Repeat(expected, 100); 133 | 134 | _serviceMock 135 | .Setup(x => x.ComplexTypeArray(largeInput)) 136 | .Returns(largeExpected); 137 | 138 | #if !DISABLE_DYNAMIC_CODE_GENERATION 139 | IEnumerable actual = await _client 140 | .InvokeAsync(x => x.ComplexTypeArray(largeInput)); 141 | 142 | Assert.Equal(largeExpected, actual); 143 | #endif 144 | 145 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "ComplexTypeArray", largeInput); 146 | var actual2 = await _client.InvokeAsync>(request); 147 | 148 | Assert.Equal(largeExpected, actual2); 149 | } 150 | 151 | [Fact] 152 | public async Task ReturnVoid() 153 | { 154 | #if !DISABLE_DYNAMIC_CODE_GENERATION 155 | await _client.InvokeAsync(x => x.ReturnVoid()); 156 | #endif 157 | 158 | await _client.InvokeAsync(TestHelpers.CreateIpcRequest("ReturnVoid")); 159 | } 160 | 161 | [Theory, AutoData] 162 | public async Task DateTime(DateTime input, DateTime expected) 163 | { 164 | _serviceMock.Setup(x => x.DateTime(input)).Returns(expected); 165 | 166 | #if !DISABLE_DYNAMIC_CODE_GENERATION 167 | DateTime actual = await _client 168 | .InvokeAsync(x => x.DateTime(input)); 169 | 170 | Assert.Equal(expected, actual); 171 | #endif 172 | 173 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "DateTime", input); 174 | DateTime actual2 = await _client.InvokeAsync(request); 175 | 176 | Assert.Equal(expected, actual2); 177 | } 178 | 179 | [Theory, AutoData] 180 | public async Task EnumType(DateTimeStyles input, DateTimeStyles expected) 181 | { 182 | _serviceMock.Setup(x => x.EnumType(input)).Returns(expected); 183 | 184 | #if !DISABLE_DYNAMIC_CODE_GENERATION 185 | DateTimeStyles actual = await _client 186 | .InvokeAsync(x => x.EnumType(input)); 187 | 188 | Assert.Equal(expected, actual); 189 | #endif 190 | 191 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "EnumType", input); 192 | DateTimeStyles actual2 = await _client.InvokeAsync(request); 193 | 194 | Assert.Equal(expected, actual2); 195 | } 196 | 197 | [Theory, AutoData] 198 | public async Task ByteArray(byte[] input, byte[] expected) 199 | { 200 | _serviceMock.Setup(x => x.ByteArray(input)).Returns(expected); 201 | 202 | #if !DISABLE_DYNAMIC_CODE_GENERATION 203 | byte[] actual = await _client 204 | .InvokeAsync(x => x.ByteArray(input)); 205 | 206 | Assert.Equal(expected, actual); 207 | #endif 208 | 209 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "ByteArray", input); 210 | byte[] actual2 = await _client.InvokeAsync(request); 211 | 212 | Assert.Equal(expected, actual2); 213 | } 214 | 215 | [Theory, AutoData] 216 | public async Task GenericMethod(decimal input, decimal expected) 217 | { 218 | _serviceMock 219 | .Setup(x => x.GenericMethod(input)) 220 | .Returns(expected); 221 | 222 | #if !DISABLE_DYNAMIC_CODE_GENERATION 223 | decimal actual = await _client 224 | .InvokeAsync(x => x.GenericMethod(input)); 225 | 226 | Assert.Equal(expected, actual); 227 | #endif 228 | } 229 | 230 | [Theory, AutoData] 231 | public async Task AsyncMethod(int expected) 232 | { 233 | _serviceMock 234 | .Setup(x => x.AsyncMethod()) 235 | .ReturnsAsync(expected); 236 | 237 | #if !DISABLE_DYNAMIC_CODE_GENERATION 238 | int actual = await _client 239 | .InvokeAsync(x => x.AsyncMethod()); 240 | 241 | Assert.Equal(expected, actual); 242 | #endif 243 | 244 | var request = TestHelpers.CreateIpcRequest("AsyncMethod"); 245 | int actual2 = await _client.InvokeAsync(request); 246 | 247 | Assert.Equal(expected, actual2); 248 | } 249 | 250 | [Theory, AutoData] 251 | public async Task Abstraction(TestDto input, TestDto expected) 252 | { 253 | _serviceMock 254 | .Setup(x => x.Abstraction(It.Is(o => o.Value == input.Value))) 255 | .Returns(expected); 256 | 257 | #if !DISABLE_DYNAMIC_CODE_GENERATION 258 | ITestDto actual = await _client.InvokeAsync(x => x.Abstraction(input)); 259 | 260 | Assert.Equal(expected.Value, actual.Value); 261 | #endif 262 | 263 | var request = TestHelpers.CreateIpcRequest(typeof(ITestService), "Abstraction", input); 264 | ITestDto actual2 = await _client.InvokeAsync(request); 265 | 266 | Assert.Equal(expected.Value, actual2.Value); 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Client/IpcClient.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.IO; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace JKang.IpcServiceFramework.Client 11 | { 12 | public abstract class IpcClient : IIpcClient 13 | where TInterface : class 14 | { 15 | private readonly IpcClientOptions _options; 16 | 17 | protected IpcClient( 18 | string name, 19 | IpcClientOptions options) 20 | { 21 | Name = name; 22 | _options = options; 23 | } 24 | 25 | public string Name { get; } 26 | 27 | #if !DISABLE_DYNAMIC_CODE_GENERATION 28 | /// If unable to serialize request 29 | /// If communication is broken 30 | /// If error occurred in server 31 | public async Task InvokeAsync(Expression> exp, 32 | CancellationToken cancellationToken = default) 33 | { 34 | var request = GetRequest(exp, DispatchProxy.Create()); 35 | var response = await GetResponseAsync(request, cancellationToken).ConfigureAwait(false); 36 | 37 | if (!response.Succeed()) 38 | { 39 | throw response.CreateFaultException(); 40 | } 41 | } 42 | 43 | /// If unable to serialize request 44 | /// If communication is broken 45 | /// If error occurred in server 46 | public async Task InvokeAsync(Expression> exp, 47 | CancellationToken cancellationToken = default) 48 | { 49 | var request = GetRequest(exp, DispatchProxy.Create>()); 50 | var response = await GetResponseAsync(request, cancellationToken).ConfigureAwait(false); 51 | 52 | if (!response.Succeed()) 53 | { 54 | throw response.CreateFaultException(); 55 | } 56 | 57 | if (!_options.ValueConverter.TryConvert(response.Data, typeof(TResult), out object @return)) 58 | { 59 | throw new IpcSerializationException($"Unable to convert returned value to '{typeof(TResult).Name}'."); 60 | } 61 | 62 | return (TResult)@return; 63 | } 64 | 65 | /// If unable to serialize request 66 | /// If communication is broken 67 | /// If error occurred in server 68 | public async Task InvokeAsync(Expression> exp, 69 | CancellationToken cancellationToken = default) 70 | { 71 | var request = GetRequest(exp, DispatchProxy.Create>()); 72 | var response = await GetResponseAsync(request, cancellationToken).ConfigureAwait(false); 73 | 74 | if (!response.Succeed()) 75 | { 76 | throw response.CreateFaultException(); 77 | } 78 | } 79 | 80 | /// If unable to serialize request 81 | /// If communication is broken 82 | /// If error occurred in server 83 | public async Task InvokeAsync(Expression>> exp, 84 | CancellationToken cancellationToken = default) 85 | { 86 | var request = GetRequest(exp, DispatchProxy.Create>>()); 87 | var response = await GetResponseAsync(request, cancellationToken).ConfigureAwait(false); 88 | 89 | if (!response.Succeed()) 90 | { 91 | throw response.CreateFaultException(); 92 | } 93 | 94 | if (_options.ValueConverter.TryConvert(response.Data, typeof(TResult), out object @return)) 95 | { 96 | return (TResult)@return; 97 | } 98 | else 99 | { 100 | throw new IpcSerializationException($"Unable to convert returned value to '{typeof(TResult).Name}'."); 101 | } 102 | } 103 | #endif 104 | 105 | public async Task InvokeAsync(IpcRequest request, CancellationToken cancellationToken = default) 106 | { 107 | var response = await GetResponseAsync(request, cancellationToken).ConfigureAwait(false); 108 | 109 | if (!response.Succeed()) 110 | { 111 | throw response.CreateFaultException(); 112 | } 113 | 114 | if (_options.ValueConverter.TryConvert(response.Data, typeof(TResult), out object @return)) 115 | { 116 | return (TResult)@return; 117 | } 118 | else 119 | { 120 | throw new IpcSerializationException($"Unable to convert returned value to '{typeof(TResult).Name}'."); 121 | } 122 | } 123 | 124 | public async Task InvokeAsync(IpcRequest request, CancellationToken cancellationToken = default) 125 | { 126 | var response = await GetResponseAsync(request, cancellationToken).ConfigureAwait(false); 127 | 128 | if (!response.Succeed()) 129 | { 130 | throw response.CreateFaultException(); 131 | } 132 | } 133 | 134 | #if !DISABLE_DYNAMIC_CODE_GENERATION 135 | private IpcRequest GetRequest(Expression exp, TInterface proxy) 136 | { 137 | if (!(exp is LambdaExpression lambdaExp)) 138 | { 139 | throw new ArgumentException("Only support lambda expression, ex: x => x.GetData(a, b)"); 140 | } 141 | 142 | if (!(lambdaExp.Body is MethodCallExpression methodCallExp)) 143 | { 144 | throw new ArgumentException("Only support calling method, ex: x => x.GetData(a, b)"); 145 | } 146 | 147 | Delegate @delegate = lambdaExp.Compile(); 148 | @delegate.DynamicInvoke(proxy); 149 | 150 | if (_options.UseSimpleTypeNameAssemblyFormatHandling) 151 | { 152 | IpcRequestParameterType[] paramByName = null; 153 | IpcRequestParameterType[] genericByName = null; 154 | 155 | var parameterTypes = (proxy as IpcProxy).LastInvocation.Method.GetParameters().Select(p => p.ParameterType); 156 | 157 | if (parameterTypes.Any()) 158 | { 159 | paramByName = new IpcRequestParameterType[parameterTypes.Count()]; 160 | int i = 0; 161 | foreach (var type in parameterTypes) 162 | { 163 | paramByName[i++] = new IpcRequestParameterType(type); 164 | } 165 | } 166 | 167 | var genericTypes = (proxy as IpcProxy).LastInvocation.Method.GetGenericArguments(); 168 | 169 | if (genericTypes.Length > 0) 170 | { 171 | genericByName = new IpcRequestParameterType[genericTypes.Count()]; 172 | int i = 0; 173 | foreach (var type in genericTypes) 174 | { 175 | genericByName[i++] = new IpcRequestParameterType(type); 176 | } 177 | } 178 | 179 | 180 | return new IpcRequest 181 | { 182 | MethodName = (proxy as IpcProxy).LastInvocation.Method.Name, 183 | Parameters = (proxy as IpcProxy).LastInvocation.Arguments, 184 | 185 | ParameterTypesByName = paramByName, 186 | GenericArgumentsByName = genericByName 187 | }; 188 | } 189 | else 190 | { 191 | return new IpcRequest 192 | { 193 | MethodName = (proxy as IpcProxy).LastInvocation.Method.Name, 194 | Parameters = (proxy as IpcProxy).LastInvocation.Arguments, 195 | 196 | ParameterTypes = (proxy as IpcProxy).LastInvocation.Method.GetParameters() 197 | .Select(p => p.ParameterType) 198 | .ToArray(), 199 | 200 | 201 | GenericArguments = (proxy as IpcProxy).LastInvocation.Method.GetGenericArguments(), 202 | }; 203 | } 204 | } 205 | #endif 206 | 207 | protected abstract Task ConnectToServerAsync(CancellationToken cancellationToken); 208 | 209 | private async Task GetResponseAsync(IpcRequest request, CancellationToken cancellationToken) 210 | { 211 | using (IpcStreamWrapper client = await ConnectToServerAsync(cancellationToken).ConfigureAwait(false)) 212 | using (Stream client2 = _options.StreamTranslator == null ? client.Stream : _options.StreamTranslator(client.Stream)) 213 | using (var writer = new IpcWriter(client2, _options.Serializer, leaveOpen: true)) 214 | using (var reader = new IpcReader(client2, _options.Serializer, leaveOpen: true)) 215 | { 216 | // send request 217 | await writer.WriteAsync(request, cancellationToken).ConfigureAwait(false); 218 | 219 | // receive response 220 | return await reader.ReadIpcResponseAsync(cancellationToken).ConfigureAwait(false); 221 | } 222 | } 223 | 224 | #if !DISABLE_DYNAMIC_CODE_GENERATION 225 | public class IpcProxy : DispatchProxy 226 | { 227 | public Invocation LastInvocation { get; protected set; } 228 | 229 | protected override object Invoke(MethodInfo targetMethod, object[] args) 230 | { 231 | LastInvocation = new Invocation(targetMethod, args); 232 | return null; 233 | } 234 | 235 | public class Invocation 236 | { 237 | public Invocation(MethodInfo method, object[] args) 238 | { 239 | Method = method; 240 | Arguments = args; 241 | } 242 | 243 | public MethodInfo Method { get; } 244 | public object[] Arguments { get; } 245 | } 246 | } 247 | 248 | public class IpcProxy : IpcProxy 249 | { 250 | protected override object Invoke(MethodInfo targetMethod, object[] args) 251 | { 252 | LastInvocation = new Invocation(targetMethod, args); 253 | return default(TResult); 254 | } 255 | } 256 | #endif 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/JKang.IpcServiceFramework.Hosting/IpcEndpoint.cs: -------------------------------------------------------------------------------- 1 | using JKang.IpcServiceFramework.IO; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace JKang.IpcServiceFramework.Hosting 13 | { 14 | public abstract class IpcEndpoint : IIpcEndpoint 15 | where TContract : class 16 | { 17 | private readonly IpcEndpointOptions _options; 18 | private readonly IServiceProvider _serviceProvider; 19 | private readonly ILogger _logger; 20 | private readonly SemaphoreSlim _semaphore; 21 | 22 | protected IpcEndpoint( 23 | IpcEndpointOptions options, 24 | IServiceProvider serviceProvider, 25 | ILogger logger) 26 | { 27 | _options = options ?? throw new ArgumentNullException(nameof(options)); 28 | _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); 29 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 30 | _semaphore = new SemaphoreSlim(options.MaxConcurrentCalls); 31 | } 32 | 33 | public async Task ExecuteAsync(CancellationToken stoppingToken) 34 | { 35 | var factory = new TaskFactory(TaskScheduler.Default); 36 | while (!stoppingToken.IsCancellationRequested) 37 | { 38 | await _semaphore.WaitAsync(stoppingToken).ConfigureAwait(false); 39 | 40 | #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed 41 | WaitAndProcessAsync(ProcessAsync, stoppingToken).ContinueWith(task => 42 | { 43 | if (task.IsFaulted) 44 | { 45 | _logger.LogError(task.Exception, "Error occurred"); 46 | } 47 | return _semaphore.Release(); 48 | }, TaskScheduler.Default); 49 | #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed 50 | } 51 | } 52 | 53 | protected abstract Task WaitAndProcessAsync(Func process, CancellationToken stoppingToken); 54 | 55 | private async Task ProcessAsync(Stream server, CancellationToken stoppingToken) 56 | { 57 | if (stoppingToken.IsCancellationRequested) 58 | { 59 | return; 60 | } 61 | 62 | if (_options.StreamTranslator != null) 63 | { 64 | server = _options.StreamTranslator(server); 65 | } 66 | 67 | using (var writer = new IpcWriter(server, _options.Serializer, leaveOpen: true)) 68 | using (var reader = new IpcReader(server, _options.Serializer, leaveOpen: true)) 69 | using (IDisposable loggingScope = _logger.BeginScope(new Dictionary 70 | { 71 | { "threadId", Thread.CurrentThread.ManagedThreadId } 72 | })) 73 | { 74 | try 75 | { 76 | IpcRequest request; 77 | try 78 | { 79 | _logger.LogDebug($"Client connected, reading request..."); 80 | request = await reader.ReadIpcRequestAsync(stoppingToken).ConfigureAwait(false); 81 | } 82 | catch (IpcSerializationException ex) 83 | { 84 | throw new IpcFaultException(IpcStatus.BadRequest, "Failed to deserialize request.", ex); 85 | } 86 | 87 | stoppingToken.ThrowIfCancellationRequested(); 88 | 89 | IpcResponse response; 90 | try 91 | { 92 | _logger.LogDebug($"Request received, invoking '{request.MethodName}'..."); 93 | using (IServiceScope scope = _serviceProvider.CreateScope()) 94 | { 95 | response = await GetResponseAsync(request, scope).ConfigureAwait(false); 96 | } 97 | } 98 | catch (Exception ex) when (!(ex is IpcException)) 99 | { 100 | throw new IpcFaultException(IpcStatus.InternalServerError, 101 | "Unexpected exception raised from user code", ex); 102 | } 103 | 104 | stoppingToken.ThrowIfCancellationRequested(); 105 | 106 | try 107 | { 108 | _logger.LogDebug($"Sending response..."); 109 | await writer.WriteAsync(response, stoppingToken).ConfigureAwait(false); 110 | } 111 | catch (IpcSerializationException ex) 112 | { 113 | throw new IpcFaultException(IpcStatus.InternalServerError, 114 | "Failed to serialize response.", ex); 115 | } 116 | 117 | _logger.LogDebug($"Process finished."); 118 | } 119 | catch (IpcCommunicationException ex) 120 | { 121 | _logger.LogError(ex, "Communication error occurred."); 122 | // if communication error occurred, client will probably not receive any response 123 | } 124 | catch (OperationCanceledException ex) 125 | { 126 | _logger.LogWarning(ex, "IPC request process cancelled"); 127 | IpcResponse response = _options.IncludeFailureDetailsInResponse 128 | ? IpcResponse.InternalServerError("IPC request process cancelled") 129 | : IpcResponse.InternalServerError(); 130 | await writer.WriteAsync(response, stoppingToken).ConfigureAwait(false); 131 | } 132 | catch (IpcFaultException ex) 133 | { 134 | _logger.LogError(ex, "Failed to process IPC request."); 135 | IpcResponse response; 136 | switch (ex.Status) 137 | { 138 | case IpcStatus.BadRequest: 139 | response = _options.IncludeFailureDetailsInResponse 140 | ? IpcResponse.BadRequest(ex.Message, ex.InnerException) 141 | : IpcResponse.BadRequest(); 142 | break; 143 | default: 144 | response = _options.IncludeFailureDetailsInResponse 145 | ? IpcResponse.InternalServerError(ex.Message, ex.InnerException) 146 | : IpcResponse.InternalServerError(); 147 | break; 148 | } 149 | await writer.WriteAsync(response, stoppingToken).ConfigureAwait(false); 150 | } 151 | } 152 | } 153 | 154 | private async Task GetResponseAsync(IpcRequest request, IServiceScope scope) 155 | { 156 | object service = scope.ServiceProvider.GetService(); 157 | if (service == null) 158 | { 159 | throw new IpcFaultException(IpcStatus.BadRequest, 160 | $"No implementation of interface '{typeof(TContract).FullName}' found."); 161 | } 162 | 163 | MethodInfo method = GetUnambiguousMethod(request, service); 164 | 165 | if (method == null) 166 | { 167 | throw new IpcFaultException(IpcStatus.BadRequest, 168 | $"Method '{request.MethodName}' not found in interface '{typeof(TContract).FullName}'."); 169 | } 170 | 171 | ParameterInfo[] paramInfos = method.GetParameters(); 172 | object[] requestParameters = request.Parameters?.ToArray() ?? Array.Empty(); 173 | if (paramInfos.Length != requestParameters.Length) 174 | { 175 | throw new IpcFaultException(IpcStatus.BadRequest, 176 | $"Method '{request.MethodName}' expects {paramInfos.Length} parameters."); 177 | } 178 | 179 | Type[] genericArguments = method.GetGenericArguments(); 180 | Type[] requestGenericArguments; 181 | 182 | if (request.GenericArguments != null) 183 | { 184 | // Generic arguments passed by Type 185 | requestGenericArguments = request.GenericArguments.ToArray(); 186 | } 187 | else if (request.GenericArgumentsByName != null) 188 | { 189 | // Generic arguments passed by name 190 | requestGenericArguments = new Type[request.GenericArgumentsByName.Count()]; 191 | int i = 0; 192 | foreach (var pair in request.GenericArgumentsByName) 193 | { 194 | var a = Assembly.Load(pair.AssemblyName); 195 | requestGenericArguments[i++] = a.GetType(pair.ParameterType); 196 | } 197 | } 198 | else 199 | { 200 | requestGenericArguments = Array.Empty(); 201 | } 202 | 203 | if (genericArguments.Length != requestGenericArguments.Length) 204 | { 205 | throw new IpcFaultException(IpcStatus.BadRequest, 206 | $"Generic arguments mismatch."); 207 | } 208 | 209 | object[] args = new object[paramInfos.Length]; 210 | for (int i = 0; i < args.Length; i++) 211 | { 212 | object origValue = requestParameters[i]; 213 | Type destType = paramInfos[i].ParameterType; 214 | if (destType.IsGenericParameter) 215 | { 216 | destType = requestGenericArguments[destType.GenericParameterPosition]; 217 | } 218 | 219 | if (_options.ValueConverter.TryConvert(origValue, destType, out object arg)) 220 | { 221 | args[i] = arg; 222 | } 223 | else 224 | { 225 | throw new IpcFaultException(IpcStatus.BadRequest, 226 | $"Cannot convert value of parameter '{paramInfos[i].Name}' ({origValue}) from {origValue.GetType().Name} to {destType.Name}."); 227 | } 228 | } 229 | 230 | if (method.IsGenericMethod) 231 | { 232 | method = method.MakeGenericMethod(requestGenericArguments); 233 | } 234 | 235 | object @return = method.Invoke(service, args); 236 | 237 | if (@return is Task task) 238 | { 239 | await task.ConfigureAwait(false); 240 | 241 | PropertyInfo resultProperty = @return.GetType().GetProperty("Result"); 242 | return IpcResponse.Success(resultProperty?.GetValue(@return)); 243 | } 244 | else 245 | { 246 | return IpcResponse.Success(@return); 247 | } 248 | } 249 | 250 | private static MethodInfo GetUnambiguousMethod(IpcRequest request, object service) 251 | { 252 | if (request is null) 253 | { 254 | throw new ArgumentNullException(nameof(request)); 255 | } 256 | 257 | if (service is null) 258 | { 259 | throw new ArgumentNullException(nameof(service)); 260 | } 261 | 262 | MethodInfo method = null; // disambiguate - can't just call as before with generics - MethodInfo method = service.GetType().GetMethod(request.MethodName); 263 | 264 | Type[] types = service.GetType().GetInterfaces(); 265 | 266 | IEnumerable allMethods = types.SelectMany(t => t.GetMethods()); 267 | 268 | var serviceMethods = allMethods.Where(t => t.Name == request.MethodName).ToList(); 269 | 270 | // Check if we were passed Type objects or IpcRequestParameterType objects 271 | if ((request.ParameterTypes != null) && (request.ParameterTypesByName != null)) 272 | { 273 | throw new IpcFaultException(IpcStatus.BadRequest, "Only one of ParameterTypes and ParameterTypesByName should be set!"); 274 | } 275 | if ((request.GenericArguments != null) && (request.GenericArgumentsByName != null)) 276 | { 277 | throw new IpcFaultException(IpcStatus.BadRequest, "Only one of GenericArguments and GenericArgumentsByName should be set!"); 278 | } 279 | 280 | object[] requestParameters = request.Parameters?.ToArray() ?? Array.Empty(); 281 | 282 | Type[] requestGenericArguments; 283 | if (request.GenericArguments != null) 284 | { 285 | // Generic arguments passed by Type 286 | requestGenericArguments = request.GenericArguments.ToArray(); 287 | } 288 | else if (request.GenericArgumentsByName != null) 289 | { 290 | // Generic arguments passed by name 291 | requestGenericArguments = new Type[request.GenericArgumentsByName.Count()]; 292 | int i = 0; 293 | foreach (var pair in request.GenericArgumentsByName) 294 | { 295 | var a = Assembly.Load(pair.AssemblyName); 296 | requestGenericArguments[i++] = a.GetType(pair.ParameterType); 297 | } 298 | } 299 | else 300 | { 301 | requestGenericArguments = Array.Empty(); 302 | } 303 | 304 | Type[] requestParameterTypes; 305 | if (request.ParameterTypes != null) 306 | { 307 | // Parameter types passed by Type 308 | requestParameterTypes = request.ParameterTypes.ToArray(); 309 | } 310 | else if (request.ParameterTypesByName != null) 311 | { 312 | // Parameter types passed by name 313 | requestParameterTypes = new Type[request.ParameterTypesByName.Count()]; 314 | int i = 0; 315 | foreach (var pair in request.ParameterTypesByName) 316 | { 317 | var a = Assembly.Load(pair.AssemblyName); 318 | requestParameterTypes[i++] = a.GetType(pair.ParameterType); 319 | } 320 | } 321 | else 322 | { 323 | requestParameterTypes = Array.Empty(); 324 | } 325 | 326 | foreach (MethodInfo serviceMethod in serviceMethods) 327 | { 328 | ParameterInfo[] serviceMethodParameters = serviceMethod.GetParameters(); 329 | int parameterTypeMatches = 0; 330 | 331 | if (serviceMethodParameters.Length == requestParameters.Length && serviceMethod.GetGenericArguments().Length == requestGenericArguments.Length) 332 | { 333 | for (int parameterIndex = 0; parameterIndex < serviceMethodParameters.Length; parameterIndex++) 334 | { 335 | Type serviceParameterType = serviceMethodParameters[parameterIndex].ParameterType.IsGenericParameter ? 336 | requestGenericArguments[serviceMethodParameters[parameterIndex].ParameterType.GenericParameterPosition] : 337 | serviceMethodParameters[parameterIndex].ParameterType; 338 | 339 | if (serviceParameterType == requestParameterTypes[parameterIndex]) 340 | { 341 | parameterTypeMatches++; 342 | } 343 | else 344 | { 345 | break; 346 | } 347 | } 348 | 349 | if (parameterTypeMatches == serviceMethodParameters.Length) 350 | { 351 | method = serviceMethod; // signatures match so assign 352 | break; 353 | } 354 | } 355 | } 356 | 357 | return method; 358 | } 359 | 360 | #region IDisposable 361 | 362 | private bool _disposed; 363 | 364 | protected virtual void Dispose(bool disposing) 365 | { 366 | if (_disposed) 367 | { 368 | return; 369 | } 370 | 371 | if (disposing) 372 | { 373 | _semaphore.Dispose(); 374 | } 375 | 376 | _disposed = true; 377 | } 378 | 379 | public void Dispose() 380 | { 381 | Dispose(true); 382 | GC.SuppressFinalize(this); 383 | } 384 | 385 | #endregion 386 | } 387 | } 388 | --------------------------------------------------------------------------------