From 8b560ff6865f00acf95b3ba606b5140d63ce6c17 Mon Sep 17 00:00:00 2001 From: John Weber Date: Tue, 14 Oct 2014 22:22:39 -0700 Subject: [PATCH 1/6] Added documentation for calling generated async methods using the dynamic keyword --- README.md | 26 ++++++++--- .../ProxyTests.cs | 45 +++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5e4119d..f3baa4d 100644 --- a/README.md +++ b/README.md @@ -83,18 +83,34 @@ Exposes the full configuration available. See the [Configuration](#configuration Async Support ------------- -WCF service contract interfaces that define task based async methods will automatically work with the .NET 4.5 async/await support. +At runtime, generating the proxy will ensure that all non-async operation contract methods have an associated Async implementation. What this means is that if your service contract is defined as: - var proxy = WcfClientProxy.Create(); - int result = await proxy.MakeCallAsync("test"); + [ServiceContract] + interface IService + { + [OperationContract] + int MakeCall(string input); + } -Service contracts that don't define task based methods can be used in an async/await fashion by calling the `WcfClientProxy.CreateAsyncProxy()` method. This call returns a type `IAsyncProxy` that exposes a `CallAsync()` method. +then the proxy returned by calling `WcfClientProxy.Create()` will automatically define a `Task MakeCallAsync(string input)` operation contract method at runtime. + +To utilize this runtime generated async method, an async friendly wrapped proxy can be generated by calling the `WcfClientProxy.CreateAsyncProxy()` method. This call returns a type `IAsyncProxy` that exposes a `CallAsync()` method. -For example, a service contract interface with method `int MakeCall(string input)` can be called asynchronously like: +The `int MakeCall(string input)` method can now be called asynchronously like: var proxy = WcfClientProxy.CreateAsyncProxy(); int result = await proxy.CallAsync(s => s.MakeCall("test")); +It is also possible to call into the runtime generated Async methods dynamically without use of the `IAsyncProxy` wrapper. For example, the same service contract interface with the non-async method defined can be called asynchronously as so: + + var proxy = WcfClientProxy.Create(); + int result = await ((dynamic) proxy).MakeCallAsync("test"); + +WCF service contract interfaces that define task based async methods at compile will automatically work with the C# 5 async/await support. + + var proxy = WcfClientProxy.Create(); + int result = await proxy.MakeCallAsync("test"); + ### Async Limitations Methods that define `out` or `ref` parameters are not supported when making async/await calls. Attempts to make async calls using a proxy with these parameter types will result in a runtime exception being thrown. diff --git a/source/WcfClientProxyGenerator.Tests/ProxyTests.cs b/source/WcfClientProxyGenerator.Tests/ProxyTests.cs index 94dc918..22cac1a 100644 --- a/source/WcfClientProxyGenerator.Tests/ProxyTests.cs +++ b/source/WcfClientProxyGenerator.Tests/ProxyTests.cs @@ -1717,6 +1717,51 @@ public async Task Async_HandleResponse_ActionWithoutPredicate_CanInspectResponse #endregion + #region Dynamic Async Invocation + + [Test] + public async Task Async_DynamicConversion_Proxy_ReturnsExpectedValue_WhenCallingGeneratedAsyncMethod() + { + var mockService = new Mock(); + mockService.Setup(m => m.TestMethod("good")).Returns("OK"); + + var serviceHost = InProcTestFactory.CreateHost(new TestServiceImpl(mockService)); + + var proxy = WcfClientProxy.Create(c => c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress)); + + // ITestService does not define TestMethodAsync, it's generated at runtime + var result = await ((dynamic) proxy).TestMethodAsync("good"); + + Assert.AreEqual("OK", result); + } + + [Test] + public async Task Async_DynamicConversion_Proxy_CanCallGeneratedAsyncVoidMethod() + { + var resetEvent = new AutoResetEvent(false); + + var mockService = new Mock(); + mockService + .Setup(m => m.VoidMethod("good")) + .Callback(input => + { + Assert.That(input, Is.EqualTo("good")); + resetEvent.Set(); + }); + + var serviceHost = InProcTestFactory.CreateHost(new TestServiceImpl(mockService)); + + var proxy = WcfClientProxy.Create(c => c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress)); + + // ITestService does not define VoidMethodAsync, it's generated at runtime + await ((dynamic) proxy).VoidMethodAsync("good"); + + if (!resetEvent.WaitOne(300)) + Assert.Fail("Timeout occurred when waiting for callback"); + } + + #endregion + #region Better error messages tests [ServiceContract] From 99f2e8687e7c26c558cfab3cd31e6add98b37eef Mon Sep 17 00:00:00 2001 From: jweber Date: Wed, 15 Oct 2014 01:33:48 -0700 Subject: [PATCH 2/6] Added HandleRequestArgument proxy configuration option. Allows for inspecting or modifying arguments in WCF requests before they are sent over the wire. --- .../Infrastructure/ITestService.cs | 3 + .../Infrastructure/TestServiceImpl.cs | 8 + .../ProxyTests.cs | 106 +++++++++ .../DynamicProxyTypeGenerator.cs | 38 ++- .../IProxyConfigurator.cs | 38 +++ .../RetryingWcfActionInvoker.cs | 20 +- .../RetryingWcfActionInvokerProvider.cs | 223 +++++++++++++++--- 7 files changed, 384 insertions(+), 52 deletions(-) diff --git a/source/WcfClientProxyGenerator.Tests/Infrastructure/ITestService.cs b/source/WcfClientProxyGenerator.Tests/Infrastructure/ITestService.cs index a5476c5..945b67b 100644 --- a/source/WcfClientProxyGenerator.Tests/Infrastructure/ITestService.cs +++ b/source/WcfClientProxyGenerator.Tests/Infrastructure/ITestService.cs @@ -12,6 +12,9 @@ public interface ITestService [OperationContract(Name = "TestMethod2")] string TestMethod(string input, string two); + [OperationContract] + int TestMethodMixed(string input, int input2); + [OperationContract] void VoidMethod(string input); diff --git a/source/WcfClientProxyGenerator.Tests/Infrastructure/TestServiceImpl.cs b/source/WcfClientProxyGenerator.Tests/Infrastructure/TestServiceImpl.cs index b9b7b02..ff8b2be 100644 --- a/source/WcfClientProxyGenerator.Tests/Infrastructure/TestServiceImpl.cs +++ b/source/WcfClientProxyGenerator.Tests/Infrastructure/TestServiceImpl.cs @@ -37,6 +37,14 @@ public string TestMethod(string input, string two) return string.Format("Echo: {0}, {1}", input, two); } + public int TestMethodMixed(string input, int input2) + { + if (_mock != null) + return _mock.Object.TestMethodMixed(input, input2); + + return input2; + } + public void VoidMethod(string input) { if (_mock != null) diff --git a/source/WcfClientProxyGenerator.Tests/ProxyTests.cs b/source/WcfClientProxyGenerator.Tests/ProxyTests.cs index 22cac1a..eaea219 100644 --- a/source/WcfClientProxyGenerator.Tests/ProxyTests.cs +++ b/source/WcfClientProxyGenerator.Tests/ProxyTests.cs @@ -1243,6 +1243,112 @@ public void Proxy_ChannelFactory_UsesConfiguredEndpoint() #endregion + #region HandleRequestArgument + + [Test] + public void HandleRequestArgument_ModifiesComplexRequest_BeforeSendingToService() + { + var mockService = new Mock(); + mockService + .Setup(m => m.TestMethodComplex(It.IsAny())) + .Returns((Request r) => new Response + { + ResponseMessage = r.RequestMessage + }); + + var serviceHost = InProcTestFactory.CreateHost(new TestServiceImpl(mockService)); + + var proxy = WcfClientProxy.Create(c => + { + c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress); + c.HandleRequestArgument( + where: (arg, param) => arg == null, + handler: arg => + { + return new Request + { + RequestMessage = "default message" + }; + }); + }); + + proxy.TestMethodComplex(null); + mockService + .Verify(m => m.TestMethodComplex(It.Is(r => r.RequestMessage == "default message")), Times.Once()); + + proxy.TestMethodComplex(new Request { RequestMessage = "set" }); + mockService + .Verify(m => m.TestMethodComplex(It.Is(r => r.RequestMessage == "set")), Times.Once()); + } + + [Test] + public void HandleRequestArgument_MatchesArgumentsOfSameType_BasedOnParameterName() + { + var mockService = new Mock(); + mockService + .Setup(m => m.TestMethod(It.IsAny() /* input */, It.IsAny() /* two */)) + .Returns("response"); + + var serviceHost = InProcTestFactory.CreateHost(new TestServiceImpl(mockService)); + + var proxy = WcfClientProxy.Create(c => + { + c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress); + + c.HandleRequestArgument( + where: (arg, paramName) => paramName == "input", + handler: arg => + { + return "always input"; + }); + + c.HandleRequestArgument( + where: (arg, paramName) => paramName == "two", + handler: arg => + { + return "always two"; + }); + }); + + proxy.TestMethod("first argument", "second argument"); + + mockService + .Verify(m => m.TestMethod("always input", "always two"), Times.Once()); + } + + [Test] + public void HandleRequestArgument_MatchesArgumentsByBaseTypes() + { + int handleRequestArgumentCounter = 0; + + var mockService = new Mock(); + mockService + .Setup(m => m.TestMethodMixed(It.IsAny(), It.IsAny())) + .Returns(10); + + var serviceHost = InProcTestFactory.CreateHost(new TestServiceImpl(mockService)); + + var proxy = WcfClientProxy.Create(c => + { + c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress); + + c.HandleRequestArgument( + handler: arg => + { + handleRequestArgumentCounter++; + }); + }); + + proxy.TestMethodMixed("first argument", 100); + + mockService + .Verify(m => m.TestMethodMixed("first argument", 100), Times.Once()); + + Assert.That(handleRequestArgumentCounter, Is.EqualTo(2)); + } + + #endregion + #region HandleResponse [Test] diff --git a/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs b/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs index a7615e7..a17405d 100644 --- a/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs +++ b/source/WcfClientProxyGenerator/DynamicProxyTypeGenerator.cs @@ -241,7 +241,11 @@ private static void GenerateAsyncTaskMethod( MethodInfo methodInfo, TypeBuilder typeBuilder) { - var parameterTypes = methodInfo.GetParameters() + var parameters = methodInfo + .GetParameters() + .ToArray(); + + var parameterTypes = parameters .Select(m => m.ParameterType) .ToArray(); @@ -266,8 +270,8 @@ private static void GenerateAsyncTaskMethod( parameterTypes); for (int i = 1; i <= parameterTypes.Length; i++) - methodBuilder.DefineParameter(i, ParameterAttributes.None, "arg" + i); - + methodBuilder.DefineParameter(i, ParameterAttributes.None, parameters[i-1].Name); + var originalOperationContract = methodInfo.GetCustomAttribute(); var attributeCtor = typeof(OperationContractAttribute) @@ -324,7 +328,11 @@ private static void GenerateServiceProxyMethod( MethodInfo methodInfo, TypeBuilder typeBuilder) { - var parameterTypes = methodInfo.GetParameters() + var parameters = methodInfo + .GetParameters() + .ToArray(); + + var parameterTypes = parameters .Select(m => m.ParameterType) .ToArray(); @@ -335,8 +343,8 @@ private static void GenerateServiceProxyMethod( methodInfo.ReturnType, parameterTypes); - for (int i = 1; i <= parameterTypes.Length; i++) - methodBuilder.DefineParameter(i, ParameterAttributes.None, "arg" + i); + for (int i = 1; i <= parameters.Length; i++) + methodBuilder.DefineParameter(i, ParameterAttributes.None, parameters[i-1].Name); Type serviceCallWrapperType; var serviceCallWrapperFields = GenerateServiceCallWrapperType( @@ -380,6 +388,24 @@ private static void GenerateServiceProxyMethod( if (serviceCallWrapperCtor == null) throw new Exception("Parameterless constructor not found for type: " + serviceCallWrapperType); + for (int i = 0; i < parameterTypes.Length; i++) + { + Type parameterType = parameterTypes[i]; + if (parameterType.IsByRef) + continue; + + var handleRequestParameterMethod = typeof(RetryingWcfActionInvokerProvider<>) + .MakeGenericType(asyncInterfaceType) + .GetMethod("HandleRequestArgument", BindingFlags.Instance | BindingFlags.NonPublic) + .MakeGenericMethod(parameterType); + + ilGenerator.Emit(OpCodes.Ldarg, 0); + ilGenerator.Emit(OpCodes.Ldarg, i + 1); + ilGenerator.Emit(OpCodes.Ldstr, parameters[i].Name); + ilGenerator.Emit(OpCodes.Call, handleRequestParameterMethod); + ilGenerator.Emit(OpCodes.Starg_S, i + 1); + } + // local2 = new MethodType(); ilGenerator.Emit(OpCodes.Newobj, serviceCallWrapperCtor); ilGenerator.Emit(OpCodes.Stloc_2); diff --git a/source/WcfClientProxyGenerator/IProxyConfigurator.cs b/source/WcfClientProxyGenerator/IProxyConfigurator.cs index f539f0f..6f174c4 100644 --- a/source/WcfClientProxyGenerator/IProxyConfigurator.cs +++ b/source/WcfClientProxyGenerator/IProxyConfigurator.cs @@ -62,6 +62,42 @@ public interface IProxyConfigurator /// ChannelFactory ChannelFactory { get; } + #region HandleRequestArgument + + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Predicate to filter the request arguments by properties of the request, or the parameter name + /// Delegate that takes a + void HandleRequestArgument(Func where, Action handler); + + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Delegate that takes a + void HandleRequestArgument(Action handler); + + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Predicate to filter the request arguments by properties of the request, or the parameter name + /// Delegate that takes a and returns a + void HandleRequestArgument(Func where, Func handler); + + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Delegate that takes a and returns a + void HandleRequestArgument(Func handler); + + #endregion + + #region HandleResponse + /// /// Allows inspecting and modifying the object /// before returning the response to the calling method. @@ -103,5 +139,7 @@ public interface IProxyConfigurator /// Delegate that takes a and returns a /// void HandleResponse(Func handler); + + #endregion } } \ No newline at end of file diff --git a/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs b/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs index 960f9b1..e6e592a 100644 --- a/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs +++ b/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs @@ -15,12 +15,6 @@ namespace WcfClientProxyGenerator { - class ResponseHandlerHolder - { - public object Predicate { get; set; } - public object ResponseHandler { get; set; } - } - internal class RetryingWcfActionInvoker : IActionInvoker where TServiceInterface : class { @@ -39,7 +33,7 @@ private static ConcurrentDictionary> ResponseHandlerCache private readonly Type _originalServiceInterfaceType; private readonly IDictionary> _retryPredicates; - private readonly IDictionary> _responseHandlers; + private readonly IDictionary> _responseHandlers; /// /// The method that initializes new WCF action providers @@ -63,7 +57,7 @@ public RetryingWcfActionInvoker( { typeof(ServerTooBusyException), new List { null } } }; - _responseHandlers = new Dictionary>(); + _responseHandlers = new Dictionary>(); _originalServiceInterfaceType = GetOriginalServiceInterface(); } @@ -143,12 +137,12 @@ public void AddResponseToRetryOn(Predicate where) public void AddResponseHandler(Func handler, Predicate @where) { if (!_responseHandlers.ContainsKey(typeof(TResponse))) - _responseHandlers.Add(typeof(TResponse), new List()); + _responseHandlers.Add(typeof(TResponse), new List()); - _responseHandlers[typeof(TResponse)].Add(new ResponseHandlerHolder + _responseHandlers[typeof(TResponse)].Add(new PredicateHandlerHolder { Predicate = @where, - ResponseHandler = handler + Handler = handler }); } @@ -450,7 +444,7 @@ private TResponse ExecuteResponseHandlers(TResponse response, Type ty if (!this._responseHandlers.ContainsKey(@type)) return response; - IList responseHandlerHolders = this._responseHandlers[@type]; + IList responseHandlerHolders = this._responseHandlers[@type]; MethodInfo predicateInvokeMethod = ResponseHandlerPredicateCache.GetOrAddSafe(@type, _ => { @@ -476,7 +470,7 @@ private TResponse ExecuteResponseHandlers(TResponse response, Type ty { try { - response = (TResponse) handlerMethod.Invoke(handler.ResponseHandler, new object[] { response }); + response = (TResponse) handlerMethod.Invoke(handler.Handler, new object[] { response }); } catch (TargetInvocationException ex) { diff --git a/source/WcfClientProxyGenerator/RetryingWcfActionInvokerProvider.cs b/source/WcfClientProxyGenerator/RetryingWcfActionInvokerProvider.cs index 8549f69..9b9ebab 100644 --- a/source/WcfClientProxyGenerator/RetryingWcfActionInvokerProvider.cs +++ b/source/WcfClientProxyGenerator/RetryingWcfActionInvokerProvider.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.ServiceModel; using System.ServiceModel.Channels; @@ -8,30 +11,182 @@ namespace WcfClientProxyGenerator { + class PredicateHandlerHolder + { + public object Predicate { get; set; } + public object Handler { get; set; } + } + internal class RetryingWcfActionInvokerProvider : IActionInvokerProvider, IRetryingProxyConfigurator where TServiceInterface : class { - private ChannelFactory _channelFactory; - private readonly RetryingWcfActionInvoker _actionInvoker; + private static ConcurrentDictionary>> TypeHierarchyCache + = new ConcurrentDictionary>>(); + + private static ConcurrentDictionary> RequestParameterHandlerPredicateCache + = new ConcurrentDictionary>(); + + private static ConcurrentDictionary> RequestParameterHandlerCache + = new ConcurrentDictionary>(); + + private ChannelFactory channelFactory; + private readonly RetryingWcfActionInvoker actionInvoker; + + private readonly IDictionary> requestArgumentHandlers + = new Dictionary>(); public RetryingWcfActionInvokerProvider() { - _actionInvoker = new RetryingWcfActionInvoker(() => + actionInvoker = new RetryingWcfActionInvoker(() => { - if (_channelFactory == null) + if (channelFactory == null) this.UseDefaultEndpoint(); - return _channelFactory.CreateChannel(); + return channelFactory.CreateChannel(); }); } public IActionInvoker ActionInvoker { - get { return _actionInvoker; } + get { return actionInvoker; } + } + + #region HandleRequestArgument + + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Predicate to filter the request arguments by properties of the request, or the parameter name + /// Delegate that takes a + public void HandleRequestArgument(Func where, Action handler) + { + this.HandleRequestArgument(where, r => + { + handler(r); + return r; + }); } + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Delegate that takes a + public void HandleRequestArgument(Action handler) + { + this.HandleRequestArgument(null, handler); + } + + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Predicate to filter the request arguments by properties of the request, or the parameter name + /// Delegate that takes a and returns a + public void HandleRequestArgument(Func where, Func handler) + { + if (!this.requestArgumentHandlers.ContainsKey(typeof(TArgument))) + this.requestArgumentHandlers.Add(typeof(TArgument), new List()); + + this.requestArgumentHandlers[typeof(TArgument)].Add(new PredicateHandlerHolder + { + Predicate = where, + Handler = handler + }); + } + + /// + /// Allows inspection or modification of request arguments immediately before sending the request. + /// + /// Type or parent type/interface of the argument + /// Delegate that takes a and returns a + public void HandleRequestArgument(Func handler) + { + this.HandleRequestArgument(null, handler); + } + + #region Runtime Handler Resolution + + /// + /// Called into by the dynamically generated proxy + /// + protected TArgument HandleRequestArgument(TArgument argument, string parameterName) + { + // Don't attempt handler resolution if there aren't any registered + if (!this.requestArgumentHandlers.Any()) + return argument; + + argument = ExecuteRequestArgumentHandlers(argument, parameterName); + return argument; + } + + private TArgument ExecuteRequestArgumentHandlers(TArgument requestArgument, string parameterName) + { + Type @type = typeof(TArgument); + var baseTypes = TypeHierarchyCache.GetOrAddSafe(@type, _ => + { + return @type.GetAllInheritedTypes(); + }); + + foreach (var baseType in baseTypes) + requestArgument = this.ExecuteRequestArgumentHandlers(requestArgument, parameterName, baseType); + + return requestArgument; + } + + private TArgument ExecuteRequestArgumentHandlers(TArgument response, string parameterName, Type @type) + { + if (!this.requestArgumentHandlers.ContainsKey(@type)) + return response; + + IList requestParameterHandlerHolders = this.requestArgumentHandlers[@type]; + + MethodInfo predicateInvokeMethod = RequestParameterHandlerPredicateCache.GetOrAddSafe(@type, _ => + { + Type predicateType = typeof(Func<,,>) + .MakeGenericType(@type, typeof(string), typeof(bool)); + + return predicateType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); + }); + + var handlers = requestParameterHandlerHolders + .Where(m => m.Predicate == null + || ((bool) predicateInvokeMethod.Invoke(m.Predicate, new object[] { response, parameterName }))) + .ToList(); + + if (!handlers.Any()) + return response; + + MethodInfo handlerMethod = RequestParameterHandlerCache.GetOrAddSafe(@type, _ => + { + Type actionType = typeof(Func<,>).MakeGenericType(@type, @type); + return actionType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); + }); + + foreach (var handler in handlers) + { + try + { + response = (TArgument) handlerMethod.Invoke(handler.Handler, new object[] { response }); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException; + } + } + + return response; + } + + #endregion + + #endregion + + #region HandleResponse + /// /// Allows inspecting and modifying the object /// before returning the response to the calling method. @@ -43,7 +198,7 @@ public IActionInvoker ActionInvoker /// public void HandleResponse(Predicate @where, Action handler) { - _actionInvoker.AddResponseHandler(r => + actionInvoker.AddResponseHandler(r => { handler(r); return r; @@ -60,7 +215,7 @@ public void HandleResponse(Predicate @where, Action public void HandleResponse(Action handler) { - _actionInvoker.AddResponseHandler(r => + actionInvoker.AddResponseHandler(r => { handler(r); return r; @@ -77,7 +232,7 @@ public void HandleResponse(Action handler) /// public void HandleResponse(Func handler) { - _actionInvoker.AddResponseHandler(handler, null); + actionInvoker.AddResponseHandler(handler, null); } /// @@ -91,9 +246,11 @@ public void HandleResponse(Func handler) /// public void HandleResponse(Predicate @where, Func handler) { - _actionInvoker.AddResponseHandler(handler, @where); + actionInvoker.AddResponseHandler(handler, @where); } + #endregion + #region IRetryingProxyConfigurator /// @@ -102,8 +259,8 @@ public void HandleResponse(Predicate @where, Func public event OnCallBeginHandler OnCallBegin { - add { _actionInvoker.OnCallBegin += value; } - remove { _actionInvoker.OnCallBegin -= value; } + add { actionInvoker.OnCallBegin += value; } + remove { actionInvoker.OnCallBegin -= value; } } /// @@ -111,8 +268,8 @@ public event OnCallBeginHandler OnCallBegin /// public event OnCallSuccessHandler OnCallSuccess { - add { _actionInvoker.OnCallSuccess += value; } - remove { _actionInvoker.OnCallSuccess -= value; } + add { actionInvoker.OnCallSuccess += value; } + remove { actionInvoker.OnCallSuccess -= value; } } /// @@ -120,8 +277,8 @@ public event OnCallSuccessHandler OnCallSuccess /// public event OnInvokeHandler OnBeforeInvoke { - add { _actionInvoker.OnBeforeInvoke += value; } - remove { _actionInvoker.OnBeforeInvoke -= value; } + add { actionInvoker.OnBeforeInvoke += value; } + remove { actionInvoker.OnBeforeInvoke -= value; } } /// @@ -129,8 +286,8 @@ public event OnInvokeHandler OnBeforeInvoke /// public event OnInvokeHandler OnAfterInvoke { - add { _actionInvoker.OnAfterInvoke += value; } - remove { _actionInvoker.OnAfterInvoke -= value; } + add { actionInvoker.OnAfterInvoke += value; } + remove { actionInvoker.OnAfterInvoke -= value; } } /// @@ -138,8 +295,8 @@ public event OnInvokeHandler OnAfterInvoke /// public event OnExceptionHandler OnException { - add { _actionInvoker.OnException += value; } - remove { _actionInvoker.OnException -= value; } + add { actionInvoker.OnException += value; } + remove { actionInvoker.OnException -= value; } } /// @@ -150,12 +307,12 @@ public ChannelFactory ChannelFactory get { // if requested without endpoint set, use default - if (_channelFactory == null) + if (channelFactory == null) { UseDefaultEndpoint(); } - return _channelFactory; + return channelFactory; } } @@ -166,11 +323,11 @@ public void UseDefaultEndpoint() if (typeof(TServiceInterface).GetCustomAttribute() != null) { Type originalServiceInterfaceType = typeof(TServiceInterface).GetInterfaces()[0]; - _channelFactory = ChannelFactoryProvider.GetChannelFactory(originalServiceInterfaceType); + channelFactory = ChannelFactoryProvider.GetChannelFactory(originalServiceInterfaceType); } else { - _channelFactory = ChannelFactoryProvider.GetChannelFactory(typeof(TServiceInterface)); + channelFactory = ChannelFactoryProvider.GetChannelFactory(typeof(TServiceInterface)); } } @@ -179,48 +336,48 @@ public void SetEndpoint(string endpointConfigurationName) if (typeof(TServiceInterface).HasAttribute()) { Type originalServiceInterfaceType = typeof(TServiceInterface).GetInterfaces()[0]; - _channelFactory = ChannelFactoryProvider.GetChannelFactory(endpointConfigurationName, originalServiceInterfaceType); + channelFactory = ChannelFactoryProvider.GetChannelFactory(endpointConfigurationName, originalServiceInterfaceType); } else { - _channelFactory = ChannelFactoryProvider.GetChannelFactory(endpointConfigurationName); + channelFactory = ChannelFactoryProvider.GetChannelFactory(endpointConfigurationName); } } public void SetEndpoint(Binding binding, EndpointAddress endpointAddress) { - _channelFactory = ChannelFactoryProvider.GetChannelFactory(binding, endpointAddress); + channelFactory = ChannelFactoryProvider.GetChannelFactory(binding, endpointAddress); } public void MaximumRetries(int retryCount) { - _actionInvoker.RetryCount = retryCount; + actionInvoker.RetryCount = retryCount; } public void SetDelayPolicy(Func policyFactory) { - _actionInvoker.DelayPolicyFactory = policyFactory; + actionInvoker.DelayPolicyFactory = policyFactory; } public void RetryOnException(Predicate where = null) where TException : Exception { - _actionInvoker.AddExceptionToRetryOn(where); + actionInvoker.AddExceptionToRetryOn(where); } public void RetryOnException(Type exceptionType, Predicate where = null) { - _actionInvoker.AddExceptionToRetryOn(exceptionType, where); + actionInvoker.AddExceptionToRetryOn(exceptionType, where); } public void RetryOnResponse(Predicate where) { - _actionInvoker.AddResponseToRetryOn(where); + actionInvoker.AddResponseToRetryOn(where); } public void RetryFailureExceptionFactory(RetryFailureExceptionFactoryDelegate factory) { - _actionInvoker.RetryFailureExceptionFactory = factory; + actionInvoker.RetryFailureExceptionFactory = factory; } #endregion From d90ac1981d6d9b925691d470fe662abdcee25610 Mon Sep 17 00:00:00 2001 From: John Weber Date: Wed, 15 Oct 2014 12:08:05 -0700 Subject: [PATCH 3/6] Updating readme --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f3baa4d..cc33d00 100644 --- a/README.md +++ b/README.md @@ -144,12 +144,54 @@ will configure the proxy based on the `` as setup in the _app.config_ #### SetEndpoint(Binding binding, EndpointAddress endpointAddress) Configures the proxy to communicate with the endpoint using the given `binding` at the `endpointAddress` +#### HandleRequestArgument\(Func\ where, Action\ handler) +_overload:_ `HandleRequestArgument\(Func\ where, Func\ handler)` + +Sets up the proxy to run handlers on argument values that are used for making WCF requests. An example use case would be to inject authentication keys into all requests where an argument value matches expectation. + +The `TArgument` value can be a type as specific or general as needed. For example, configuring the proxy to handle the `object` type will result in the handler being run for all operation contract arguments, whereas configuring with a sealed type will result in only those types being handled. + +This example sets the `AccessKey` property for all requests inheriting from `IAuthenticatedRequest`: + + var proxy = WcfClientProxy.Create(c => + { + c.HandleRequestArgument(req => + { + req.AccessKey = "..."; + }); + }); + +The `where` condition takes the request object as its first parameter and the actual name of the operation contract parameter secondly. This allows for conditional handling of non-specific types based on naming conventions. + +For example, a service contract defined as: + + [ServiceContract] + public interface IAuthenticatedService + { + [OperationContract] + void OperationOne(string accessKey, string input); + + [OperationContract] + void OperationTwo(string accessKey, string anotherInput); + } + +can have the `accessKey` parameter automatically filled in with the following proxy configuration: + + var proxy = WcfClientProxy.Create(c => + { + c.HandleRequestArgument( + where: (req, paramName) => paramName == "accessKey", + handler: req => "access key value"); + }); + +the proxy can now be called with with any value in the `accessKey` parameter (e.g. `proxy.OperationOne(null, "input value")` and before the request is actually started, the `accessKey` value will be swapped out with `access key value`. + #### HandleResponse\(Predicate\ where, Action\ handler) _overload:_ `HandleResponse(Predicate where, Func handler)` Sets up the proxy to allow inspection and manipulation of responses from the service. -The `TResponse` value can be a type as specific or general as needed. For instance, `c.HandleResponse(...)` will only handle responses of type `SealedResponseType` whereas `c.HandleResponse(...)` will be fired for all responses. +Similar to `HandleRequestArgument`, the `TResponse` value can be a type as specific or general as needed. For instance, `c.HandleResponse(...)` will only handle responses of type `SealedResponseType` whereas `c.HandleResponse(...)` will be fired for all responses. For example, if sensitive information is needed to be stripped out of certain response messages, `HandleResponse` can be used to do this. From ee277ba05230d1e756c16330db9ea9ddd13a24ee Mon Sep 17 00:00:00 2001 From: John Weber Date: Wed, 15 Oct 2014 12:10:33 -0700 Subject: [PATCH 4/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc33d00..6cfd71b 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ will configure the proxy based on the `` as setup in the _app.config_ Configures the proxy to communicate with the endpoint using the given `binding` at the `endpointAddress` #### HandleRequestArgument\(Func\ where, Action\ handler) -_overload:_ `HandleRequestArgument\(Func\ where, Func\ handler)` +_overload:_ `HandleRequestArgument(Func where, Func handler)` Sets up the proxy to run handlers on argument values that are used for making WCF requests. An example use case would be to inject authentication keys into all requests where an argument value matches expectation. From be3997f093f06fc4f71284cfb23c4886a2ee708b Mon Sep 17 00:00:00 2001 From: John Weber Date: Wed, 22 Oct 2014 17:35:00 -0700 Subject: [PATCH 5/6] Updated version --- .semver | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.semver b/.semver index 6520791..0c9a2ea 100644 --- a/.semver +++ b/.semver @@ -1,5 +1,5 @@ --- :major: 2 -:minor: 0 -:patch: 9 -:special: '' +:minor: 1 +:patch: 0 +:special: beta From 3a9586615e5d77da972e67c338db2eff0ea8547c Mon Sep 17 00:00:00 2001 From: John Weber Date: Wed, 22 Oct 2014 17:55:40 -0700 Subject: [PATCH 6/6] Removed beta tag from v2.1.0 --- .semver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.semver b/.semver index 0c9a2ea..4b3e029 100644 --- a/.semver +++ b/.semver @@ -2,4 +2,4 @@ :major: 2 :minor: 1 :patch: 0 -:special: beta +:special: ''