diff --git a/build/Build.cs b/build/Build.cs index 4395370b..2e056427 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -146,9 +146,10 @@ ITargetDefinition TestDefinition(ITargetDefinition targetDefinition, Target depe .SetVerbosity(DotNetVerbosity.Normal)); }); + [PublicAPI] Target CopyToLocalPackages => _ => _ .OnlyWhenStatic(() => IsLocalBuild) - .TriggeredBy(Pack) + .DependsOn(Pack) .Executes(() => { EnsureExistingDirectory(LocalPackagesDirectory); diff --git a/source/Halibut.TestUtils.Contracts/IGenericService`.cs b/source/Halibut.TestUtils.Contracts/IGenericService`.cs new file mode 100644 index 00000000..c6f39027 --- /dev/null +++ b/source/Halibut.TestUtils.Contracts/IGenericService`.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Halibut.TestUtils.Contracts +{ + public interface IGenericService + { + string GetInfo(T value); + } + + public interface IAsyncGenericService + { + Task GetInfoAsync(T value, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/source/Halibut.Tests/GenericServiceFixture.cs b/source/Halibut.Tests/GenericServiceFixture.cs new file mode 100644 index 00000000..326aab9d --- /dev/null +++ b/source/Halibut.Tests/GenericServiceFixture.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using FluentAssertions; +using Halibut.Logging; +using Halibut.Tests.Support.TestAttributes; +using Halibut.Tests.Support.TestCases; +using Halibut.Tests.TestServices.Async; +using Halibut.TestUtils.Contracts; +using NUnit.Framework; + +namespace Halibut.Tests +{ + public class GenericServiceFixture : BaseTest + { + [Test] + [LatestClientAndLatestServiceTestCases(testNetworkConditions: false)] + public async Task GenericServiceNamesAreCorrectlyCalled(ClientAndServiceTestCase clientAndServiceTestCase) + { + await using var clientAndService = await clientAndServiceTestCase.CreateTestCaseBuilder() + .WithGenericService() + .WithGenericService() + .WithHalibutLoggingLevel(LogLevel.Info) + .Build(CancellationToken); + + var stringClient = clientAndService.CreateClient, IAsyncClientGenericService>(); + (await stringClient.GetInfoAsync("Cool string")).Should().Be("String => Cool string"); + + var doubleClient = clientAndService.CreateClient, IAsyncClientGenericService>(); + (await doubleClient.GetInfoAsync(6.54321)).Should().Be("Double => 6.54321"); + } + } +} \ No newline at end of file diff --git a/source/Halibut.Tests/Support/BackwardsCompatibility/LatestClientAndPreviousServiceVersionBuilder.cs b/source/Halibut.Tests/Support/BackwardsCompatibility/LatestClientAndPreviousServiceVersionBuilder.cs index 47bce466..42480874 100644 --- a/source/Halibut.Tests/Support/BackwardsCompatibility/LatestClientAndPreviousServiceVersionBuilder.cs +++ b/source/Halibut.Tests/Support/BackwardsCompatibility/LatestClientAndPreviousServiceVersionBuilder.cs @@ -109,13 +109,21 @@ public LatestClientAndPreviousServiceVersionBuilder WithTentacleServices() } IClientAndServiceBuilder IClientAndServiceBuilder.WithCachingService() => WithCachingService(); - + + public IClientAndServiceBuilder WithCachingService() { availableServices.HasCachingService = true; return this; } + IClientAndServiceBuilder IClientAndServiceBuilder.WithGenericService() => WithGenericService(); + public IClientAndServiceBuilder WithGenericService() + { + availableServices.HasGenericService = true; + return this; + } + IClientAndServiceBuilder IClientAndServiceBuilder.WithProxy() { return WithProxy(); @@ -156,7 +164,7 @@ IClientAndServiceBuilder IClientAndServiceBuilder.NoService() { return NoService(); } - + IClientAndServiceBuilder IClientAndServiceBuilder.WithForcingClientProxyType(ForceClientProxyType forceClientProxyType) { return WithForcingClientProxyType(forceClientProxyType); @@ -177,7 +185,7 @@ async Task IClientAndServiceBuilder.Build(CancellationToken c { return await Build(cancellationToken); } - + public async Task Build(CancellationToken cancellationToken) { var logger = new SerilogLoggerBuilder().Build().ForContext(); @@ -368,12 +376,12 @@ public TClientService CreateClient(Action(forceClientProxyType, Client, serviceEndpoint); } - + public TAsyncClientWithOptions CreateClientWithOptions() { return CreateClientWithOptions(_ => { }); } - + public TAsyncClientWithOptions CreateClientWithOptions(Action modifyServiceEndpoint) { var serviceEndpoint = ServiceEndPoint; @@ -389,7 +397,7 @@ public TAsyncClientService CreateAsyncClient() public async ValueTask DisposeAsync() { var logger = new SerilogLoggerBuilder().Build().ForContext(); - + logger.Information("****** ****** ****** ****** ****** ****** ******"); logger.Information("****** CLIENT AND SERVICE DISPOSE CALLED ******"); logger.Information("* Subsequent errors should be ignored *"); diff --git a/source/Halibut.Tests/Support/BackwardsCompatibility/OldServiceAvailableServices.cs b/source/Halibut.Tests/Support/BackwardsCompatibility/OldServiceAvailableServices.cs index 47be2e32..330954e4 100644 --- a/source/Halibut.Tests/Support/BackwardsCompatibility/OldServiceAvailableServices.cs +++ b/source/Halibut.Tests/Support/BackwardsCompatibility/OldServiceAvailableServices.cs @@ -11,5 +11,6 @@ public OldServiceAvailableServices(bool hasStandardServices, bool hasCachingServ public bool HasStandardServices { get; set; } public bool HasCachingService { get; set; } public bool HasTentacleServices { get; set; } + public bool HasGenericService { get; set; } } } \ No newline at end of file diff --git a/source/Halibut.Tests/Support/BackwardsCompatibility/PreviousClientVersionAndLatestServiceBuilder.cs b/source/Halibut.Tests/Support/BackwardsCompatibility/PreviousClientVersionAndLatestServiceBuilder.cs index cdca24d7..52eb5378 100644 --- a/source/Halibut.Tests/Support/BackwardsCompatibility/PreviousClientVersionAndLatestServiceBuilder.cs +++ b/source/Halibut.Tests/Support/BackwardsCompatibility/PreviousClientVersionAndLatestServiceBuilder.cs @@ -117,6 +117,11 @@ IClientAndServiceBuilder IClientAndServiceBuilder.WithCachingService() throw new Exception("Caching service is not supported, when testing on the old Client. Since the old client is on external CLR which does not have the new caching attributes which this service is used to test."); } + public IClientAndServiceBuilder WithGenericService() + { + throw new NotSupportedException("Generic services are not supported when testing the old client"); + } + public PreviousClientVersionAndLatestServiceBuilder WithMultipleParametersTestService(IMultipleParametersTestService multipleParametersTestService) { this.multipleParametersTestService = multipleParametersTestService; @@ -128,13 +133,13 @@ public PreviousClientVersionAndLatestServiceBuilder WithComplexObjectService(ICo this.complexObjectService = complexObjectService; return this; } - + public PreviousClientVersionAndLatestServiceBuilder WithLockService(ILockService lockService) { this.lockService = lockService; return this; } - + public PreviousClientVersionAndLatestServiceBuilder WithCountingService(ICountingService countingService) { this.countingService = countingService; @@ -185,7 +190,7 @@ public PreviousClientVersionAndLatestServiceBuilder WithProxy() return this; } - + IClientAndServiceBuilder IClientAndServiceBuilder.WithHalibutLoggingLevel(LogLevel halibutLogLevel) { return WithHalibutLoggingLevel(halibutLogLevel); @@ -202,7 +207,7 @@ async Task IClientAndServiceBuilder.Build(CancellationToken c { return await Build(cancellationToken); } - + public PreviousClientVersionAndLatestServiceBuilder NoService() { throw new NotImplementedException(); @@ -212,7 +217,7 @@ IClientAndServiceBuilder IClientAndServiceBuilder.NoService() { return NoService(); } - + IClientAndServiceBuilder IClientAndServiceBuilder.WithForcingClientProxyType(ForceClientProxyType forceClientProxyType) { throw new Exception("Not supported"); @@ -240,7 +245,7 @@ public async Task Build(CancellationToken cancellationToken) .WithLogFactory(new TestContextLogCreator("ProxyClient", halibutLogLevel).ToCachingLogFactory()) .WithAsyncHalibutFeatureEnabled() .Build(); - + proxyClient.Trust(serviceCertAndThumbprint.Thumbprint); var serviceFactory = new DelegateServiceFactory() @@ -464,9 +469,9 @@ public TClientService CreateClient() { throw new Exception($"Unsupported, since types within {typeof(TClientService)} would not actually be passed on to the remote process which holds the actual client under test.", e); } - + var serviceEndpoint = new ServiceEndPoint(serviceUri, serviceCertAndThumbprint.Thumbprint, Client.TimeoutsAndLimits); - + return ProxyClient.CreateAsyncClient(serviceEndpoint); } @@ -479,12 +484,12 @@ public TAsyncClientWithOptions CreateClientWithOptions(_ => { }); } - + public TAsyncClientWithOptions CreateClientWithOptions(Action modifyServiceEndpoint) { throw new NotSupportedException("Not supported since the options can not be passed to the external binary."); } - + public TAsyncClientService CreateAsyncClient() { return Client.CreateAsyncClient(ServiceEndPoint); diff --git a/source/Halibut.Tests/Support/IClientAndServiceBuilder.cs b/source/Halibut.Tests/Support/IClientAndServiceBuilder.cs index 85502a06..7195d1b8 100644 --- a/source/Halibut.Tests/Support/IClientAndServiceBuilder.cs +++ b/source/Halibut.Tests/Support/IClientAndServiceBuilder.cs @@ -15,6 +15,7 @@ public interface IClientAndServiceBuilder IClientAndServiceBuilder WithTentacleServices(); IClientAndServiceBuilder WithHalibutLoggingLevel(LogLevel info); IClientAndServiceBuilder WithCachingService(); + IClientAndServiceBuilder WithGenericService(); IClientAndServiceBuilder NoService(); IClientAndServiceBuilder WithForcingClientProxyType(ForceClientProxyType forceClientProxyType); IClientAndServiceBuilder WithServiceAsyncHalibutFeatureEnabled(); diff --git a/source/Halibut.Tests/Support/LatestClientAndLatestServiceBuilder.cs b/source/Halibut.Tests/Support/LatestClientAndLatestServiceBuilder.cs index ac69ff9c..a5e47a62 100644 --- a/source/Halibut.Tests/Support/LatestClientAndLatestServiceBuilder.cs +++ b/source/Halibut.Tests/Support/LatestClientAndLatestServiceBuilder.cs @@ -35,7 +35,7 @@ public class LatestClientAndLatestServiceBuilder : IClientAndServiceBuilder ServiceFactoryBuilder serviceFactoryBuilder = new(); IServiceFactory? serviceFactory; readonly CertAndThumbprint serviceCertAndThumbprint; - string clientTrustsThumbprint; + string clientTrustsThumbprint; readonly CertAndThumbprint clientCertAndThumbprint; string serviceTrustsThumbprint; ForceClientProxyType? forceClientProxyType; @@ -105,7 +105,7 @@ public static LatestClientAndLatestServiceBuilder ForServiceConnectionType(Servi throw new ArgumentOutOfRangeException(nameof(serviceConnectionType), serviceConnectionType, null); } } - + /// /// Ie no tentacle. /// In the case of listening, a TCPListenerWhichKillsNewConnections will be created. This will cause connections to @@ -122,7 +122,7 @@ IClientAndServiceBuilder IClientAndServiceBuilder.NoService() { return NoService(); } - + public LatestClientAndLatestServiceBuilder WithPollingClients(IEnumerable pollingClientUris) { createClient = false; @@ -137,13 +137,13 @@ public LatestClientAndLatestServiceBuilder WithStreamFactory(IStreamFactory stre this.clientStreamFactory = streamFactory; return this; } - + public LatestClientAndLatestServiceBuilder WithClientStreamFactory(IStreamFactory clientStreamFactory) { this.clientStreamFactory = clientStreamFactory; return this; } - + public LatestClientAndLatestServiceBuilder WithServiceStreamFactory(IStreamFactory serviceStreamFactory) { this.serviceStreamFactory = serviceStreamFactory; @@ -155,13 +155,13 @@ public LatestClientAndLatestServiceBuilder WithServiceConnectionsObserver(IConne this.serviceConnectionsObserver = connectionsObserver; return this; } - + public LatestClientAndLatestServiceBuilder WithClientConnectionsObserver(IConnectionsObserver connectionsObserver) { this.clientConnectionsObserver = connectionsObserver; return this; } - + public LatestClientAndLatestServiceBuilder WithHalibutTimeoutsAndLimits(HalibutTimeoutsAndLimits halibutTimeoutsAndLimits) { this.halibutTimeoutsAndLimits = halibutTimeoutsAndLimits; @@ -183,7 +183,7 @@ public LatestClientAndLatestServiceBuilder WithServiceFactory(IServiceFactory se public LatestClientAndLatestServiceBuilder WithService(Func implementation) { serviceFactoryBuilder.WithService(implementation); - + if (serviceFactory != null) { if (serviceFactory is DelegateServiceFactory delegateServiceFactory) @@ -202,7 +202,7 @@ public LatestClientAndLatestServiceBuilder WithService(Func(Func implementation) { serviceFactoryBuilder.WithService(implementation); - + if (serviceFactory != null) { if (serviceFactory is DelegateServiceFactory delegateServiceFactory) @@ -281,12 +281,18 @@ public LatestClientAndLatestServiceBuilder WithTentacleServices() } IClientAndServiceBuilder IClientAndServiceBuilder.WithCachingService() => WithCachingService(); - + public LatestClientAndLatestServiceBuilder WithCachingService() { return this.WithService(() => new CachingService()); } + IClientAndServiceBuilder IClientAndServiceBuilder.WithGenericService() => WithGenericService(); + + public LatestClientAndLatestServiceBuilder WithGenericService() + { + return this.WithService>(() => new GenericService()); + } IClientAndServiceBuilder IClientAndServiceBuilder.WithProxy() { return WithProxy(); @@ -326,23 +332,23 @@ IClientAndServiceBuilder IClientAndServiceBuilder.WithHalibutLoggingLevel(LogLev { return WithHalibutLoggingLevel(halibutLogLevel); } - + public LatestClientAndLatestServiceBuilder WithHalibutLoggingLevel(LogLevel halibutLogLevel) { this.halibutLogLevel = halibutLogLevel; return this; } - + public LatestClientAndLatestServiceBuilder RecordingClientLogs(out ConcurrentDictionary inMemoryLoggers) { inMemoryLoggers = new ConcurrentDictionary(); this.clientInMemoryLoggers = inMemoryLoggers; return this; } - + public LatestClientAndLatestServiceBuilder RecordingServiceLogs(out ConcurrentDictionary inMemoryLoggers) { - inMemoryLoggers = new ConcurrentDictionary(); + inMemoryLoggers = new ConcurrentDictionary(); this.serviceInMemoryLoggers = inMemoryLoggers; return this; } @@ -352,7 +358,7 @@ public LatestClientAndLatestServiceBuilder WithClientOnUnauthorizedClientConnect clientOnUnauthorizedClientConnect = onUnauthorizedClientConnect; return this; } - + public LatestClientAndLatestServiceBuilder WithClientTrustProvider(ITrustProvider trustProvider) { clientTrustProvider = trustProvider; @@ -364,7 +370,7 @@ public LatestClientAndLatestServiceBuilder WithClientTrustingTheWrongCertificate clientTrustsThumbprint = CertAndThumbprint.Wrong.Thumbprint; return this; } - + public LatestClientAndLatestServiceBuilder WithServiceTrustingTheWrongCertificate() { serviceTrustsThumbprint = CertAndThumbprint.Wrong.Thumbprint; @@ -387,7 +393,7 @@ public LatestClientAndLatestServiceBuilder WithForcingClientProxyType(ForceClien this.forceClientProxyType = forceClientProxyType; return this; } - + public LatestClientAndLatestServiceBuilder WithClientRpcObserver(IRpcObserver? clientRpcObserver) { this.clientRpcObserver = clientRpcObserver; @@ -403,7 +409,7 @@ public async Task Build(CancellationToken cancellationToken) { var logger = new SerilogLoggerBuilder().Build().ForContext(); CancellationTokenSource cancellationTokenSource = new(); - + serviceFactory ??= serviceFactoryBuilder.Build(); var octopusLogFactory = BuildClientLogger(); @@ -431,7 +437,7 @@ public async Task Build(CancellationToken cancellationToken) client = clientBuilder.Build(); client.Trust(clientTrustsThumbprint); } - + HalibutRuntime? service = null; if (createService) @@ -462,7 +468,7 @@ public async Task Build(CancellationToken cancellationToken) Uri serviceUri; Uri? clientUri = null; - + if (ServiceConnectionType == ServiceConnectionType.Polling) { serviceUri = new Uri("poll://SQ-TENTAPOLL"); @@ -473,11 +479,11 @@ public async Task Build(CancellationToken cancellationToken) var clientListenPort = client!.Listen(); portForwarder = portForwarderFactory?.Invoke(clientListenPort); - + clientUri = new Uri($"https://localhost:{portForwarder?.ListeningPort ?? clientListenPort}"); clientUrisToPoll.Add(clientUri); } - + if (service != null) { foreach (var clientUriToPoll in clientUrisToPoll) @@ -492,7 +498,7 @@ public async Task Build(CancellationToken cancellationToken) else if (ServiceConnectionType == ServiceConnectionType.PollingOverWebSocket) { serviceUri = new Uri("poll://SQ-TENTAPOLL"); - + var clientUrsToPoll = pollingClientUris.ToList(); if (createClient) { @@ -595,7 +601,7 @@ ILogFactory BuildClientLogger() ) .ToCachingLogFactory(); } - + ILogFactory BuildServiceLogger() { if (serviceInMemoryLoggers == null) @@ -692,7 +698,7 @@ public TClientService CreateClient(Action(forceClientProxyType, Client, serviceEndpoint); } - + public TAsyncClientWithOptions CreateClientWithOptions() { return CreateClientWithOptions(_ => { }); @@ -704,7 +710,7 @@ public TAsyncClientWithOptions CreateClientWithOptions(forceClientProxyType, Client, serviceEndpoint); } - + public TAsyncClientService CreateAsyncClient() { return Client.CreateAsyncClient(ServiceEndPoint); diff --git a/source/Halibut.Tests/TestServices/Async/IAsyncClientGenericService`.cs b/source/Halibut.Tests/TestServices/Async/IAsyncClientGenericService`.cs new file mode 100644 index 00000000..d449d222 --- /dev/null +++ b/source/Halibut.Tests/TestServices/Async/IAsyncClientGenericService`.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace Halibut.Tests.TestServices.Async +{ + public interface IAsyncClientGenericService + { + Task GetInfoAsync(T value); + } +} \ No newline at end of file diff --git a/source/Halibut.Tests/TestServices/GenericService`.cs b/source/Halibut.Tests/TestServices/GenericService`.cs new file mode 100644 index 00000000..27f752ee --- /dev/null +++ b/source/Halibut.Tests/TestServices/GenericService`.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using Halibut.TestUtils.Contracts; + +namespace Halibut.Tests.TestServices +{ + public class GenericService : IGenericService + { + public string GetInfo(T value) + { + return $"{typeof(T).Name} => {value}"; + } + } + + public class AsyncGenericService : IAsyncGenericService + { + readonly GenericService genericService; + + public AsyncGenericService(GenericService genericService) + { + this.genericService = genericService; + } + + public async Task GetInfoAsync(T value, CancellationToken cancellationToken) + { + await Task.CompletedTask; + return genericService.GetInfo(value); + } + } +} \ No newline at end of file diff --git a/source/Halibut.Tests/Util/NoSanityCheckingDelegateServiceFactory.cs b/source/Halibut.Tests/Util/NoSanityCheckingDelegateServiceFactory.cs index 6f4f93d4..b07fe615 100644 --- a/source/Halibut.Tests/Util/NoSanityCheckingDelegateServiceFactory.cs +++ b/source/Halibut.Tests/Util/NoSanityCheckingDelegateServiceFactory.cs @@ -3,6 +3,7 @@ using System.Linq; using Halibut.Exceptions; using Halibut.ServiceModel; +using Halibut.Util; namespace Halibut.Tests.Util { @@ -16,7 +17,7 @@ public class NoSanityCheckingDelegateServiceFactory : IServiceFactory public NoSanityCheckingDelegateServiceFactory Register(Func implementation) { var serviceType = typeof(TContract); - services.Add(serviceType.Name, () => implementation()); + services.Add(serviceType.GetGenericQualifiedTypeName(), () => implementation()); lock (serviceTypes) { serviceTypes.Add(serviceType); @@ -28,7 +29,7 @@ public NoSanityCheckingDelegateServiceFactory Register(Func(Func implementation) { var serviceType = typeof(TContract); - services.Add(serviceType.Name, () => implementation()); + services.Add(serviceType.GetGenericQualifiedTypeName(), () => implementation()); lock (serviceTypes) { serviceTypes.Add(serviceType); diff --git a/source/Halibut/ServiceModel/DelegateServiceFactory.cs b/source/Halibut/ServiceModel/DelegateServiceFactory.cs index 1522e245..e1cc8d12 100644 --- a/source/Halibut/ServiceModel/DelegateServiceFactory.cs +++ b/source/Halibut/ServiceModel/DelegateServiceFactory.cs @@ -14,10 +14,10 @@ public class DelegateServiceFactory : IServiceFactory public DelegateServiceFactory Register(Func implementation) { var serviceType = typeof(TContract); - services.Add(serviceType.Name, () => implementation()); + services.Add(serviceType.GetGenericQualifiedTypeName(), () => implementation()); lock (serviceTypes) { - serviceTypes.Add(serviceType); + serviceTypes.Add(serviceType); } return this; @@ -26,9 +26,9 @@ public DelegateServiceFactory Register(Func implementation public DelegateServiceFactory Register(Func implementation) { AsyncServiceVerifier.VerifyAsyncSurfaceAreaFollowsConventions(); - + var serviceType = typeof(TContract); - services.Add(serviceType.Name, () => implementation()); + services.Add(serviceType.GetGenericQualifiedTypeName(), () => implementation()); lock (serviceTypes) { serviceTypes.Add(serviceType); @@ -58,14 +58,14 @@ static IServiceLease CreateService(Func serviceBuilder) var service = serviceBuilder(); return new Lease(service); } - + public IReadOnlyList RegisteredServiceTypes { get { lock (serviceTypes) { - return serviceTypes.ToList(); + return serviceTypes.ToList(); } } } diff --git a/source/Halibut/ServiceModel/HalibutProxy.cs b/source/Halibut/ServiceModel/HalibutProxy.cs index c9544800..d1ac6f43 100644 --- a/source/Halibut/ServiceModel/HalibutProxy.cs +++ b/source/Halibut/ServiceModel/HalibutProxy.cs @@ -5,6 +5,7 @@ using Halibut.Diagnostics; using Halibut.Exceptions; using Halibut.Transport.Protocol; +using Halibut.Util; namespace Halibut.ServiceModel { @@ -72,13 +73,14 @@ RequestMessage CreateRequest(IMethodMessage methodCall, object[] args) var activityId = Guid.NewGuid(); var method = ((MethodInfo) methodCall.MethodBase); + var contractTypeName = contractType.GetGenericQualifiedTypeName(); var request = new RequestMessage { - Id = contractType.Name + "::" + method.Name + "[" + Interlocked.Increment(ref callId) + "] / " + activityId, + Id = contractTypeName + "::" + method.Name + "[" + Interlocked.Increment(ref callId) + "] / " + activityId, ActivityId = activityId, Destination = endPoint, MethodName = method.Name, - ServiceName = contractType.Name, + ServiceName = contractTypeName, Params = args }; return request; @@ -140,13 +142,14 @@ RequestMessage CreateRequest(MethodInfo targetMethod, object[] args) { var activityId = Guid.NewGuid(); + var contractTypeName = contractType.GetGenericQualifiedTypeName(); var request = new RequestMessage { - Id = contractType.Name + "::" + targetMethod.Name + "[" + Interlocked.Increment(ref callId) + "] / " + activityId, + Id = contractTypeName + "::" + targetMethod.Name + "[" + Interlocked.Increment(ref callId) + "] / " + activityId, ActivityId = activityId, Destination = endPoint, MethodName = targetMethod.Name, - ServiceName = contractType.Name, + ServiceName = contractTypeName, Params = args }; return request; diff --git a/source/Halibut/ServiceModel/HalibutProxyWithAsync.cs b/source/Halibut/ServiceModel/HalibutProxyWithAsync.cs index 3570cf51..60eb08ee 100644 --- a/source/Halibut/ServiceModel/HalibutProxyWithAsync.cs +++ b/source/Halibut/ServiceModel/HalibutProxyWithAsync.cs @@ -6,6 +6,7 @@ using Halibut.Diagnostics; using Halibut.Exceptions; using Halibut.Transport.Protocol; +using Halibut.Util; namespace Halibut.ServiceModel { @@ -22,10 +23,10 @@ public class HalibutProxyWithAsync : DispatchProxyAsync ILog logger; public void Configure( - MessageRouter messageRouter, - Type contractType, + MessageRouter messageRouter, + Type contractType, ServiceEndPoint endPoint, - ILog logger, + ILog logger, CancellationToken cancellationToken) { this.messageRouter = messageRouter; @@ -77,12 +78,12 @@ public override async Task InvokeAsyncT(MethodInfo asyncMethod, object[] a var response = await messageRouter(request, serviceMethod, requestCancellationTokens); EnsureNotError(response); - + return (serviceMethod, response.Result); } /// - /// + /// /// /// The async client method called, used in the call id. /// The method to actually invoke @@ -92,13 +93,14 @@ RequestMessage CreateRequest(MethodInfo asyncMethod, MethodInfo targetMethod, ob { var activityId = Guid.NewGuid(); + var contractTypeName = contractType.GetGenericQualifiedTypeName(); var request = new RequestMessage { - Id = contractType.Name + "::" + asyncMethod.Name + "[" + Interlocked.Increment(ref callId) + "] / " + activityId, + Id = contractTypeName + "::" + asyncMethod.Name + "[" + Interlocked.Increment(ref callId) + "] / " + activityId, ActivityId = activityId, Destination = endPoint, MethodName = targetMethod.Name, - ServiceName = contractType.Name, + ServiceName = contractTypeName, Params = args }; return request; diff --git a/source/Halibut/Transport/Caching/CachingKeyGenerator.cs b/source/Halibut/Transport/Caching/CachingKeyGenerator.cs index 985a3fe0..05fecf3d 100644 --- a/source/Halibut/Transport/Caching/CachingKeyGenerator.cs +++ b/source/Halibut/Transport/Caching/CachingKeyGenerator.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using System.Text; +using Halibut.Util; namespace Halibut.Transport.Caching { @@ -22,7 +23,7 @@ string GetCacheKeyPrefix(MethodInfo methodInfo, string prefix) { if (!string.IsNullOrWhiteSpace(prefix)) return $"{prefix}{LinkChar}"; - var typeName = methodInfo.DeclaringType?.Name; + var typeName = methodInfo.DeclaringType?.GetGenericQualifiedTypeName(); var methodName = methodInfo.Name; return $"{typeName}{LinkChar}{methodName}{LinkChar}"; diff --git a/source/Halibut/Transport/Protocol/TypeRegistry.cs b/source/Halibut/Transport/Protocol/TypeRegistry.cs index 0272f4cf..6b70010c 100644 --- a/source/Halibut/Transport/Protocol/TypeRegistry.cs +++ b/source/Halibut/Transport/Protocol/TypeRegistry.cs @@ -17,11 +17,11 @@ public void Register(params Type[] registeredServiceTypes) { foreach (var method in serviceType.GetHalibutServiceMethods()) { - RegisterType(method.ReturnType, $"{serviceType.Name}.{method.Name}:{method.ReturnType.Name}", false); + RegisterType(method.ReturnType, $"{serviceType.GetGenericQualifiedTypeName()}.{method.Name}:{method.ReturnType.Name}", false); foreach (var param in method.GetParameters()) { - RegisterType(param.ParameterType,$"{serviceType.Name}.{method.Name}(){param.Name}:{param.ParameterType.Name}", false); + RegisterType(param.ParameterType,$"{serviceType.GetGenericQualifiedTypeName()}.{method.Name}(){param.Name}:{param.ParameterType.Name}", false); } } } @@ -58,7 +58,7 @@ public void RegisterType(Type type, string path, bool ignoreObject) foreach (var sub in SubTypesFor(type)) { - RegisterType(sub, $"{path}<{sub.Name}>", ignoreObject); + RegisterType(sub, $"{path}<{sub.GetGenericQualifiedTypeName()}>", ignoreObject); } } @@ -81,6 +81,18 @@ IEnumerable SubTypesFor(Type type) yield return t; } } + + if (type.IsAbstract) + { + var derivedTypes = AppDomain.CurrentDomain.GetAssemblies() + .Where(asm => !asm.IsDynamic) + .SelectMany(asm => asm.GetExportedTypes()) + .Where(t => !t.IsAbstract) + .Where(type.IsAssignableFrom); + + foreach (var derivedType in derivedTypes) + yield return derivedType; + } } public bool IsInAllowedTypes(Type type) diff --git a/source/Halibut/Util/TypeExtensionMethods.cs b/source/Halibut/Util/TypeExtensionMethods.cs index 63db6ebc..0f92cd0e 100644 --- a/source/Halibut/Util/TypeExtensionMethods.cs +++ b/source/Halibut/Util/TypeExtensionMethods.cs @@ -37,5 +37,13 @@ public static bool AllowedOnHalibutInterface(this Type type) return true; } + + public static string GetGenericQualifiedTypeName(this Type type) + { + return !type.IsGenericType + ? type.Name + // MyService`2[string,int] or MyService`2[string,MyType`1[bool]] + : $"{type.Name}[{string.Join(",", type.GenericTypeArguments.Select(GetGenericQualifiedTypeName))}]"; + } } } \ No newline at end of file