diff --git a/.semver b/.semver index 4856f93..0c72a70 100644 --- a/.semver +++ b/.semver @@ -1,5 +1,5 @@ --- :major: 2 :minor: 0 -:patch: 6 +:patch: 7 :special: '' diff --git a/README.md b/README.md index 2ecff66..30219e6 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,13 @@ For example, if sensitive information is needed to be stripped out of certain re #### MaximumRetries(int retryCount) Sets the maximum amount of times the the proxy will additionally attempt to call the service in the event it encounters a known retry-friendly exception or response. If retryCount is set to 0, then only one request attempt will be made. +#### RetryFailureExceptionFactory(RetryFailureExceptionFactoryDelegate) +By default, if the value configured in the `MaximumRetries` call is exceeded (i.e. a WCF call could not successfully be made after more than 1 attempt), a `WcfRetryFailedException` exception is thrown. In some cases, it makes sense to throw an exception of a different type. This method can be used to define a factory that generates the `Exception` that is thrown. + +The signature of this delegate is: + + public delegate Exception RetryFailureExceptionFactoryDelegate(int retryAttempts, Exception lastException, InvokeInfo invocationInfo); + #### RetryOnException\(Predicate\ where = null) Configures the proxy to retry calls when it encounters arbitrary exceptions. The optional `Predicate` can be used to refine properties of the Exception that it should retry on. diff --git a/source/WcfClientProxyGenerator.Tests/ProxyTests.cs b/source/WcfClientProxyGenerator.Tests/ProxyTests.cs index da06928..c75ae50 100644 --- a/source/WcfClientProxyGenerator.Tests/ProxyTests.cs +++ b/source/WcfClientProxyGenerator.Tests/ProxyTests.cs @@ -68,18 +68,24 @@ public void Proxy_ReturnsExpectedValue_WhenCallingService() [Test] public void Proxy_CanCallVoidMethod() { + 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)); proxy.VoidMethod("good"); + + if (!resetEvent.WaitOne(300)) + Assert.Fail("Timeout occurred when waiting for callback"); } [Test] @@ -217,6 +223,38 @@ public void Proxy_ConfiguredWithAtLeastOnRetry_CallsServiceMultipleTimes_AndThro mockService.Verify(m => m.VoidMethod("test"), Times.Exactly(2)); } + [Test] + public void Proxy_ConfiguredWithAtLeastOnRetry_CallsServiceMultipleTimes_AndThrowsCustomRetryFailureException() + { + var mockService = new Mock(); + mockService + .Setup(m => m.VoidMethod("test")) + .Throws(new FaultException()); + + var serviceHost = InProcTestFactory.CreateHost(new TestServiceImpl(mockService)); + + var proxy = WcfClientProxy.Create(c => + { + c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress); + c.MaximumRetries(1); + c.RetryOnException(); + c.RetryFailureExceptionFactory((attempts, exception, info) => + { + string message = string.Format("Failed call to {0} {1} times", info.MethodName, attempts); + return new CustomFailureException(message, exception); + }); + }); + + Assert.That(() => proxy.VoidMethod("test"), Throws.TypeOf()); + mockService.Verify(m => m.VoidMethod("test"), Times.Exactly(2)); + } + + public class CustomFailureException : Exception + { + public CustomFailureException(string message, Exception innerException) : base(message, innerException) + {} + } + #region Out Parameter Support [Test] diff --git a/source/WcfClientProxyGenerator/DefaultProxyConfigurator.cs b/source/WcfClientProxyGenerator/DefaultProxyConfigurator.cs index 1b58ad2..ca2c3fb 100644 --- a/source/WcfClientProxyGenerator/DefaultProxyConfigurator.cs +++ b/source/WcfClientProxyGenerator/DefaultProxyConfigurator.cs @@ -13,5 +13,8 @@ public static void Configure(IRetryingProxyConfigurator proxy public static readonly Func DefaultDelayPolicyFactory = () => new LinearBackoffDelayPolicy(TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(10)); + + public static readonly RetryFailureExceptionFactoryDelegate DefaultRetryFailureExceptionFactory + = (retryCount, lastException, invokInfo) => new WcfRetryFailedException(string.Format("WCF call failed after {0} retries.", retryCount), lastException); } } \ No newline at end of file diff --git a/source/WcfClientProxyGenerator/IRetryingProxyConfigurator.cs b/source/WcfClientProxyGenerator/IRetryingProxyConfigurator.cs index 622b7c3..7606b33 100644 --- a/source/WcfClientProxyGenerator/IRetryingProxyConfigurator.cs +++ b/source/WcfClientProxyGenerator/IRetryingProxyConfigurator.cs @@ -3,6 +3,14 @@ namespace WcfClientProxyGenerator { + /// + /// Signature to use when defining a custom exception to use in place of the built in + /// + /// The total amount of retry attempts made before ultimately failing + /// The last exception (if any) encountered when retrying the WCF calls + /// Additional information about the WCF request + public delegate Exception RetryFailureExceptionFactoryDelegate(int retryAttempts, Exception lastException, InvokeInfo invocationInfo); + /// /// Proxy configuration options for managing retry logic /// @@ -43,5 +51,13 @@ void RetryOnException(Predicate where = null) /// /// Predicate defining the case to retry on void RetryOnResponse(Predicate where); + + /// + /// Overrides the default use of the type when + /// the WCF call fails more than and uses the + /// type generated by this in its place. + /// + /// Delegate with int, Exception and InvokeInfo arguments + void RetryFailureExceptionFactory(RetryFailureExceptionFactoryDelegate factory); } } diff --git a/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs b/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs index 7f853dc..23debfe 100644 --- a/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs +++ b/source/WcfClientProxyGenerator/RetryingWcfActionInvoker.cs @@ -47,6 +47,7 @@ public RetryingWcfActionInvoker( { RetryCount = retryCount; DelayPolicyFactory = delayPolicyFactory ?? DefaultProxyConfigurator.DefaultDelayPolicyFactory; + RetryFailureExceptionFactory = DefaultProxyConfigurator.DefaultRetryFailureExceptionFactory; _wcfActionProviderCreator = wcfActionProviderCreator; _retryPredicates = new Dictionary @@ -70,6 +71,8 @@ public RetryingWcfActionInvoker( public Func DelayPolicyFactory { get; set; } + public RetryFailureExceptionFactoryDelegate RetryFailureExceptionFactory { get; set; } + /// /// Event that is fired immediately before the service method will be called. This event /// is called only once per request. @@ -226,9 +229,8 @@ public TResponse Invoke(Func method, In if (RetryCount == 0) throw mostRecentException; - throw new WcfRetryFailedException( - string.Format("WCF call failed after {0} retries.", this.RetryCount), - mostRecentException); + var exception = this.RetryFailureExceptionFactory(this.RetryCount, mostRecentException, invokeInfo); + throw exception; } } finally @@ -312,9 +314,8 @@ public async Task InvokeAsync(Func(Predicate where) _actionInvoker.AddResponseToRetryOn(where); } + public void RetryFailureExceptionFactory(RetryFailureExceptionFactoryDelegate factory) + { + _actionInvoker.RetryFailureExceptionFactory = factory; + } + #endregion } } \ No newline at end of file