├── .gitignore ├── .semver ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── build.bat ├── lib ├── ILVisualizer │ ├── ClrTest.Reflection.ILReader.dll │ └── ClrTest.Reflection.ILVisualizer.dll └── NuGet.exe ├── rakefile.rb ├── setup-build-env.bat ├── setup-build-env.sh └── source ├── WcfClientProxyGenerator.Tests ├── App.config ├── AsyncTests.cs ├── BenchmarkTests.cs ├── ChannelFactoryProviderTests.cs ├── DefaultProxyConfiguratorTests.cs ├── DelayPolicyTests.cs ├── DictionaryExtensionsTests.cs ├── DuplexProxyTests.cs ├── DynamicProxyTypeGeneratorTests.cs ├── FastActivatorTests.cs ├── Infrastructure │ ├── ExceptionDetailService.cs │ ├── IChildService.cs │ ├── IOutParamTestService.cs │ ├── ITestService.cs │ ├── ITestService2.cs │ ├── ITestServiceSingleEndpointConfig.cs │ ├── InProcTestFactory.cs │ ├── ResetEventExt.cs │ ├── TestBase.cs │ └── TrailingSlashOnNamespaceService.cs ├── Issues │ └── Issue25.cs ├── Properties │ └── AssemblyInfo.cs ├── ProxyTests.cs ├── RetryingWcfActionInvokerTests.cs ├── SanityTests.cs ├── TypeExtensionsTests.cs ├── WcfClientProxyGenerator.Tests.csproj └── packages.config ├── WcfClientProxyGenerator.sln ├── WcfClientProxyGenerator.sln.DotSettings └── WcfClientProxyGenerator ├── Async ├── AsyncProxy.cs └── GeneratedAsyncInterfaceAttribute.cs ├── ChannelFactoryProvider.cs ├── DefaultProxyConfigurator.cs ├── DynamicProxyTypeGenerator.cs ├── IActionInvoker.cs ├── IActionInvokerProvider.cs ├── IProxyConfigurator.cs ├── IRetryingProxyConfigurator.cs ├── InstanceContext.cs ├── InvokeInfo.cs ├── OnCallBeginHandler.cs ├── OnCallSuccessHandler.cs ├── OnExceptionHandler.cs ├── OnInvokeHandler.cs ├── Policy ├── ConstantDelayPolicy.cs ├── ExponentialBackoffDelayPolicy.cs ├── IDelayPolicy.cs └── LinearBackoffDelayPolicy.cs ├── Properties └── AssemblyInfo.cs ├── RetryingWcfActionInvoker.cs ├── RetryingWcfActionInvokerProvider.cs ├── Util ├── AttributeExtensions.cs ├── DictionaryExtensions.cs ├── FastActivator.cs └── TypeExtensions.cs ├── WcfClientProxy.cs ├── WcfClientProxyGenerator.csproj └── WcfRetryFailedException.cs /.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | *.pdb 3 | *.user 4 | *.aps 5 | *.pch 6 | *.vspscc 7 | *_i.c 8 | *_p.c 9 | *.ncb 10 | *.suo 11 | *.tlb 12 | *.tlh 13 | *.bak 14 | *.cache 15 | *.ilk 16 | *.log 17 | *.lib 18 | *.sbr 19 | *.scc 20 | [Bb]in 21 | [Dd]ebug*/ 22 | obj/ 23 | [Rr]elease*/ 24 | _ReSharper*/ 25 | [Tt]est[Rr]esult* 26 | [Bb]uild[Ll]og.* 27 | *.[Pp]ublish.xml 28 | BuildProcessTemplates/* 29 | Published/* 30 | [Tt]humbs.db 31 | [Uu]pgradeLog*.[Xx][Mm][Ll] 32 | _[Uu]pgradeReport_Files*/ 33 | *.build.csdef 34 | [Pp]ackage/* 35 | ASPNETDB.MDF 36 | aspnetdb_log.ldf 37 | logs/ 38 | *.docstates 39 | data 40 | *.log 41 | *.log.* 42 | lib/.nuget_packages 43 | build-artifacts 44 | build 45 | source/VersionInfo.cs 46 | .settings 47 | source/packages 48 | -------------------------------------------------------------------------------- /.semver: -------------------------------------------------------------------------------- 1 | --- 2 | :major: 2 3 | :minor: 2 4 | :patch: 6 5 | :special: '' 6 | :metadata: '' 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'rake' 3 | gem 'albacore', '2.5.8' 4 | gem 'semver2' 5 | gem 'rubyzip' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | albacore (2.5.8) 5 | map (~> 6.5) 6 | nokogiri (~> 1.5) 7 | rake (~> 10) 8 | semver2 (~> 3.4) 9 | map (6.6.0) 10 | mini_portile2 (2.0.0) 11 | nokogiri (1.6.7.2-x64-mingw32) 12 | mini_portile2 (~> 2.0.0.rc2) 13 | nokogiri (1.6.7.2-x86-mingw32) 14 | mini_portile2 (~> 2.0.0.rc2) 15 | rake (10.5.0) 16 | rubyzip (1.1.7) 17 | semver2 (3.4.2) 18 | 19 | PLATFORMS 20 | x64-mingw32 21 | x86-mingw32 22 | 23 | DEPENDENCIES 24 | albacore (= 2.5.8) 25 | rake 26 | rubyzip 27 | semver2 28 | 29 | BUNDLED WITH 30 | 1.11.2 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WcfClientProxyGenerator 2 | ========================= 3 | Utility to generate fault tolerant and retry capable dynamic proxies for WCF services based on the WCF service interface. 4 | 5 | With normal Service Reference or ChannelFactory instantiated clients, care must be taken to abort and recreate the client in the event that a communication fault occurs. The goal of this project is to provide an easy-to-use method of creating WCF clients that are self healing and tolerant of temporary network communication errors while still being as transparently useable as default WCF clients. 6 | 7 | Generated proxies fully support the C# 5 async/await syntax for service contracts that define `Task` based async operations. Automatic extension of non-async ready service contracts with full async support can be created through use of the [`WcfClientProxy.CreateAsyncProxy()`](README.md#async-support) factory method. 8 | 9 | Installation 10 | ------------ 11 | 12 | NuGet> Install-Package WcfClientProxyGenerator 13 | 14 | Examples 15 | -------- 16 | The following interface defines the contract for the service: 17 | 18 | [ServiceContract] 19 | public interface ITestService 20 | { 21 | [OperationContract] 22 | string ServiceMethod(string request); 23 | 24 | [OperationContract] 25 | Status ServiceMethod2(); 26 | } 27 | 28 | The proxy can then be created based on this interface by using the `Create` method of the proxy generator: 29 | 30 | ITestService proxy = WcfClientProxy.Create(c => c.SetEndpoint(binding, endpointAddress)); 31 | 32 | The proxy generated is now tolerant of faults and communication exceptions. In this example, if the first request results in a faulted channel, you would normally have to manually dispose of it. With the proxy instance, you can continue using it. 33 | 34 | ITestService proxy = WcfClientProxy.Create(c => c.SetEndpoint("testServiceConfiguration")); 35 | var response = proxy.ServiceMethod("request"); 36 | var response2 = proxy.ServiceMethod("request2"); // even if the previous request resulted in a FaultException this call will still work 37 | 38 | If there are known exceptions or responses that you would like the proxy to retry calls on, it can be configured to retry when a custom exception or response is encountered: 39 | 40 | var proxy = WcfClientProxy.Create(c => 41 | { 42 | c.SetEndpoint("testServiceConfiguration"); 43 | c.RetryOnException(); 44 | c.RetryOnException(e => e.Message == "retry only for this message"); 45 | c.RetryOnResponse(r => r.StatusCode == 503 || r.StatusCode == 504); 46 | }); 47 | 48 | Responses can also be intercepted and transformed by the proxy through use of the `HandleResponse` configuration: 49 | 50 | var proxy = WcfClientProxy.Create(c => 51 | { 52 | c.SetEndpoint("testServiceConfiguration"); 53 | c.HandleResponse(where: r => r.StatusCode == 500, handler: r => 54 | { 55 | throw new Exception("InternalServerError"); 56 | }); 57 | }); 58 | 59 | Using this same synchronous interface, async/await calls can be made to the `ServiceMethod` operation by creating an async enabled proxy: 60 | 61 | IAsyncProxy asyncProxy = WcfClientProxy.CreateAsyncProxy(); 62 | 63 | Making the request asynchronously is done by using the `CallAsync` method: 64 | 65 | string response = await asyncProxy.CallAsync(m => m.ServiceMethod("request")); 66 | 67 | Synchronous calls are still supported using the `IAsyncProxy` proxy by accessing the `Client` property: 68 | 69 | string response = asyncProxy.Client.ServiceMethod("request"); 70 | 71 | Usage 72 | ----- 73 | To create a proxy, use the `WcfClientProxy.Create()` method. There are multiple overloads that can be used to setup and configure the proxy. 74 | 75 | #### WcfClientProxy.Create\() 76 | Calling create without passing any configuration in will configure the proxy using the `endpoint` section in your config where the `contract` attribute matches `TServiceInterface`. If more than one `endpoint` section exists, an `InvalidOperationException` is thrown. The alternate overloads must be used to select the appropriate endpoint configuration. 77 | 78 | #### WcfClientProxy.Create\(string endpointConfigurationName) 79 | This is a shortcut to using the overload that accepts an `Action`. It's the same as calling `WcfClientProxy.Create(c => c.SetEndpoint(endpointConfigurationName))`. 80 | 81 | #### WcfClientProxy.Create\(Action\ config) 82 | Exposes the full configuration available. See the [Configuration](#configuration) section of the documentation. 83 | #### Duplex Proxy 84 | To create a duplex proxy, you must use the last factory method `Create(Action config)` from above. 85 | 86 | Then you can specify the instance context which holds the implementation of your callback receiver. 87 | 88 | [ServiceContract(CallbackContract = typeof(ITestCallback))] 89 | public interface IServiceWithCallback 90 | { 91 | } 92 | 93 | public interface ICallbackContract 94 | { 95 | [OperationContract] 96 | void OnCallback(string data); 97 | } 98 | 99 | public class TestReceiver : ICallbackContract 100 | { 101 | public void OnCallback(string data) 102 | { 103 | } 104 | } 105 | 106 | var receiver = new TestReceiver(); 107 | vat ctx = new InstanceContext(receiver); 108 | var proxy = WcfClientProxy.Create(c => 109 | { 110 | c.SetEndpoint(binding, endpointAddress, ctx); 111 | }); 112 | 113 | var receiver = new TestReceiver(); 114 | var proxy = WcfClientProxy.Create(c => 115 | { 116 | c.SetEndpoint(binding, endpointAddress, receiver); 117 | }); 118 | 119 | Async Support 120 | ------------- 121 | 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: 122 | 123 | [ServiceContract] 124 | interface IService 125 | { 126 | [OperationContract] 127 | int MakeCall(string input); 128 | } 129 | 130 | then the proxy returned by calling `WcfClientProxy.Create()` will automatically define a `Task MakeCallAsync(string input)` operation contract method at runtime. 131 | 132 | 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. 133 | 134 | The `int MakeCall(string input)` method can now be called asynchronously like: 135 | 136 | var proxy = WcfClientProxy.CreateAsyncProxy(); 137 | int result = await proxy.CallAsync(s => s.MakeCall("test")); 138 | 139 | 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: 140 | 141 | var proxy = WcfClientProxy.Create(); 142 | int result = await ((dynamic) proxy).MakeCallAsync("test"); 143 | 144 | WCF service contract interfaces that define task based async methods at compile will automatically work with the C# 5 async/await support. 145 | 146 | var proxy = WcfClientProxy.Create(); 147 | int result = await proxy.MakeCallAsync("test"); 148 | 149 | ### Async Limitations 150 | 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. 151 | 152 | Configuration 153 | ------------- 154 | When calling the `WcfClientProxy.Create()` method, a configuration Action is used to setup the proxy. The following configuration options are available at the proxy creation time: 155 | 156 | If no configurator is given, then a `client` configuration section with the full name of the service interface type is looked for. If no `client` configuration section is present, an `InvalidOperationException` is thrown. 157 | 158 | #### SetEndpoint(string endpointConfigurationName) 159 | Configures the proxy to communicate with the endpoint as configured in the _app.config_ or _web.config_ `` section. The `endpointConfigurationName` value needs to match the _name_ attribute value of the ``. 160 | 161 | For example, using: 162 | 163 | var proxy = WcfClientProxy.Create(c => c.SetEndpoint("WSHttpBinding_ITestService")) 164 | 165 | will configure the proxy based on the `` as setup in the _app.config_: 166 | 167 | 168 | 169 | 170 | 171 | 175 | 176 | 177 | 178 | 179 | #### SetEndpoint(Binding binding, EndpointAddress endpointAddress) 180 | Configures the proxy to communicate with the endpoint using the given `binding` at the `endpointAddress` 181 | 182 | #### HandleRequestArgument\(Func\ where, Action\ handler) 183 | _overload:_ `HandleRequestArgument(Func where, Func handler)` 184 | 185 | 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. 186 | 187 | 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. 188 | 189 | This example sets the `AccessKey` property for all requests inheriting from `IAuthenticatedRequest`: 190 | 191 | var proxy = WcfClientProxy.Create(c => 192 | { 193 | c.HandleRequestArgument(req => 194 | { 195 | req.AccessKey = "..."; 196 | }); 197 | }); 198 | 199 | 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. 200 | 201 | For example, a service contract defined as: 202 | 203 | [ServiceContract] 204 | public interface IAuthenticatedService 205 | { 206 | [OperationContract] 207 | void OperationOne(string accessKey, string input); 208 | 209 | [OperationContract] 210 | void OperationTwo(string accessKey, string anotherInput); 211 | } 212 | 213 | can have the `accessKey` parameter automatically filled in with the following proxy configuration: 214 | 215 | var proxy = WcfClientProxy.Create(c => 216 | { 217 | c.HandleRequestArgument( 218 | where: (req, paramName) => paramName == "accessKey", 219 | handler: req => "access key value"); 220 | }); 221 | 222 | 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`. 223 | 224 | #### HandleResponse\(Predicate\ where, Action\ handler) 225 | _overload:_ `HandleResponse(Predicate where, Func handler)` 226 | 227 | Sets up the proxy to allow inspection and manipulation of responses from the service. 228 | 229 | 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. 230 | 231 | For example, if sensitive information is needed to be stripped out of certain response messages, `HandleResponse` can be used to do this. 232 | 233 | var proxy = WcfClientProxy.Create(c => 234 | { 235 | c.HandleResponse(where: r => r.Password != null, handler: r => 236 | { 237 | r.Password = null; 238 | return r; 239 | }); 240 | }); 241 | 242 | `HandleResponse` can also be used to throw exceptions on the client side based on the inspection of responses. 243 | 244 | Multiple calls to `HandleResponse` can be made with different variations of `TResponse` and the `Predicate`. With this in mind, it is possible to have more than one response handler manipulate a `TResponse` object. It is important to note that there is *no guarantee* in the order that the response handlers operate at runtime. 245 | 246 | #### MaximumRetries(int retryCount) 247 | 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. 248 | 249 | #### RetryFailureExceptionFactory(RetryFailureExceptionFactoryDelegate) 250 | 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. 251 | 252 | The signature of this delegate is: 253 | 254 | public delegate Exception RetryFailureExceptionFactoryDelegate(int retryAttempts, Exception lastException, InvokeInfo invocationInfo); 255 | 256 | #### RetryOnException\(Predicate\ where = null) 257 | 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. 258 | 259 | By default, the following Exception types are registered to trigger a call retry if the `MaximumRetries` count is greater than 0: 260 | 261 | * ChannelTerminatedException 262 | * EndpointNotFoundException 263 | * ServerTooBusyException 264 | 265 | #### RetryOnResponse\(Predicate\ where) 266 | Configures the proxy to retry calls based on conditions in the response from the service. 267 | 268 | For example, if your response objects all inherit from a base `IResponseStatus` interface and you would like to retry calls when certain status codes are returned, the proxy can be configured as such: 269 | 270 | ITestService proxy = WcfClientProxy.Create(c => 271 | { 272 | c.SetEndpoint("testServiceConfiguration"); 273 | c.RetryOnResponse(r => r.StatusCode == 503 || r.StatusCode == 504); 274 | }); 275 | 276 | The proxy will now retry calls made into the service when it detects a `503` or `504` status code. 277 | 278 | #### SetDelayPolicy(Func\ policyFactory) 279 | Configures how the proxy will handle pausing between failed calls to the WCF service. See the [Delay Policies](#delay-policies) section below. 280 | 281 | Instances of `IDelayPolicy` are generated through the provided factory for each call made to the WCF service. 282 | 283 | For example, to wait an exponentially growing amount of time starting at 500 milliseconds between failures: 284 | 285 | ITestService proxy = WcfClientProxy.Create(c => 286 | { 287 | c.SetDelayPolicy(() => new ExponentialBackoffDelayPolicy(TimeSpan.FromMilliseconds(500))); 288 | }); 289 | 290 | #### OnCallBegin 291 | Event handler that is fired immediately before a service request is made. 292 | 293 | #### OnCallSuccess 294 | Event handler that is fired after the service request completes successfully. Returns the count of call attempts made and the overall elapsed time that the request took. 295 | 296 | #### OnBeforeInvoke & OnAfterInvoke 297 | Allows configuring event handlers that are called every time a method is called on the service. 298 | Events will receive information which method was called and with what parameters in the `OnInvokeHandlerArguments` structure. 299 | 300 | The `OnBeforeInvoke` event will fire every time a method is attempted to be called, and thus can be fired multiple times if you have a retry policy in place. 301 | The `OnAfterInvoke` event will fire once after a successful call to a service method. 302 | 303 | For example, to log all service calls: 304 | 305 | ````csharp 306 | ITestService proxy = WcfClientProxy.Create(c => 307 | c.OnBeforeInvoke += (sender, args) => { 308 | Console.WriteLine("{0}.{1} called with parameters: {2}", 309 | args.ServiceType.Name, args.InvokeInfo.MethodName, 310 | String.Join(", ", args.InvokeInfo.Parameters)); 311 | }; 312 | c.OnAfterInvoke += (sender, args) => { 313 | Console.WriteLine("{0}.{1} returned value: {2}", 314 | args.ServiceType.Name, args.InvokeInfo.MethodName, 315 | args.InvokeInfo.ReturnValue); 316 | }; 317 | }); 318 | int result = proxy.AddNumbers(3, 42); 319 | ```` 320 | 321 | Will print: 322 | 323 | ITestService.AddNumbers called with parameters: 3, 42 324 | ITestService.AddNumbers returned value: 45 325 | 326 | #### OnException 327 | Like [OnBeforeInvoke and OnAfterInvoke](#onbeforeinvoke--onafterinvoke), but for exceptions. 328 | Allows configuring an event handler that is called if a service method call results in an exception, 329 | such as a communication failure or a FaultException originating from the service. 330 | Configuring this event handler will not affect to the exception that is thrown to user code. 331 | 332 | For example, to log information of all exceptions that happen: 333 | ````csharp 334 | ITestService proxy = WcfClientProxy.Create(c => 335 | c.OnException += (sender, args) => { 336 | Console.WriteLine("Exception during service call to {0}.{1}: {2}", 337 | args.ServiceType.Name, args.InvokeInfo.MethodName, 338 | args.Exception); 339 | }; 340 | }); 341 | ```` 342 | 343 | #### ChannelFactory 344 | Allows access to WCF extensibility features from code for advanced use cases. 345 | Can be used, for example, to add endpoint behaviors and change client credentials used to connect to services. 346 | 347 | Delay Policies 348 | -------------- 349 | Delay policies are classes that implement the `WcfClientProxyGenerator.Policy.IDelayPolicy` interface. There are a handful of pre-defined delay policies to use in this namespace. 350 | 351 | If not specified, the `LinearBackoffDelayPolicy` will be used with a minimum delay of 500 milliseconds and a maximum of 10 seconds. 352 | 353 | 354 | #### ConstantDelayPolicy 355 | Waits a constant amount of time between call failures regardless of how many calls have failed. 356 | 357 | #### LinearBackoffDelayPolicy 358 | Waits a linearly increasing amount of time between call failures that grows based on how many previous calls have failed. This policy also accepts a maximum delay argument which insures the policy will never wait more than the defined maximum value. 359 | 360 | #### ExponentialBackoffDelayPolicy 361 | Same as the LinearBackoffDelayPolicy, but increases the amount of time between call failures exponentially. 362 | 363 | 364 | License 365 | ------- 366 | Apache 2.0 367 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo Compiling 4 | call rake compile 5 | IF NOT %ERRORLEVEL% == 0 goto FAILED 6 | 7 | :FAILED 8 | -------------------------------------------------------------------------------- /lib/ILVisualizer/ClrTest.Reflection.ILReader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jweber/WcfClientProxyGenerator/18a45e8f536f89bef6f48a441f53d73f1532c566/lib/ILVisualizer/ClrTest.Reflection.ILReader.dll -------------------------------------------------------------------------------- /lib/ILVisualizer/ClrTest.Reflection.ILVisualizer.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jweber/WcfClientProxyGenerator/18a45e8f536f89bef6f48a441f53d73f1532c566/lib/ILVisualizer/ClrTest.Reflection.ILVisualizer.dll -------------------------------------------------------------------------------- /lib/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jweber/WcfClientProxyGenerator/18a45e8f536f89bef6f48a441f53d73f1532c566/lib/NuGet.exe -------------------------------------------------------------------------------- /rakefile.rb: -------------------------------------------------------------------------------- 1 | require 'albacore' 2 | require 'fileutils' 3 | require 'semver' 4 | 5 | CLR_TOOLS_VERSION = 'v4.0.30319' 6 | ARTIFACTS_PATH = File.expand_path("./build") 7 | PROJECT_NAME = "WcfClientProxyGenerator" 8 | 9 | $config = ENV['config'] || 'Debug' 10 | $nuget_api_key = ENV['nuget_api_key'] 11 | $teamcity = (not ENV['TEAMCITY_VERSION'].nil?) 12 | 13 | task :default => :compile 14 | 15 | desc 'Generate the VersionInfo.cs class' 16 | asmver :version => [:versioning] do |a| 17 | git_data = commit_data() 18 | revision_hash = git_data[0] 19 | revision_date = git_data[1] 20 | 21 | a.file_path = "source/VersionInfo.cs" 22 | a.attributes assembly_version: FORMAL_VERSION, 23 | assembly_file_version: FORMAL_VERSION, 24 | assembly_product: PROJECT_NAME, 25 | assembly_description: "Git comit hash: #{revision_hash} - #{revision_date}", 26 | assembly_informational_version: BUILD_VERSION 27 | end 28 | 29 | desc 'Compile the project' 30 | task :compile => ['nuget:restore'] do 31 | Rake::Task['build:net45'].invoke() 32 | end 33 | 34 | namespace :build do 35 | task :all => [:net45] do 36 | end 37 | 38 | build :clean do |b| 39 | b.prop 'configuration', $config 40 | b.prop 'framework', 'NET45' 41 | b.target = ['clean'] 42 | b.file = "source/#{PROJECT_NAME}.sln" 43 | end 44 | 45 | build :net45 => ["nuget:restore", "version"] do |b| 46 | b.prop 'configuration', $config 47 | b.prop 'framework', 'NET45' 48 | b.target = ['clean', 'rebuild'] 49 | b.file = "source/#{PROJECT_NAME}.sln" 50 | end 51 | end 52 | 53 | desc 'Run tests' 54 | test_runner :test => :compile do |tests| 55 | include FileUtils 56 | mkpath ARTIFACTS_PATH unless Dir.exists? ARTIFACTS_PATH 57 | 58 | tests.files = FileList["source/#{PROJECT_NAME}.Tests/bin/#{$config}/#{PROJECT_NAME}.Tests.dll"] 59 | tests.exe = nunit_path 60 | tests.add_parameter "--labels=ALL" 61 | tests.add_parameter "--noresult" 62 | 63 | if $teamcity 64 | tests.add_parameter "--teamcity" 65 | end 66 | end 67 | 68 | desc 'Cleans build artifacts' 69 | task :clean => 'build:clean' do 70 | rm_rf ARTIFACTS_PATH 71 | end 72 | 73 | desc 'Builds release package' 74 | task :package => 'build:all' do 75 | include FileUtils 76 | 77 | assemble_path = File.join(ARTIFACTS_PATH, "assemble") 78 | 79 | mkpath ARTIFACTS_PATH unless Dir.exists? ARTIFACTS_PATH 80 | rm_rf Dir.glob(File.join(ARTIFACTS_PATH, "**/*.zip")) 81 | rm_rf assemble_path if Dir.exists? assemble_path 82 | 83 | mkpath assemble_path unless Dir.exists? assemble_path 84 | rm_rf Dir.glob(File.join(assemble_path, "**/*")) 85 | 86 | cp_r Dir.glob("source/#{PROJECT_NAME}/bin/#{$config}/**"), assemble_path, :verbose => true 87 | rm Dir.glob("#{assemble_path}/log.*") 88 | 89 | zip_directory(assemble_path, File.join(ARTIFACTS_PATH, "#{PROJECT_NAME}-#{BUILD_VERSION}.zip")) 90 | rm_rf assemble_path if Dir.exists? assemble_path 91 | end 92 | 93 | namespace :nuget do 94 | desc 'Restores nuget packages' 95 | nugets_restore :restore do |p| 96 | p.out = "source/packages" 97 | p.exe = nuget_path 98 | end 99 | 100 | desc 'Creates the nuspec file' 101 | nugets_pack :pack => ['clean', 'build:all'] do |p| 102 | mkpath ARTIFACTS_PATH unless Dir.exists? ARTIFACTS_PATH 103 | 104 | p.configuration = 'Release' 105 | p.target = 'net45' 106 | p.files = FileList['source/WcfClientProxyGenerator/WcfClientProxyGenerator.csproj'] 107 | p.exe = nuget_path 108 | p.out = 'build' 109 | 110 | p.with_metadata do |m| 111 | m.id = PROJECT_NAME 112 | m.version = ENV['NUGET_VERSION'] 113 | m.authors = "j.weber" 114 | m.description = "Utility to generate fault tolerant and highly configurable client proxies for WCF services based on WCF ServiceContracts. Supports making async calls using non async-ready ServiceContracts." 115 | m.project_url = "https://github.com/jweber/WcfClientProxyGenerator" 116 | m.title = PROJECT_NAME 117 | m.tags = "wcf service client proxy dynamic async" 118 | end 119 | end 120 | 121 | task :push => [:pack] do 122 | raise "No NuGet API key was defined" unless $nuget_api_key 123 | 124 | nuget_package = "build\\#{PROJECT_NAME}.#{ENV['NUGET_VERSION']}.nupkg" 125 | sh "#{nuget_path} push #{nuget_package} #{$nuget_api_key} -NonInteractive -Source https://www.nuget.org/api/v2/package" 126 | end 127 | end 128 | 129 | task :versioning do 130 | ver = SemVer.find 131 | #revision = (ENV['BUILD_NUMBER'] || ver.patch).to_i 132 | #ver = SemVer.new(ver.major, ver.minor, revision, ver.special) 133 | 134 | if ver.special != '' 135 | ENV['BUILD_VERSION'] = BUILD_VERSION = ver.format("%M.%m.%p.%s") + ".#{commit_data()[0]}" 136 | ENV['NUGET_VERSION'] = NUGET_VERSION = ver.format("%M.%m.%p-%s") 137 | else 138 | ENV['BUILD_VERSION'] = BUILD_VERSION = ver.format("%M.%m.%p") + ".#{commit_data()[0]}" 139 | ENV['NUGET_VERSION'] = NUGET_VERSION = ver.format("%M.%m.%p") 140 | end 141 | 142 | ENV['FORMAL_VERSION'] = FORMAL_VERSION = "#{ ver.format("%M.%m.%p")}" 143 | puts "##teamcity[buildNumber '#{BUILD_VERSION}']" 144 | end 145 | 146 | def nunit_path() 147 | File.join(Dir.glob(File.join('source', 'packages', "nunit.consolerunner.*")).sort.last, "tools", "nunit3-console.exe") 148 | end 149 | 150 | def nuget_path() 151 | File.join('lib', 'NuGet.exe') 152 | end 153 | 154 | def zip_directory(assemble_path, output_path) 155 | require 'albacore/tools/zippy' 156 | 157 | zf = Zippy.new assemble_path, output_path 158 | zf.write 159 | 160 | # zip = ZipDirectory.new 161 | # zip.directories_to_zip assemble_path 162 | # zip.output_path = File.dirname(output_path) 163 | # zip.output_file = File.basename(output_path) 164 | # zip.execute 165 | end 166 | 167 | def commit_data 168 | begin 169 | commit = `git rev-parse --short HEAD`.chomp() 170 | git_date = `git log -1 --date=iso --pretty=format:%ad` 171 | commit_date = DateTime.parse(git_date).strftime("%Y-%m-%d %H%M%S") 172 | rescue Exception => e 173 | puts e.inspect 174 | commit = (ENV['BUILD_VCS_NUMBER'] || "000000") 175 | commit_date = Time.new.strftime("%Y-%m-%d %H%M%S") 176 | end 177 | [commit, commit_date] 178 | end 179 | 180 | -------------------------------------------------------------------------------- /setup-build-env.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo Setting up rake environment for building 4 | call gem install bundler 5 | call bundle 6 | -------------------------------------------------------------------------------- /setup-build-env.sh: -------------------------------------------------------------------------------- 1 | gem install bundler 2 | bundle 3 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 27 | 28 | 32 | 33 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/AsyncTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ServiceModel; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using NSubstitute; 7 | using NUnit.Framework; 8 | using WcfClientProxyGenerator.Tests.Infrastructure; 9 | 10 | namespace WcfClientProxyGenerator.Tests 11 | { 12 | [TestFixture] 13 | public class AsyncTests 14 | { 15 | [Test] 16 | public async Task ServiceContractDefinedAsyncMethod_WithReturnValue() 17 | { 18 | var resetEvent = new AutoResetEvent(false); 19 | 20 | var service = Substitute.For(); 21 | 22 | service 23 | .ReturnMethodDefinedAsync("test") 24 | .Returns(Task.FromResult("response")) 25 | .AndDoes(m => 26 | { 27 | Console.WriteLine("Callback thread: " + Thread.CurrentThread.ManagedThreadId); 28 | resetEvent.Set(); 29 | }); 30 | 31 | 32 | var proxy = service.StartHostAndProxy(); 33 | 34 | Console.WriteLine("Caller thread: " + Thread.CurrentThread.ManagedThreadId); 35 | 36 | string result = await proxy.ReturnMethodDefinedAsync("test"); 37 | 38 | Console.WriteLine("Contination thread: " + Thread.CurrentThread.ManagedThreadId); 39 | 40 | Assert.That(result, Is.EqualTo("response")); 41 | 42 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(2))) 43 | Assert.Fail("Callback never called"); 44 | } 45 | 46 | [Test] 47 | public async Task ServiceContractDefinedAsyncMethod_WithNoReturnValue() 48 | { 49 | var resetEvent = new AutoResetEvent(false); 50 | 51 | var service = Substitute.For(); 52 | 53 | service 54 | .VoidMethodDefinedAsync("test") 55 | .Returns(Task.FromResult(true)) 56 | .AndDoes(m => 57 | { 58 | Console.WriteLine("Callback thread: " + Thread.CurrentThread.ManagedThreadId); 59 | resetEvent.Set(); 60 | }); 61 | 62 | var proxy = service.StartHostAndProxy(); 63 | 64 | Console.WriteLine("Caller thread: " + Thread.CurrentThread.ManagedThreadId); 65 | 66 | await proxy.VoidMethodDefinedAsync("test"); 67 | 68 | Console.WriteLine("Contination thread: " + Thread.CurrentThread.ManagedThreadId); 69 | 70 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(2))) 71 | Assert.Fail("Callback never called"); 72 | } 73 | 74 | [Test] 75 | public async Task CallAsync_MethodWithReturnValue() 76 | { 77 | var service = Substitute.For(); 78 | 79 | service 80 | .TestMethod("good") 81 | .Returns("BAD", "OK"); 82 | 83 | service 84 | .TestMethod("second", "two") 85 | .Returns("2"); 86 | 87 | var proxy = service.StartHostAndAsyncProxy(c => 88 | { 89 | c.MaximumRetries(1); 90 | c.RetryOnResponse(s => s == "BAD"); 91 | }); 92 | 93 | Console.WriteLine("Caller thread: " + Thread.CurrentThread.ManagedThreadId); 94 | 95 | string result = await proxy.CallAsync(m => m.TestMethod("good")); 96 | string result2 = await proxy.CallAsync(m => m.TestMethod("second", "two")); 97 | 98 | Console.WriteLine("Continuation thread: " + Thread.CurrentThread.ManagedThreadId); 99 | 100 | Assert.That(result, Is.EqualTo("OK")); 101 | Assert.That(result2, Is.EqualTo("2")); 102 | } 103 | 104 | [Test] 105 | public async Task CallAsync_MethodWithReturnValue2() 106 | { 107 | var request = new Request() { RequestMessage = "test" }; 108 | 109 | var service = Substitute.For(); 110 | service 111 | .TestMethodComplex(Arg.Any()) 112 | .Returns( 113 | new Response {ResponseMessage = "test", StatusCode = 1}, 114 | new Response {ResponseMessage = "test", StatusCode = 0}); 115 | 116 | var proxy = service.StartHostAndAsyncProxy(c => 117 | { 118 | c.MaximumRetries(1); 119 | c.RetryOnResponse(s => s.StatusCode == 1); 120 | }); 121 | 122 | Console.WriteLine("Caller thread: " + Thread.CurrentThread.ManagedThreadId); 123 | 124 | var result = await proxy.CallAsync(m => m.TestMethodComplex(request)); 125 | 126 | Console.WriteLine("Continuation thread: " + Thread.CurrentThread.ManagedThreadId); 127 | 128 | Assert.That(result.StatusCode, Is.EqualTo(0)); 129 | Assert.That(result.ResponseMessage, Is.EqualTo("test")); 130 | } 131 | 132 | [Test] 133 | public async Task CallAsync_VoidMethod() 134 | { 135 | var resetEvent = new AutoResetEvent(false); 136 | 137 | var service = Substitute.For(); 138 | 139 | service 140 | .When(m => m.VoidMethod("good")) 141 | .Do(m => 142 | { 143 | Console.WriteLine("Callback thread: " + Thread.CurrentThread.ManagedThreadId); 144 | resetEvent.Set(); 145 | }); 146 | 147 | var proxy = service.StartHostAndAsyncProxy(); 148 | 149 | Console.WriteLine("Caller thread: " + Thread.CurrentThread.ManagedThreadId); 150 | 151 | await proxy.CallAsync(m => m.VoidMethod("good")); 152 | 153 | Console.WriteLine("Continuation thread: " + Thread.CurrentThread.ManagedThreadId); 154 | 155 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(2))) 156 | Assert.Fail("Callback never triggered"); 157 | } 158 | 159 | [Test] 160 | public async Task CallAsync_OneWayOperation() 161 | { 162 | var resetEvent = new AutoResetEvent(false); 163 | 164 | var service = Substitute.For(); 165 | 166 | service 167 | .When(m => m.OneWay(Arg.Any())) 168 | .Do(m => 169 | { 170 | Assert.That(m.Arg(), Is.EqualTo("test")); 171 | 172 | Console.WriteLine("Callback thread: " + Thread.CurrentThread.ManagedThreadId); 173 | resetEvent.Set(); 174 | }); 175 | 176 | 177 | var proxy = service.StartHostAndAsyncProxy(); 178 | 179 | await proxy.CallAsync(m => m.OneWay("test")); 180 | 181 | Console.WriteLine("Continuation thread: " + Thread.CurrentThread.ManagedThreadId); 182 | 183 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10))) 184 | Assert.Fail("Callback not entered"); 185 | } 186 | 187 | [Test] 188 | public void CallAsync_MultipleConcurrentCalls() 189 | { 190 | int iterations = 20; 191 | 192 | var service = Substitute.For(); 193 | 194 | service 195 | .TestMethod(Arg.Any()) 196 | .Returns(m => $"Echo: {m.Arg()}") 197 | .AndDoes(m => Console.WriteLine($"Callback: {m.Arg()}")); 198 | 199 | var proxy = service.StartHostAndAsyncProxy(); 200 | 201 | var tasks = new List(); 202 | for (int i = 0; i < iterations; i++) 203 | { 204 | int i1 = i; 205 | tasks.Add(proxy.CallAsync(m => m.TestMethod(i1.ToString()))); 206 | Console.WriteLine("Queued task: " + i); 207 | } 208 | 209 | Console.WriteLine("Waiting tasks..."); 210 | 211 | Task.WaitAll(tasks.ToArray()); 212 | 213 | for (int i = 0; i < iterations; i++) 214 | { 215 | string result = ((Task) tasks[i]).Result; 216 | Assert.That(result, Is.EqualTo("Echo: " + i)); 217 | } 218 | } 219 | 220 | [Test] 221 | public void CallAsync_CanCallIntoSyncProxy() 222 | { 223 | var service = Substitute.For(); 224 | 225 | service 226 | .TestMethod("good") 227 | .Returns("OK"); 228 | 229 | var proxy = service.StartHostAndAsyncProxy(); 230 | 231 | string result = proxy.Client.TestMethod("good"); 232 | Assert.That(result, Is.EqualTo("OK")); 233 | } 234 | 235 | [Test] 236 | public void CallAsync_CallingMethodWithByRefParams_ThrowsNotSupportedException() 237 | { 238 | var service = Substitute.For(); 239 | 240 | byte[] expectedOutParam = { 0x01 }; 241 | 242 | service 243 | .SingleOutParam(out expectedOutParam) 244 | .Returns(100); 245 | 246 | var proxy = service.StartHostAndAsyncProxy(); 247 | 248 | byte[] resultingOutParam; 249 | 250 | Assert.That(() => proxy.CallAsync(m => m.SingleOutParam(out resultingOutParam)), Throws.TypeOf()); 251 | } 252 | } 253 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/BenchmarkTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq; 3 | using NSubstitute; 4 | using NUnit.Framework; 5 | using WcfClientProxyGenerator.Tests.Infrastructure; 6 | 7 | namespace WcfClientProxyGenerator.Tests 8 | { 9 | [TestFixture] 10 | public class BenchmarkTests 11 | { 12 | [Test, Explicit] 13 | public void Benchmark_RetryingWcfActionInvoker_Invoke() 14 | { 15 | var service = Substitute.For(); 16 | 17 | service 18 | .TestMethodComplex(Arg.Any()) 19 | .Returns(new Response { StatusCode = 1 }); 20 | 21 | var actionInvoker = new RetryingWcfActionInvoker( 22 | () => service); 23 | 24 | actionInvoker.AddResponseToRetryOn(r => r.StatusCode == 100); 25 | 26 | var sw = Stopwatch.StartNew(); 27 | foreach (var i in Enumerable.Range(0, 300000)) 28 | { 29 | var response = actionInvoker.Invoke(s => s.TestMethodComplex(new Request())); 30 | Assert.That(response.StatusCode, Is.EqualTo(1)); 31 | } 32 | sw.Stop(); 33 | 34 | Trace.WriteLine(string.Format("Complete in {0}", sw.Elapsed)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/ChannelFactoryProviderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ServiceModel; 3 | using System.ServiceModel.Description; 4 | using NUnit.Framework; 5 | using WcfClientProxyGenerator.Tests.Infrastructure; 6 | 7 | namespace WcfClientProxyGenerator.Tests 8 | { 9 | [TestFixture] 10 | public class ChannelFactoryProviderTests 11 | { 12 | /// 13 | /// Issue #19 exposed a failure where using the endpointConfigurationName 14 | /// to generate a proxy would not use the dynamically generated *Async service 15 | /// interface to create the channel. This would cause a NotSupportedException 16 | /// to be thrown when attempting to make a service call using a generated *Async 17 | /// method. 18 | /// 19 | [Test, Description("Github issue #19")] 20 | public void UsingEndpointConfigurationName_BuildServiceEndpointForChannelFactory_UsingDynamicallyGeneratedAsyncInterface() 21 | { 22 | var proxy = WcfClientProxy.CreateAsyncProxy("ITestService"); 23 | 24 | var exception = Assert.ThrowsAsync( 25 | () => proxy.CallAsync(m => m.IntMethod()), 26 | message: "Expected EndpointNotFoundException (since ITestService address is not running)"); 27 | 28 | Assert.That(exception.Message, Does.StartWith("There was no endpoint listening at ")); 29 | } 30 | 31 | [Test] 32 | public void ChannelFactory_FromEndpointConfigurationName_WithBehaviorConfiguration_ContainsConfiguredBehaviors() 33 | { 34 | var factory = ChannelFactoryProvider.GetChannelFactory("BehaviorService"); 35 | var behavior = factory.Endpoint.Behaviors.Find(); 36 | 37 | Assert.That(behavior, Is.Not.Null); 38 | Assert.That(behavior.HelpEnabled, Is.True); 39 | } 40 | 41 | [Test] 42 | public void ChannelFactory_FromEndpointConfigurationName_WithoutBehaviorConfiguratio_DoesNotContainEndpointBehaviors() 43 | { 44 | var factory = ChannelFactoryProvider.GetChannelFactory("ITestService"); 45 | var behavior = factory.Endpoint.Behaviors.Find(); 46 | 47 | Assert.That(behavior, Is.Null); 48 | } 49 | 50 | [Test] 51 | public void ChannelFactories_WithIdenticalConfiguration_AreSameInstance_ForCodeBasedConfiguration() 52 | { 53 | var factory1 = ChannelFactoryProvider.GetChannelFactory( 54 | new WSHttpBinding(), 55 | new EndpointAddress("http://localhost:23456/TestService")); 56 | 57 | var factory2 = ChannelFactoryProvider.GetChannelFactory( 58 | new WSHttpBinding(), 59 | new EndpointAddress("http://localhost:23456/TestService")); 60 | 61 | Assert.AreSame(factory1, factory2); 62 | } 63 | 64 | [Test] 65 | public void ChannelFactories_WithNonIdenticalConfiguration_AreNotSameInstance_ForCodeBasedConfiguration() 66 | { 67 | var factory1 = ChannelFactoryProvider.GetChannelFactory( 68 | new WSHttpBinding(), 69 | new EndpointAddress("http://localhost:23456/TestService")); 70 | 71 | var factory2 = ChannelFactoryProvider.GetChannelFactory( 72 | new WSHttpBinding(), 73 | new EndpointAddress("http://localhost:23456/TestService2")); 74 | 75 | Assert.AreNotSame(factory1, factory2); 76 | } 77 | 78 | [Test] 79 | public void ChannelFactories_WithIdenticalConfiguration_AreSameInstance_ForEndpointConfigurationName() 80 | { 81 | var factory1 = ChannelFactoryProvider.GetChannelFactory("ITestService"); 82 | var factory2 = ChannelFactoryProvider.GetChannelFactory("ITestService"); 83 | 84 | Assert.AreSame(factory1, factory2); 85 | } 86 | 87 | [Test] 88 | public void ChannelFactories_WithNonIdenticalConfiguration_AreNotSameInstance__ForEndpointConfigurationName() 89 | { 90 | var factory1 = ChannelFactoryProvider.GetChannelFactory("ITestService"); 91 | var factory2 = ChannelFactoryProvider.GetChannelFactory("ITestService2"); 92 | 93 | Assert.AreNotSame(factory1, factory2); 94 | } 95 | 96 | [Test] 97 | public void ChannelFactory_WithSingleConfigurationForContract_UsesDefaultConfiguration() 98 | { 99 | var factory = ChannelFactoryProvider.GetChannelFactory(); 100 | 101 | Assert.That(factory.Endpoint.Address.ToString(), Is.EqualTo("http://localhost:23456/TestService2")); 102 | Assert.That(factory.Endpoint.Binding.Name, Is.EqualTo("WSHttpBinding")); 103 | } 104 | 105 | [Test] 106 | public void ServiceInterface_WithMultipleClientEndpoints_ThrowsInvalidOperationException_WhenUsingDefaultCtor() 107 | { 108 | Assert.That(() => ChannelFactoryProvider.GetChannelFactory(), Throws.TypeOf()); 109 | } 110 | 111 | [Test] 112 | public void ChannelFactory_ClientEndpoint_WithCustomBindingConfiguration() 113 | { 114 | var factory = ChannelFactoryProvider.GetChannelFactory("ITestService"); 115 | 116 | Assert.That(factory.Endpoint.Address.ToString(), Is.EqualTo("http://localhost:23456/TestService")); 117 | 118 | var binding = (WSHttpBinding) factory.Endpoint.Binding; 119 | Assert.That(binding.Name, Is.EqualTo("wsHttpBinding_ITestService")); 120 | Assert.That(binding.MaxReceivedMessageSize, Is.EqualTo(12345)); 121 | } 122 | 123 | [Test] 124 | public void NoConfigurationForServiceType_ThrowsInvalidOperationException() 125 | { 126 | Assert.That(() => ChannelFactoryProvider.GetChannelFactory(), Throws.TypeOf()); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/DefaultProxyConfiguratorTests.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute; 2 | using NUnit.Framework; 3 | using WcfClientProxyGenerator.Tests.Infrastructure; 4 | 5 | namespace WcfClientProxyGenerator.Tests 6 | { 7 | [TestFixture] 8 | public class DefaultProxyConfiguratorTests 9 | { 10 | [Test] 11 | public void SetEndpoint_IsCalledWith_FullNamespaceOfServiceInterface() 12 | { 13 | var config = Substitute.For(); 14 | 15 | DefaultProxyConfigurator.Configure(config); 16 | 17 | config 18 | .Received(1) 19 | .UseDefaultEndpoint(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/DelayPolicyTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using WcfClientProxyGenerator.Policy; 5 | 6 | namespace WcfClientProxyGenerator.Tests 7 | { 8 | [TestFixture] 9 | public class DelayPolicyTests 10 | { 11 | #region ConstantDelayPolicy 12 | 13 | [Test] 14 | public void ConstantDelayPolicy_ReturnsSameDelay_ForRangeOfInput() 15 | { 16 | TimeSpan expectedDelay = TimeSpan.FromMilliseconds(100); 17 | var policy = new ConstantDelayPolicy(expectedDelay); 18 | 19 | foreach (int i in Enumerable.Range(0, 100)) 20 | { 21 | Assert.AreEqual(expectedDelay, policy.GetDelay(i)); 22 | } 23 | } 24 | 25 | [Test] 26 | public void ConstantDelayPolicy_ReturnsSameDelay_ForRandomInput() 27 | { 28 | TimeSpan expectedDelay = TimeSpan.FromSeconds(10); 29 | var policy = new ConstantDelayPolicy(expectedDelay); 30 | 31 | var random = new Random(); 32 | foreach (int i in Enumerable.Range(0, 100)) 33 | { 34 | Assert.AreEqual(expectedDelay, policy.GetDelay(random.Next())); 35 | } 36 | } 37 | 38 | #endregion 39 | 40 | #region LinearBackoffDelayPolicy 41 | 42 | [Test] 43 | public void LinearBackoffDelayPolicy_BacksOffLinearly() 44 | { 45 | TimeSpan minimumDelay = TimeSpan.FromSeconds(2); 46 | var policy = new LinearBackoffDelayPolicy(minimumDelay); 47 | 48 | Assert.AreEqual(TimeSpan.FromSeconds(2), policy.GetDelay(0)); 49 | Assert.AreEqual(TimeSpan.FromSeconds(4), policy.GetDelay(1)); 50 | Assert.AreEqual(TimeSpan.FromSeconds(6), policy.GetDelay(2)); 51 | Assert.AreEqual(TimeSpan.FromSeconds(8), policy.GetDelay(3)); 52 | Assert.AreEqual(TimeSpan.FromSeconds(10), policy.GetDelay(4)); 53 | Assert.AreEqual(TimeSpan.FromSeconds(12), policy.GetDelay(5)); 54 | } 55 | 56 | [Test] 57 | public void LinearBackoffDelayPolicy_BacksOffLinearly_UntilReachingMaximumDelay() 58 | { 59 | TimeSpan minimumDelay = TimeSpan.FromSeconds(2); 60 | TimeSpan maximumDelay = TimeSpan.FromSeconds(7); 61 | var policy = new LinearBackoffDelayPolicy(minimumDelay, maximumDelay); 62 | 63 | Assert.AreEqual(TimeSpan.FromSeconds(2), policy.GetDelay(0)); 64 | Assert.AreEqual(TimeSpan.FromSeconds(4), policy.GetDelay(1)); 65 | Assert.AreEqual(TimeSpan.FromSeconds(6), policy.GetDelay(2)); 66 | Assert.AreEqual(TimeSpan.FromSeconds(7), policy.GetDelay(3)); 67 | Assert.AreEqual(TimeSpan.FromSeconds(7), policy.GetDelay(4)); 68 | Assert.AreEqual(TimeSpan.FromSeconds(7), policy.GetDelay(5)); 69 | } 70 | 71 | #endregion 72 | 73 | #region ExponentialBackoffDelayPolicy 74 | 75 | [Test] 76 | public void ExponentialBackoffDelayPolicy_BacksOffExponentially() 77 | { 78 | TimeSpan minimumDelay = TimeSpan.FromSeconds(2); 79 | var policy = new ExponentialBackoffDelayPolicy(minimumDelay); 80 | 81 | Assert.AreEqual(TimeSpan.FromSeconds(2), policy.GetDelay(0)); 82 | Assert.AreEqual(TimeSpan.FromSeconds(4), policy.GetDelay(1)); 83 | Assert.AreEqual(TimeSpan.FromSeconds(8), policy.GetDelay(2)); 84 | Assert.AreEqual(TimeSpan.FromSeconds(16), policy.GetDelay(3)); 85 | Assert.AreEqual(TimeSpan.FromSeconds(32), policy.GetDelay(4)); 86 | Assert.AreEqual(TimeSpan.FromSeconds(64), policy.GetDelay(5)); 87 | Assert.AreEqual(TimeSpan.FromSeconds(128), policy.GetDelay(6)); 88 | } 89 | 90 | [Test] 91 | public void ExponentialBackoffDelayPolicy_BacksOffExponentially_UntilReachingMaximumDelay() 92 | { 93 | TimeSpan minimumDelay = TimeSpan.FromSeconds(2); 94 | TimeSpan maximumDelay = TimeSpan.FromSeconds(20); 95 | var policy = new ExponentialBackoffDelayPolicy(minimumDelay, maximumDelay); 96 | 97 | Assert.AreEqual(TimeSpan.FromSeconds(2), policy.GetDelay(0)); 98 | Assert.AreEqual(TimeSpan.FromSeconds(4), policy.GetDelay(1)); 99 | Assert.AreEqual(TimeSpan.FromSeconds(8), policy.GetDelay(2)); 100 | Assert.AreEqual(TimeSpan.FromSeconds(16), policy.GetDelay(3)); 101 | Assert.AreEqual(TimeSpan.FromSeconds(20), policy.GetDelay(4)); 102 | Assert.AreEqual(TimeSpan.FromSeconds(20), policy.GetDelay(5)); 103 | Assert.AreEqual(TimeSpan.FromSeconds(20), policy.GetDelay(6)); 104 | } 105 | 106 | #endregion 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/DictionaryExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | using NUnit.Framework; 5 | using WcfClientProxyGenerator.Util; 6 | 7 | namespace WcfClientProxyGenerator.Tests 8 | { 9 | [TestFixture] 10 | public class DictionaryExtensionsTests 11 | { 12 | [Test, Explicit] 13 | public void Unsafe_ConcurrentDictionary_GetOrAdd_CallsValueFactoryMultipleTimes() 14 | { 15 | var dictionary = new ConcurrentDictionary(); 16 | string thread1Message = null, thread2Message = null; 17 | 18 | var thread1 = new Thread(() => 19 | dictionary.GetOrAdd("key", _ => 20 | { 21 | Thread.SpinWait(10000); 22 | thread1Message = "thread 1"; 23 | return thread1Message; 24 | })); 25 | 26 | var thread2 = new Thread(() => 27 | dictionary.GetOrAdd("key", _ => 28 | { 29 | Thread.SpinWait(10000); 30 | thread2Message = "thread 2"; 31 | return thread2Message; 32 | })); 33 | 34 | thread1.Start(); 35 | thread2.Start(); 36 | thread1.Join(); 37 | thread2.Join(); 38 | 39 | Assert.That(thread1Message, Is.EqualTo("thread 1")); 40 | Assert.That(thread2Message, Is.EqualTo("thread 2")); 41 | } 42 | 43 | [Test] 44 | public void Safe_ConcurrentDictionary_GetOrAdd_CallsValueFactoryOnlyOnce() 45 | { 46 | var dictionary = new ConcurrentDictionary>(); 47 | string thread1Message = null, thread2Message = null; 48 | 49 | var thread1 = new Thread(() => 50 | dictionary.GetOrAddSafe("key", _ => 51 | { 52 | Thread.SpinWait(10000); 53 | thread1Message = "thread 1"; 54 | return thread1Message; 55 | })); 56 | 57 | var thread2 = new Thread(() => 58 | dictionary.GetOrAddSafe("key", _ => 59 | { 60 | Thread.SpinWait(10000); 61 | thread2Message = "thread 2"; 62 | return thread2Message; 63 | })); 64 | 65 | thread1.Start(); 66 | thread2.Start(); 67 | thread1.Join(); 68 | thread2.Join(); 69 | 70 | bool thread1AndNotThread2 = thread1Message == "thread 1" && thread2Message == null; 71 | bool thread2AndNotThread1 = thread2Message == "thread 2" && thread1Message == null; 72 | 73 | Assert.IsTrue(thread1AndNotThread2 || thread2AndNotThread1); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/DuplexProxyTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ServiceModel; 3 | using System.Threading; 4 | using NSubstitute; 5 | using NUnit.Framework; 6 | using WcfClientProxyGenerator.Tests.Infrastructure; 7 | 8 | namespace WcfClientProxyGenerator.Tests 9 | { 10 | [TestFixture] 11 | public class DuplexProxyTests 12 | { 13 | [Test] 14 | public void DuplexService_TriggersCallback() 15 | { 16 | var resetEvent = new AutoResetEvent(false); 17 | 18 | var serviceHost = InProcTestFactory.CreateHost(new DuplexService()); 19 | 20 | var callback = Substitute.For(); 21 | 22 | callback 23 | .TestCallback(Arg.Any()) 24 | .Returns(m => m.Arg()) 25 | .AndDoes(_ => resetEvent.Set()); 26 | 27 | var proxy = WcfClientProxy.Create(c => 28 | { 29 | c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress, callback); 30 | }); 31 | 32 | proxy.Test("test"); 33 | 34 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10))) 35 | Assert.Fail("Callback not entered"); 36 | } 37 | 38 | [Test] 39 | public void DuplexService_OneWayOperation_TriggersCallback() 40 | { 41 | var resetEvent = new AutoResetEvent(false); 42 | 43 | var serviceHost = InProcTestFactory.CreateHost(new DuplexService()); 44 | 45 | var callback = Substitute.For(); 46 | 47 | callback 48 | .When(m => m.OneWayCallback(Arg.Any())) 49 | .Do(_ => resetEvent.Set()); 50 | 51 | var proxy = WcfClientProxy.Create(c => 52 | { 53 | c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress, callback); 54 | }); 55 | 56 | proxy.OneWay("test"); 57 | 58 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10))) 59 | Assert.Fail("Callback not entered"); 60 | } 61 | 62 | [Test] 63 | public void DuplexService_WithInstanceContext_TriggersCallback() 64 | { 65 | var resetEvent = new AutoResetEvent(false); 66 | 67 | var serviceHost = InProcTestFactory.CreateHost(new DuplexService()); 68 | 69 | var callback = Substitute.For(); 70 | 71 | callback 72 | .TestCallback(Arg.Any()) 73 | .Returns(m => m.Arg()) 74 | .AndDoes(_ => resetEvent.Set()); 75 | 76 | InstanceContext ctx = new InstanceContext(callback); 77 | var proxy = WcfClientProxy.Create(c => 78 | { 79 | c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress, ctx); 80 | }); 81 | 82 | proxy.Test("test"); 83 | 84 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(10))) 85 | Assert.Fail("Callback not entered"); 86 | } 87 | } 88 | 89 | [ServiceContract(CallbackContract = typeof(IDuplexServiceCallback))] 90 | public interface IDuplexService 91 | { 92 | [OperationContract] 93 | string Test(string input); 94 | 95 | [OperationContract(IsOneWay = true)] 96 | void OneWay(string input); 97 | } 98 | 99 | [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Reentrant)] 100 | public class DuplexService : IDuplexService 101 | { 102 | public string Test(string input) 103 | { 104 | var callBackResponse = Callback.TestCallback(input); 105 | return $"Method Echo: {callBackResponse}"; 106 | } 107 | 108 | public void OneWay(string input) 109 | { 110 | Callback.OneWayCallback(input); 111 | } 112 | 113 | IDuplexServiceCallback Callback => OperationContext.Current.GetCallbackChannel(); 114 | } 115 | 116 | public interface IDuplexServiceCallback 117 | { 118 | [OperationContract] 119 | string TestCallback(string input); 120 | 121 | [OperationContract(IsOneWay = true)] 122 | void OneWayCallback(string input); 123 | } 124 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/DynamicProxyTypeGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.ServiceModel; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | using WcfClientProxyGenerator.Tests.Infrastructure; 8 | 9 | namespace WcfClientProxyGenerator.Tests 10 | { 11 | #region Test support 12 | 13 | [ServiceContract] 14 | public interface IOverloadedService 15 | { 16 | [OperationContract] 17 | int Method(string input); 18 | 19 | [OperationContract(Name = "Method2")] 20 | int Method(string input, string input2); 21 | } 22 | 23 | [AttributeUsage(AttributeTargets.Interface)] 24 | public class CustomServiceAttributeAttribute : Attribute 25 | { 26 | public const string CtorArg = "hello world"; 27 | public const int NumberProperty = 100; 28 | 29 | public CustomServiceAttributeAttribute(string name) 30 | { 31 | Name = name; 32 | } 33 | 34 | public string Name { get; } 35 | public int Number { get; set; } 36 | } 37 | 38 | [AttributeUsage(AttributeTargets.Method)] 39 | public class CustomMethodAttributeAttribute : Attribute 40 | { 41 | public const string CtorArg = "method"; 42 | public const int NumberProperty = 200; 43 | 44 | public CustomMethodAttributeAttribute(string name) 45 | { 46 | Name = name; 47 | } 48 | 49 | public string Name { get; } 50 | public int Number { get; set; } 51 | } 52 | 53 | [ServiceContract] 54 | [CustomServiceAttribute(CustomServiceAttributeAttribute.CtorArg, Number = CustomServiceAttributeAttribute.NumberProperty)] 55 | public interface ICustomAttributeService 56 | { 57 | [OperationContract] 58 | [CustomMethodAttribute(CustomMethodAttributeAttribute.CtorArg, Number = CustomMethodAttributeAttribute.NumberProperty)] 59 | string Method(string input); 60 | 61 | [OperationContract] 62 | [FaultContract(typeof(Exception))] 63 | string FaultMethod(string input); 64 | 65 | [OperationContract] 66 | [ServiceKnownType(typeof(string))] 67 | string KnownTypeMethod(string input); 68 | } 69 | 70 | [ServiceContract] 71 | public interface IAsyncTestInterface 72 | { 73 | [OperationContract] 74 | string ReturnMethod(string input); 75 | 76 | [OperationContract] 77 | void VoidMethod(string input); 78 | 79 | [OperationContract] 80 | Task ReturnMethodDefinedAsync(string input); 81 | 82 | [OperationContract] 83 | Task VoidMethodDefinedAsync(string input); 84 | } 85 | 86 | [ServiceContract] 87 | public interface IOperationContractInterface 88 | { 89 | [OperationContract] 90 | string DefaultActionAndReplyAction(); 91 | 92 | [OperationContract(Action = "NewAction")] 93 | string CustomAction(); 94 | 95 | [OperationContract(ReplyAction = "NewReplyAction")] 96 | string CustomReplyAction(); 97 | 98 | [OperationContract(Name = "NewName")] 99 | string CustomName(); 100 | } 101 | 102 | [ServiceContract] 103 | public interface IAsyncTestInterface2 104 | { 105 | [OperationContract(Action = "Method", ReplyAction = "MethodResponse")] 106 | string Method(string input); 107 | 108 | [OperationContract(Action = "Method", ReplyAction = "MethodResponse")] 109 | Task MethodAsync(string input); 110 | } 111 | 112 | [ServiceContract] 113 | public interface IAsyncTestInterface3 114 | { 115 | [OperationContract()] 116 | string Method(string input); 117 | 118 | [OperationContract(Action = "http://tempuri.org/IAsyncTestInterface3/Method", ReplyAction = "http://tempuri.org/IAsyncTestInterface3/MethodResponse")] 119 | Task MethodAsync(string input); 120 | } 121 | 122 | #endregion 123 | 124 | [TestFixture] 125 | public class DynamicProxyTypeGeneratorTests 126 | { 127 | [SetUp] 128 | public void Setup() 129 | { 130 | DynamicProxyAssembly.Initialize(); 131 | } 132 | 133 | private GeneratedTypes GenerateTypes() 134 | where TServiceInterface : class 135 | { 136 | return DynamicProxyTypeGenerator 137 | .GenerateTypes>(); 138 | } 139 | 140 | [Test] 141 | public void ContractsWithOverloadedMethods_DoNotDuplicateSupportMethods() 142 | { 143 | Assert.That(() => this.GenerateTypes(), Throws.Nothing); 144 | } 145 | 146 | [Test] 147 | public void CustomAttributes_AreCopiedTo_GeneratedInterface() 148 | { 149 | var types = this.GenerateTypes(); 150 | var attr = types.AsyncInterface.GetCustomAttribute(); 151 | 152 | Assert.That(attr.Name, Is.EqualTo(CustomServiceAttributeAttribute.CtorArg)); 153 | Assert.That(attr.Number, Is.EqualTo(CustomServiceAttributeAttribute.NumberProperty)); 154 | } 155 | 156 | [Test] 157 | public void CustomAttributes_AreCopiedTo_GeneratedMethods() 158 | { 159 | var types = this.GenerateTypes(); 160 | 161 | var method = types.AsyncInterface 162 | .GetMethods() 163 | .First(m => m.Name.StartsWith(nameof(ICustomAttributeService.Method), StringComparison.Ordinal)); 164 | 165 | var attr = method.GetCustomAttribute(); 166 | 167 | Assert.That(attr.Name, Is.EqualTo(CustomMethodAttributeAttribute.CtorArg)); 168 | Assert.That(attr.Number, Is.EqualTo(CustomMethodAttributeAttribute.NumberProperty)); 169 | } 170 | 171 | [Test] 172 | public void FaultContractAttributes_AreNotCopiedTo_GeneratedAsyncMethods() 173 | { 174 | var types = this.GenerateTypes(); 175 | 176 | var method = types.AsyncInterface 177 | .GetMethods() 178 | .First(m => m.Name.StartsWith(nameof(ICustomAttributeService.FaultMethod), StringComparison.Ordinal)); 179 | 180 | var attr = method.GetCustomAttribute(); 181 | 182 | Assert.That(attr, Is.Null); 183 | } 184 | 185 | [Test] 186 | public void AsyncInterface_AsyncMethodSignature_IsCreatedForSyncMethodWithReturnValue() 187 | { 188 | var types = this.GenerateTypes(); 189 | 190 | var asyncMethod = types.AsyncInterface.GetMethod("ReturnMethodAsync"); 191 | Assert.That(asyncMethod, Is.Not.Null); 192 | Assert.That(asyncMethod.ReturnType, Is.EqualTo(typeof(Task))); 193 | } 194 | 195 | [Test] 196 | public void AsyncInterface_AsyncMethodSignature_IsCreatedForSyncMethodWithoutReturnValue() 197 | { 198 | var types = this.GenerateTypes(); 199 | 200 | var asyncMethod = types.AsyncInterface.GetMethod("VoidMethodAsync"); 201 | Assert.That(asyncMethod, Is.Not.Null); 202 | Assert.That(asyncMethod.ReturnType, Is.EqualTo(typeof(Task))); 203 | } 204 | 205 | [Test] 206 | public void AsyncInterface_AsyncMethodSignature_IsNotCreated_ForAlreadyAsyncMethodWithReturnValue() 207 | { 208 | var types = this.GenerateTypes(); 209 | 210 | var asyncMethod = types.AsyncInterface.GetMethods() 211 | .Where(m => m.Name.StartsWith("ReturnMethodDefinedAsync")); 212 | 213 | Assert.That(asyncMethod, Is.Null.Or.Empty); 214 | } 215 | 216 | [Test] 217 | public void AsyncInterface_AsyncMethodSignature_IsNotCreated_ForAlreadyAsyncMethodWithNoReturnValue() 218 | { 219 | var types = this.GenerateTypes(); 220 | 221 | var asyncMethod = types.AsyncInterface.GetMethods() 222 | .Where(m => m.Name.StartsWith("VoidMethodDefinedAsync")); 223 | 224 | Assert.That(asyncMethod, Is.Null.Or.Empty); 225 | } 226 | 227 | [Test] 228 | public void AsyncInterface_Default_ActionAndReplyAction_AttributeValuesAreGenerated() 229 | { 230 | var types = this.GenerateTypes(); 231 | 232 | var asyncMethod = types.AsyncInterface.GetMethod("DefaultActionAndReplyActionAsync"); 233 | 234 | var attr = asyncMethod.GetCustomAttribute(); 235 | 236 | Assert.That(attr.Action, Is.EqualTo("http://tempuri.org/IOperationContractInterface/DefaultActionAndReplyAction")); 237 | Assert.That(attr.ReplyAction, Is.EqualTo("http://tempuri.org/IOperationContractInterface/DefaultActionAndReplyActionResponse")); 238 | } 239 | 240 | [Test] 241 | public void AsyncInterface_DefaultAction_NamespaceIsDerivedFromDeclaringType() 242 | { 243 | var types = this.GenerateTypes(); 244 | 245 | var childMethod = types.AsyncInterface.GetMethod("ChildMethodAsync"); 246 | var childMethodAttr = childMethod.GetCustomAttribute(); 247 | 248 | var baseMethod = types.AsyncInterface.GetMethod("VoidMethodAsync"); 249 | var baseMethodAttr = baseMethod.GetCustomAttribute(); 250 | 251 | Assert.That(childMethodAttr.Action, Is.EqualTo("http://tempuri.org/IChildService/ChildMethod")); 252 | Assert.That(baseMethodAttr.Action, Is.EqualTo("http://tempuri.org/ITestService/VoidMethod")); 253 | } 254 | 255 | [Test] 256 | public void AsyncInterface_DefaultReplyAction_NamespaceIsDerivedFromDeclaringType() 257 | { 258 | var types = this.GenerateTypes(); 259 | 260 | var childMethod = types.AsyncInterface.GetMethod("ChildMethodAsync"); 261 | var childMethodAttr = childMethod.GetCustomAttribute(); 262 | 263 | var baseMethod = types.AsyncInterface.GetMethod("VoidMethodAsync"); 264 | var baseMethodAttr = baseMethod.GetCustomAttribute(); 265 | 266 | Assert.That(childMethodAttr.ReplyAction, Is.EqualTo("http://tempuri.org/IChildService/ChildMethodResponse")); 267 | Assert.That(baseMethodAttr.ReplyAction, Is.EqualTo("http://tempuri.org/ITestService/VoidMethodResponse")); 268 | } 269 | 270 | [Test] 271 | public void AsyncInterface_CustomAction_IsUsedOnAsyncMethod() 272 | { 273 | var types = this.GenerateTypes(); 274 | 275 | var asyncMethod = types.AsyncInterface.GetMethod("CustomActionAsync"); 276 | 277 | var attr = asyncMethod.GetCustomAttribute(); 278 | 279 | Assert.That(attr.Action, Is.EqualTo("NewAction")); 280 | } 281 | 282 | [Test] 283 | public void AsyncInterface_CustomReplyAction_IsUsedOnAsyncMethod() 284 | { 285 | var types = this.GenerateTypes(); 286 | 287 | var asyncMethod = types.AsyncInterface.GetMethod("CustomReplyActionAsync"); 288 | 289 | var attr = asyncMethod.GetCustomAttribute(); 290 | 291 | Assert.That(attr.ReplyAction, Is.EqualTo("NewReplyAction")); 292 | } 293 | 294 | [Test] 295 | public void AsyncInterface_CustomName_IsUsedOnAsyncMethod() 296 | { 297 | var types = this.GenerateTypes(); 298 | 299 | var asyncMethod = types.AsyncInterface.GetMethod("CustomNameAsync"); 300 | 301 | var attr = asyncMethod.GetCustomAttribute(); 302 | 303 | Assert.That(attr.Name, Is.EqualTo("NewName")); 304 | } 305 | 306 | [Test] 307 | public void AsyncInterface_CustomName_IsUsedToBuildDefaultActionAndReplyAction() 308 | { 309 | var types = this.GenerateTypes(); 310 | 311 | var asyncMethod = types.AsyncInterface.GetMethod("CustomNameAsync"); 312 | 313 | var attr = asyncMethod.GetCustomAttribute(); 314 | 315 | Assert.That(attr.Action, Is.EqualTo("http://tempuri.org/IOperationContractInterface/NewName")); 316 | Assert.That(attr.ReplyAction, Is.EqualTo("http://tempuri.org/IOperationContractInterface/NewNameResponse")); 317 | } 318 | 319 | [Test] 320 | public void AsyncMethodDefinition_NotGeneratedForNonAsyncMethod_WithExistingAsyncDefinition() 321 | { 322 | var types = this.GenerateTypes(); 323 | 324 | var generatedAsyncMethod = types.AsyncInterface.GetMethod("MethodAsync"); 325 | Assert.That(generatedAsyncMethod, Is.Null); 326 | } 327 | 328 | [Test] 329 | public void AsyncMethodDefinition_NotGeneratedForNonAsyncMethod_WithExistingAsyncDefinition_NotUsingCustomActionAndReplyAction() 330 | { 331 | var types = this.GenerateTypes(); 332 | 333 | var generatedAsyncMethod = types.AsyncInterface.GetMethod("MethodAsync"); 334 | Assert.That(generatedAsyncMethod, Is.Null); 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/FastActivatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using NUnit.Framework; 4 | using WcfClientProxyGenerator.Util; 5 | 6 | namespace WcfClientProxyGenerator.Tests 7 | { 8 | [TestFixture] 9 | public class FastActivatorTests 10 | { 11 | [SetUp] 12 | public void Setup() 13 | { 14 | FastActivator.ClearActivatorCache(); 15 | } 16 | 17 | [Test] 18 | public void GenericType_CanBeActivated_UsingParameterlessConstructor() 19 | { 20 | var inst = FastActivator.CreateInstance(); 21 | Assert.That(inst, Is.Not.Null); 22 | Assert.That(inst.Arg1, Is.EqualTo(default(string))); 23 | Assert.That(inst.Arg2, Is.EqualTo(default(bool))); 24 | Assert.That(inst.Arg3, Is.EqualTo(default(DateTime))); 25 | } 26 | 27 | [Test] 28 | public void GenericType_CanBeActivated_UsingParameterizedConstructor() 29 | { 30 | var dateTime = DateTime.Now; 31 | var instance = FastActivator.CreateInstance(new object[] { "test2", true, dateTime }); 32 | 33 | Assert.That(instance, Is.Not.Null); 34 | Assert.That(instance.Arg1, Is.EqualTo("test2")); 35 | Assert.That(instance.Arg2, Is.EqualTo(true)); 36 | Assert.That(instance.Arg3, Is.EqualTo(dateTime)); 37 | } 38 | 39 | [Test] 40 | public void Type_CanBeActivated_UsingParameterlessConstructor() 41 | { 42 | var inst = FastActivator.CreateInstance(typeof(TestClass)) as TestClass; 43 | 44 | Assert.That(inst, Is.Not.Null); 45 | Assert.That(inst.Arg1, Is.EqualTo(default(string))); 46 | Assert.That(inst.Arg2, Is.EqualTo(default(bool))); 47 | Assert.That(inst.Arg3, Is.EqualTo(default(DateTime))); 48 | } 49 | 50 | [Test] 51 | public void Type_CanBeActivated_UsingParameterizedConstructor() 52 | { 53 | var dateTime = DateTime.Now; 54 | var instance = FastActivator.CreateInstance(typeof(TestClass), new object[] { "test2", true, dateTime }) as TestClass; 55 | 56 | Assert.That(instance, Is.Not.Null); 57 | Assert.That(instance.Arg1, Is.EqualTo("test2")); 58 | Assert.That(instance.Arg2, Is.EqualTo(true)); 59 | Assert.That(instance.Arg3, Is.EqualTo(dateTime)); 60 | } 61 | 62 | [Test] 63 | public void MultipleGenericTypes_CanBeActivated_UsingParameterlessConstructor() 64 | { 65 | var instance1 = FastActivator.CreateInstance(); 66 | var instance2 = FastActivator.CreateInstance(); 67 | 68 | Assert.That(instance1, Is.Not.Null); 69 | Assert.That(instance1.GetType(), Is.EqualTo(typeof(TestClass))); 70 | 71 | Assert.That(instance2, Is.Not.Null); 72 | Assert.That(instance2.GetType(), Is.EqualTo(typeof(TestClass2))); 73 | } 74 | 75 | [Test] 76 | public void MultipleGenericTypes_CanBeActivated_UsingParameterizedConstructor() 77 | { 78 | var instance1 = FastActivator.CreateInstance(new object[] { "instance1" }); 79 | var instance2 = FastActivator.CreateInstance(new object[] { "instance2" }); 80 | 81 | Assert.That(instance1, Is.Not.Null); 82 | Assert.That(instance1.Arg1, Is.EqualTo("instance1")); 83 | Assert.That(instance1.GetType(), Is.EqualTo(typeof(TestClass))); 84 | 85 | Assert.That(instance2, Is.Not.Null); 86 | Assert.That(instance2.Arg1, Is.EqualTo("instance2")); 87 | Assert.That(instance2.GetType(), Is.EqualTo(typeof(TestClass2))); 88 | } 89 | 90 | [Test] 91 | public void MultipleTypes_CanBeActivated_UsingParameterlessConstructor() 92 | { 93 | var instance1 = FastActivator.CreateInstance(typeof(TestClass)); 94 | var instance2 = FastActivator.CreateInstance(typeof(TestClass2)); 95 | 96 | Assert.That(instance1, Is.Not.Null); 97 | Assert.That(instance1.GetType(), Is.EqualTo(typeof(TestClass))); 98 | 99 | Assert.That(instance2, Is.Not.Null); 100 | Assert.That(instance2.GetType(), Is.EqualTo(typeof(TestClass2))); 101 | } 102 | 103 | [Test] 104 | public void MultipleTypes_CanBeActivated_UsingParameterizedConstructor() 105 | { 106 | var instance1 = FastActivator.CreateInstance(typeof(TestClass), new object[] { "instance1" }) as TestClass; 107 | var instance2 = FastActivator.CreateInstance(typeof(TestClass2), new object[] { "instance2" }) as TestClass2; 108 | 109 | Assert.That(instance1, Is.Not.Null); 110 | Assert.That(instance1.Arg1, Is.EqualTo("instance1")); 111 | 112 | Assert.That(instance2, Is.Not.Null); 113 | Assert.That(instance2.Arg1, Is.EqualTo("instance2")); 114 | } 115 | 116 | [Test] 117 | public void Benchmark() 118 | { 119 | var activatorDuration = Benchmark("Activator", () => (TestClass) Activator.CreateInstance(typeof(TestClass), "test", true, DateTime.Now)); 120 | var fastActivatorDuration = Benchmark("FastActivator", () => FastActivator.CreateInstance(new object[] { "test", true, DateTime.Now })); 121 | 122 | Assert.That(fastActivatorDuration, Is.LessThan(activatorDuration)); 123 | 124 | Benchmark("Activator (parameterless)", () => (TestClass) Activator.CreateInstance(typeof(TestClass))); 125 | Benchmark("FastActivator (parameterless)", FastActivator.CreateInstance); 126 | } 127 | 128 | private TimeSpan Benchmark(string method, Func func) 129 | { 130 | const int iterations = 1000000; 131 | 132 | Stopwatch stopwatch = Stopwatch.StartNew(); 133 | for (int i = 0; i < iterations; i++) 134 | { 135 | var instance = func(); 136 | } 137 | stopwatch.Stop(); 138 | 139 | Trace.WriteLine(string.Format("{0}: {1}", method, stopwatch.Elapsed)); 140 | return stopwatch.Elapsed; 141 | } 142 | 143 | class TestClass 144 | { 145 | private readonly string _arg1; 146 | private readonly bool _arg2; 147 | private readonly DateTime _arg3; 148 | 149 | public TestClass() 150 | {} 151 | 152 | public TestClass(string arg1) 153 | { 154 | _arg1 = arg1; 155 | } 156 | 157 | public TestClass(string arg1, bool arg2, DateTime arg3) 158 | { 159 | _arg1 = arg1; 160 | _arg2 = arg2; 161 | _arg3 = arg3; 162 | } 163 | 164 | public string Arg1 165 | { 166 | get { return _arg1; } 167 | } 168 | 169 | public bool Arg2 170 | { 171 | get { return _arg2; } 172 | } 173 | 174 | public DateTime Arg3 175 | { 176 | get { return _arg3; } 177 | } 178 | } 179 | 180 | class TestClass2 181 | { 182 | private readonly string _arg1; 183 | 184 | public TestClass2() 185 | {} 186 | 187 | public TestClass2(string arg1) 188 | { 189 | _arg1 = arg1; 190 | } 191 | 192 | public string Arg1 193 | { 194 | get { return _arg1; } 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/ExceptionDetailService.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | 3 | namespace WcfClientProxyGenerator.Tests.Infrastructure 4 | { 5 | [ServiceContract] 6 | public interface IExceptionDetailService 7 | { 8 | [OperationContract] 9 | string Method(string input); 10 | } 11 | 12 | /// 13 | /// Enabling IncludeExceptionDetailInFaults. Can't do this through a substitute 14 | /// 15 | [ServiceBehavior(IncludeExceptionDetailInFaults = true)] 16 | public class ExceptionDetailService : IExceptionDetailService 17 | { 18 | private readonly IExceptionDetailService _sub; 19 | 20 | public ExceptionDetailService(IExceptionDetailService sub) 21 | { 22 | _sub = sub; 23 | } 24 | 25 | public string Method(string input) => _sub.Method(input); 26 | } 27 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/IChildService.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | 3 | namespace WcfClientProxyGenerator.Tests.Infrastructure 4 | { 5 | [ServiceContract] 6 | public interface IChildService : ITestService 7 | { 8 | [OperationContract] 9 | string ChildMethod(string input); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/IOutParamTestService.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | 3 | namespace WcfClientProxyGenerator.Tests.Infrastructure 4 | { 5 | [ServiceContract] 6 | public interface IOutParamTestService 7 | { 8 | [OperationContract] 9 | int SingleOutParam(out byte[] output); 10 | 11 | [OperationContract] 12 | int MultipleOutParams(out byte[] out1, out string out2); 13 | 14 | [OperationContract] 15 | int MixedParams(int inp1, out int out1, string inp2); 16 | } 17 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/ITestService.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using System.ServiceModel; 3 | 4 | namespace WcfClientProxyGenerator.Tests.Infrastructure 5 | { 6 | [ServiceContract] 7 | public interface ITestService 8 | { 9 | [OperationContract] 10 | string TestMethod(string input); 11 | 12 | [OperationContract(Name = "TestMethod2")] 13 | string TestMethod(string input, string two); 14 | 15 | [OperationContract] 16 | int TestMethodMixed(string input, int input2); 17 | 18 | [OperationContract] 19 | void VoidMethod(string input); 20 | 21 | [OperationContract] 22 | void VoidMethodNoParameters(); 23 | 24 | [OperationContract] 25 | void VoidMethodIntParameter(int input); 26 | 27 | [OperationContract] 28 | int IntMethod(); 29 | 30 | [OperationContract] 31 | Response TestMethodComplex(Request request); 32 | 33 | [OperationContract] 34 | Response TestMethodComplexMulti(string input, Request request); 35 | 36 | [OperationContract(IsOneWay = true)] 37 | void OneWay(string input); 38 | } 39 | 40 | [DataContract] 41 | public class Request 42 | { 43 | [DataMember] 44 | public string RequestMessage { get; set; } 45 | 46 | protected bool Equals(Request other) 47 | { 48 | return string.Equals(RequestMessage, other.RequestMessage); 49 | } 50 | 51 | public override bool Equals(object obj) 52 | { 53 | if (ReferenceEquals(null, obj)) 54 | { 55 | return false; 56 | } 57 | if (ReferenceEquals(this, obj)) 58 | { 59 | return true; 60 | } 61 | if (obj.GetType() != this.GetType()) 62 | { 63 | return false; 64 | } 65 | return Equals((Request) obj); 66 | } 67 | 68 | public override int GetHashCode() 69 | { 70 | return (RequestMessage != null ? RequestMessage.GetHashCode() : 0); 71 | } 72 | } 73 | 74 | public interface IResponseStatus 75 | { 76 | int StatusCode { get; } 77 | } 78 | 79 | [DataContract] 80 | public class Response : IResponseStatus 81 | { 82 | [DataMember] 83 | public string ResponseMessage { get; set; } 84 | 85 | [DataMember] 86 | public int StatusCode { get; set; } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/ITestService2.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | using System.Threading.Tasks; 3 | 4 | namespace WcfClientProxyGenerator.Tests.Infrastructure 5 | { 6 | [ServiceContract] 7 | public interface ITestService2 8 | { 9 | [OperationContract] 10 | string TestMethod(string input); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/ITestServiceSingleEndpointConfig.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | 3 | namespace WcfClientProxyGenerator.Tests.Infrastructure 4 | { 5 | [ServiceContract] 6 | public interface ITestServiceSingleEndpointConfig 7 | { 8 | [OperationContract] 9 | string TestMethod(string input); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/InProcTestFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.ServiceModel; 7 | using System.ServiceModel.Channels; 8 | using WcfClientProxyGenerator.Async; 9 | 10 | namespace WcfClientProxyGenerator.Tests.Infrastructure 11 | { 12 | public static class HostingExtensions 13 | { 14 | public static HostInformation StartHost(this TServiceInterface serviceInstance) 15 | { 16 | var serviceInterface = GetServiceInterfaceType(serviceInstance); 17 | return InProcTestFactory.CreateHost(serviceInterface, serviceInstance); 18 | } 19 | 20 | public static TServiceInterface StartHostAndProxy(this TServiceInterface serviceInstance, Action configurator = null) 21 | where TServiceInterface : class 22 | { 23 | var host = serviceInstance.StartHost(); 24 | return WcfClientProxy.Create(c => 25 | { 26 | c.SetEndpoint(host.Binding, host.EndpointAddress); 27 | configurator?.Invoke(c); 28 | }); 29 | } 30 | 31 | public static IAsyncProxy StartHostAndAsyncProxy(this TServiceInterface serviceInstance, Action configurator = null) 32 | where TServiceInterface : class 33 | { 34 | var host = serviceInstance.StartHost(); 35 | return WcfClientProxy.CreateAsyncProxy(c => 36 | { 37 | c.SetEndpoint(host.Binding, host.EndpointAddress); 38 | configurator?.Invoke(c); 39 | }); 40 | } 41 | 42 | private static Type GetServiceInterfaceType(object serviceInstance) 43 | { 44 | var serviceInterfaceType = serviceInstance.GetType() 45 | .GetInterfaces() 46 | .FirstOrDefault(m => m.GetCustomAttribute() != null); 47 | 48 | if (serviceInterfaceType == null) 49 | throw new Exception("Unable to find interface marked with ServiceContractAttribute"); 50 | 51 | return serviceInterfaceType; 52 | } 53 | } 54 | 55 | public class HostInformation 56 | { 57 | public HostInformation(Binding binding, EndpointAddress endpointAddress) 58 | { 59 | this.Binding = binding; 60 | this.EndpointAddress = endpointAddress; 61 | } 62 | 63 | public Binding Binding { get; private set; } 64 | public EndpointAddress EndpointAddress { get; private set; } 65 | } 66 | 67 | public static class InProcTestFactory 68 | { 69 | private static readonly string BaseAddress = "net.pipe://localhost/" + Guid.NewGuid(); 70 | private static readonly Binding Binding; 71 | 72 | private static readonly IDictionary Hosts 73 | = new ConcurrentDictionary(); 74 | 75 | static InProcTestFactory() 76 | { 77 | var binding = new NetNamedPipeBinding(); 78 | binding.TransactionFlow = true; 79 | 80 | Binding = binding; 81 | } 82 | 83 | public static HostInformation CreateHost(object serviceInstance) 84 | where TServiceInterface : class 85 | => CreateHost(typeof(TServiceInterface), serviceInstance); 86 | 87 | public static HostInformation CreateHost(Type serviceInterfaceType, object serviceInstance) 88 | { 89 | var address = OpenHost(serviceInterfaceType, serviceInstance); 90 | return new HostInformation(Binding, address); 91 | } 92 | 93 | private static EndpointAddress OpenHost(Type serviceInterfaceType, object serviceInstance) 94 | { 95 | var host = new ServiceHost(serviceInstance); 96 | string address = BaseAddress + Guid.NewGuid(); 97 | host.AddServiceEndpoint(serviceInterfaceType, Binding, address); 98 | 99 | host.Description.Behaviors.Find().InstanceContextMode = InstanceContextMode.Single; 100 | 101 | host.Open(); 102 | 103 | var endpointAddress = new EndpointAddress(address); 104 | Hosts[endpointAddress] = host; 105 | 106 | foreach (var endpoint in host.Description.Endpoints) 107 | { 108 | foreach (var operation in endpoint.Contract.Operations) 109 | { 110 | string operationName = operation.Name; 111 | string action = operation.Messages[0].Action; 112 | if (operation.Messages.Count > 1) 113 | { 114 | string replyAction = operation.Messages[1].Action; 115 | } 116 | } 117 | } 118 | 119 | return endpointAddress; 120 | } 121 | 122 | public static void CloseHost(EndpointAddress endpointAddress) 123 | { 124 | if (!Hosts.ContainsKey(endpointAddress)) 125 | return; 126 | 127 | Hosts[endpointAddress].Close(); 128 | Hosts.Remove(endpointAddress); 129 | } 130 | 131 | public static void CloseHosts() 132 | { 133 | foreach (var address in Hosts.Keys) 134 | CloseHost(address); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/ResetEventExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using NUnit.Framework; 4 | 5 | namespace WcfClientProxyGenerator.Tests.Infrastructure 6 | { 7 | public static class ResetEventExt 8 | { 9 | public static void WaitOrFail(this AutoResetEvent resetEvent, string message) 10 | { 11 | if (!resetEvent.WaitOne(TimeSpan.FromSeconds(1))) 12 | Assert.Fail(message); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/TestBase.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace WcfClientProxyGenerator.Tests.Infrastructure 4 | { 5 | [TestFixture] 6 | public abstract class TestBase 7 | { 8 | [TearDown] 9 | public virtual void AfterEachTest() 10 | { 11 | InProcTestFactory.CloseHosts(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Infrastructure/TrailingSlashOnNamespaceService.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | 3 | namespace WcfClientProxyGenerator.Tests.Infrastructure 4 | { 5 | [ServiceContract(Namespace = "http://example.com/services/")] 6 | public interface ITrailingSlashOnNamespaceService 7 | { 8 | [OperationContract] 9 | string Echo(string input); 10 | } 11 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Issues/Issue25.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | using NSubstitute; 3 | using NSubstitute.ExceptionExtensions; 4 | using NUnit.Framework; 5 | using WcfClientProxyGenerator.Tests.Infrastructure; 6 | 7 | namespace WcfClientProxyGenerator.Tests.Issues 8 | { 9 | [TestFixture] 10 | public class Issue25 11 | { 12 | [Test] 13 | public void Test() 14 | { 15 | var service = Substitute.For(); 16 | var serviceHost = InProcTestFactory.CreateHost(service); 17 | 18 | Assert.DoesNotThrow(() => WcfClientProxy.Create( 19 | c => c.SetEndpoint(serviceHost.Binding, serviceHost.EndpointAddress))); 20 | } 21 | 22 | [ServiceContract( 23 | Namespace = "SomeDomain.Contracts", 24 | Name = "SomeVendorServices")] 25 | public interface IIssue25Service 26 | { 27 | [OperationContract] 28 | Issue25Response GetOperation1(Issue25Request request); 29 | } 30 | 31 | public class Issue25Request 32 | { } 33 | 34 | public class Issue25Response 35 | { } 36 | } 37 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WcfClientProxyGenerator.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WcfClientProxyGenerator.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a2c7d4cf-7315-4e48-a652-237bf0b776f3")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/RetryingWcfActionInvokerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ServiceModel; 3 | using NSubstitute; 4 | using NSubstitute.ExceptionExtensions; 5 | using NUnit.Framework; 6 | using WcfClientProxyGenerator.Policy; 7 | using WcfClientProxyGenerator.Tests.Infrastructure; 8 | 9 | namespace WcfClientProxyGenerator.Tests 10 | { 11 | [TestFixture] 12 | public class RetryingWcfActionInvokerTests 13 | { 14 | [Test] 15 | public void Retries_OnChannelTerminatedException() 16 | { 17 | this.AssertThatCallRetriesOnException(); 18 | } 19 | 20 | [Test] 21 | public void Retries_OnEndpointNotFoundException() 22 | { 23 | this.AssertThatCallRetriesOnException(); 24 | } 25 | 26 | [Test] 27 | public void Retries_OnServerTooBusyException() 28 | { 29 | this.AssertThatCallRetriesOnException(); 30 | } 31 | 32 | #region RetriesOnException 33 | 34 | [Test] 35 | public void AddExceptionToRetryOn_RetriesOnConfiguredException() 36 | { 37 | this.AssertThatCallRetriesOnException( 38 | c => c.AddExceptionToRetryOn()); 39 | } 40 | 41 | [Test] 42 | public void AddExceptionToRetryOn_RetriesOnConfiguredException_WhenPredicateMatches() 43 | { 44 | this.AssertThatCallRetriesOnException( 45 | c => c.AddExceptionToRetryOn(e => e.Message == "The operation has timed out.")); 46 | } 47 | 48 | [Test] 49 | public void AddExceptionToRetryOn_RetriesOnConfiguredException_WhenPredicateMatches_UsingActualExceptionType() 50 | { 51 | this.AssertThatCallRetriesOnException( 52 | c => c.AddExceptionToRetryOn(e => e.TestExceptionMessage == "test"), 53 | () => new TestException("test")); 54 | } 55 | 56 | [Test] 57 | public void AddExceptionToRetryOn_RetriesOnMatchedPredicate_WhenMultiplePredicatesAreRegistered() 58 | { 59 | this.AssertThatCallRetriesOnException( 60 | c => 61 | { 62 | c.AddExceptionToRetryOn(e => e.TestExceptionMessage == "test"); 63 | c.AddExceptionToRetryOn(e => e.TestExceptionMessage == "other"); 64 | }, 65 | () => new TestException("test")); 66 | 67 | this.AssertThatCallRetriesOnException( 68 | c => 69 | { 70 | c.AddExceptionToRetryOn(e => e.TestExceptionMessage == "test"); 71 | c.AddExceptionToRetryOn(e => e.TestExceptionMessage == "other"); 72 | }, 73 | () => new TestException("other")); 74 | } 75 | 76 | #endregion 77 | 78 | #region RetryOnResponseCondition 79 | 80 | [Test] 81 | public void AddResponseToRetryOn_RetriesOnConfiguredResponse_ForResponseType() 82 | { 83 | var service = Substitute.For(); 84 | 85 | var failResponse = new Response { ResponseMessage = "fail" }; 86 | var successResponse = new Response { ResponseMessage = "success" }; 87 | 88 | service 89 | .TestMethodComplex(Arg.Any()) 90 | .Returns(failResponse, failResponse, successResponse); 91 | 92 | var actionInvoker = new RetryingWcfActionInvoker( 93 | () => service, 94 | () => new ConstantDelayPolicy(TimeSpan.FromMilliseconds(50)), 95 | retryCount: 2); 96 | 97 | actionInvoker.AddResponseToRetryOn(r => r.ResponseMessage == failResponse.ResponseMessage); 98 | 99 | var response = actionInvoker.Invoke(s => s.TestMethodComplex(new Request())); 100 | Assert.That(response.ResponseMessage, Is.EqualTo(successResponse.ResponseMessage)); 101 | } 102 | 103 | [Test] 104 | public void AddResponseToRetryOn_RetriesOnConfiguredResponse_ForResponseBaseType() 105 | { 106 | var service = Substitute.For(); 107 | 108 | var failResponse = new Response { StatusCode = 100 }; 109 | var successResponse = new Response { StatusCode = 1 }; 110 | 111 | service 112 | .TestMethodComplex(Arg.Any()) 113 | .Returns(failResponse, failResponse, successResponse); 114 | 115 | var actionInvoker = new RetryingWcfActionInvoker( 116 | () => service, 117 | () => new ConstantDelayPolicy(TimeSpan.FromMilliseconds(50)), 118 | retryCount: 2); 119 | 120 | actionInvoker.AddResponseToRetryOn(r => r.StatusCode == failResponse.StatusCode); 121 | 122 | var response = actionInvoker.Invoke(s => s.TestMethodComplex(new Request())); 123 | Assert.That(response.StatusCode, Is.EqualTo(successResponse.StatusCode)); 124 | } 125 | 126 | [Test] 127 | public void AddResponseToRetryOn_RetriesOnMatchedPredicate_WhenMultiplePredicatesAreRegistered() 128 | { 129 | var service = Substitute.For(); 130 | 131 | var firstFailResponse = new Response { StatusCode = 100 }; 132 | var secondFailResponse = new Response { StatusCode = 101 }; 133 | var successResponse = new Response { StatusCode = 1 }; 134 | 135 | service 136 | .TestMethodComplex(Arg.Any()) 137 | .Returns(firstFailResponse, secondFailResponse, successResponse); 138 | 139 | var actionInvoker = new RetryingWcfActionInvoker( 140 | () => service, 141 | () => new ConstantDelayPolicy(TimeSpan.FromMilliseconds(50)), 142 | retryCount: 2); 143 | 144 | actionInvoker.AddResponseToRetryOn(r => r.StatusCode == firstFailResponse.StatusCode); 145 | actionInvoker.AddResponseToRetryOn(r => r.StatusCode == secondFailResponse.StatusCode); 146 | 147 | var response = actionInvoker.Invoke(s => s.TestMethodComplex(new Request())); 148 | Assert.That(response.StatusCode, Is.EqualTo(successResponse.StatusCode)); 149 | } 150 | 151 | #endregion 152 | 153 | public class TestException : Exception 154 | { 155 | public TestException() 156 | {} 157 | 158 | public TestException(string testExceptionMessage) 159 | { 160 | TestExceptionMessage = testExceptionMessage; 161 | } 162 | 163 | public string TestExceptionMessage { get; set; } 164 | } 165 | 166 | [Test] 167 | public void AddExceptionToRetryOn_PassesThroughException_OnConfiguredException_WhenPredicateDoesNotMatch() 168 | { 169 | var service = Substitute.For(); 170 | 171 | service 172 | .TestMethod(Arg.Any()) 173 | .Throws(); 174 | 175 | var actionInvoker = new RetryingWcfActionInvoker( 176 | () => service, 177 | () => new ConstantDelayPolicy(TimeSpan.FromMilliseconds(50))); 178 | 179 | actionInvoker.AddExceptionToRetryOn(where: e => e.Message == "not the message"); 180 | 181 | Assert.That(() => actionInvoker.Invoke(s => s.TestMethod("test")), Throws.TypeOf()); 182 | } 183 | 184 | private void AssertThatCallRetriesOnException( 185 | Action> configurator = null, 186 | Func exceptionFactory = null) 187 | where TException : Exception, new() 188 | { 189 | var service = Substitute.For(); 190 | 191 | if (exceptionFactory != null) 192 | { 193 | service 194 | .TestMethod(Arg.Any()) 195 | .Throws(exceptionFactory()); 196 | } 197 | else 198 | { 199 | service 200 | .TestMethod(Arg.Any()) 201 | .Throws(); 202 | } 203 | 204 | var delayPolicy = Substitute.For(); 205 | 206 | var actionInvoker = new RetryingWcfActionInvoker( 207 | () => service, 208 | () => delayPolicy, 209 | retryCount: 5); 210 | 211 | configurator?.Invoke(actionInvoker); 212 | 213 | Assert.That( 214 | () => actionInvoker.Invoke(s => s.TestMethod("test")), 215 | Throws.TypeOf()); 216 | 217 | delayPolicy 218 | .Received(5) 219 | .GetDelay(Arg.Is(i => i >= 0 && i <= 4)); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/SanityTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ServiceModel; 3 | using NSubstitute; 4 | using NSubstitute.ExceptionExtensions; 5 | using NUnit.Framework; 6 | using WcfClientProxyGenerator.Tests.Infrastructure; 7 | 8 | namespace WcfClientProxyGenerator.Tests 9 | { 10 | public class SanityTests : TestBase 11 | { 12 | [Test, Description("Asserts that we can mock a WCF service in memory")] 13 | public void MockedService_WorksAsExpected() 14 | { 15 | var service = Substitute.For(); 16 | 17 | service 18 | .TestMethod("known") 19 | .Returns("test"); 20 | 21 | var host = service.StartHost(); 22 | 23 | var proxy = ChannelFactory.CreateChannel(host.Binding, host.EndpointAddress); 24 | 25 | Assert.That(() => proxy.TestMethod("known"), Is.EqualTo("test")); 26 | } 27 | 28 | [Test, Description("Asserts that we can fault a default Client Channel")] 29 | public void FaultHappens_WithDefaultChannelProxy() 30 | { 31 | var service = Substitute.For(); 32 | 33 | service 34 | .TestMethod("good") 35 | .Returns("OK"); 36 | 37 | service 38 | .TestMethod("bad") 39 | .Throws(); 40 | 41 | var host = service.StartHost(); 42 | 43 | var proxy = new ChannelFactory(host.Binding, host.EndpointAddress).CreateChannel(); 44 | 45 | // Will fault the channel 46 | Assert.That(() => proxy.TestMethod("bad"), Throws.Exception); 47 | Assert.That(() => proxy.TestMethod("good"), Throws.Exception.TypeOf()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/TypeExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using WcfClientProxyGenerator.Util; 5 | 6 | namespace WcfClientProxyGenerator.Tests 7 | { 8 | [TestFixture] 9 | public class TypeExtensionsTests 10 | { 11 | [Test] 12 | public void GetAllInheritedTypes_ForObjectType_ReturnsObjectType() 13 | { 14 | var types = typeof(object).GetAllInheritedTypes(); 15 | 16 | Assert.That(types.Count(), Is.EqualTo(1)); 17 | Assert.That(types, Contains.Item(typeof(object))); 18 | } 19 | 20 | [Test] 21 | public void GetAllInheritedTypes_ForValueType() 22 | { 23 | var types = typeof(int).GetAllInheritedTypes(); 24 | 25 | Assert.That(types, Contains.Item(typeof(int))); 26 | Assert.That(types, Contains.Item(typeof(ValueType))); 27 | Assert.That(types, Contains.Item(typeof(object))); 28 | } 29 | 30 | [Test] 31 | public void GetAllInheritedTypes_ForCustomType_ReturnsExpectedTypes() 32 | { 33 | var types = typeof(TestClass).GetAllInheritedTypes(); 34 | 35 | Assert.That(types.Count(), Is.EqualTo(4)); 36 | 37 | Assert.That(types, Contains.Item(typeof(TestClass))); 38 | Assert.That(types, Contains.Item(typeof(ITestInterface))); 39 | Assert.That(types, Contains.Item(typeof(IDisposable))); 40 | Assert.That(types, Contains.Item(typeof(object))); 41 | } 42 | 43 | [Test] 44 | public void GetAllInheritedTypes_ForCustomType_WithoutInterfaces_ReturnsExpectedTypes() 45 | { 46 | var types = typeof(TestClass).GetAllInheritedTypes(includeInterfaces: false); 47 | 48 | Assert.That(types.Count(), Is.EqualTo(2)); 49 | 50 | Assert.That(types, Contains.Item(typeof(TestClass))); 51 | Assert.That(types, Contains.Item(typeof(object))); 52 | } 53 | 54 | [Test] 55 | public void GetAllInheritedTypes_ForChildCustomType_ReturnsExpectedTypes() 56 | { 57 | var types = typeof(ChildTestClass).GetAllInheritedTypes(); 58 | 59 | Assert.That(types.Count(), Is.EqualTo(5)); 60 | 61 | Assert.That(types, Contains.Item(typeof(ChildTestClass))); 62 | Assert.That(types, Contains.Item(typeof(TestClass))); 63 | Assert.That(types, Contains.Item(typeof(ITestInterface))); 64 | Assert.That(types, Contains.Item(typeof(IDisposable))); 65 | Assert.That(types, Contains.Item(typeof(object))); 66 | } 67 | 68 | interface ITestInterface : IDisposable 69 | {} 70 | 71 | class TestClass : ITestInterface 72 | { 73 | public void Dispose() 74 | {} 75 | } 76 | 77 | class ChildTestClass : TestClass 78 | { 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/WcfClientProxyGenerator.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | NET45 6 | Debug 7 | AnyCPU 8 | {C8B3B23C-F719-4F31-B6B7-8F02B3A817FA} 9 | Library 10 | Properties 11 | WcfClientProxyGenerator.Tests 12 | WcfClientProxyGenerator.Tests 13 | v4.5 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | 21 | 22 | prompt 23 | 4 24 | bin\Debug\ 25 | 26 | 27 | pdbonly 28 | true 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | NET45 35 | bin\$(Configuration) 36 | v4.5 37 | 38 | 39 | NET40 40 | bin\$(Configuration)\net-4.0 41 | v4.0 42 | 43 | 44 | 45 | ..\packages\NSubstitute.1.10.0.0\lib\net45\NSubstitute.dll 46 | True 47 | 48 | 49 | ..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll 50 | True 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Always 94 | 95 | 96 | 97 | 98 | 99 | {56614d90-9eea-4908-8beb-7cd6e35bfcb0} 100 | WcfClientProxyGenerator 101 | 102 | 103 | 104 | 111 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WcfClientProxyGenerator", "WcfClientProxyGenerator\WcfClientProxyGenerator.csproj", "{56614D90-9EEA-4908-8BEB-7CD6E35BFCB0}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WcfClientProxyGenerator.Tests", "WcfClientProxyGenerator.Tests\WcfClientProxyGenerator.Tests.csproj", "{C8B3B23C-F719-4F31-B6B7-8F02B3A817FA}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {56614D90-9EEA-4908-8BEB-7CD6E35BFCB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {56614D90-9EEA-4908-8BEB-7CD6E35BFCB0}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {56614D90-9EEA-4908-8BEB-7CD6E35BFCB0}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {56614D90-9EEA-4908-8BEB-7CD6E35BFCB0}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {C8B3B23C-F719-4F31-B6B7-8F02B3A817FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {C8B3B23C-F719-4F31-B6B7-8F02B3A817FA}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {C8B3B23C-F719-4F31-B6B7-8F02B3A817FA}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {C8B3B23C-F719-4F31-B6B7-8F02B3A817FA}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | <data><IncludeFilters /><ExcludeFilters /></data> 3 | <data /> -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Async/AsyncProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using WcfClientProxyGenerator.Util; 9 | 10 | namespace WcfClientProxyGenerator.Async 11 | { 12 | /// 13 | /// Wrapper around TServiceInterface providing an async friendly 14 | /// interface. 15 | /// 16 | /// 17 | public interface IAsyncProxy 18 | where TServiceInterface : class 19 | { 20 | /// 21 | /// Access to the underlying client proxy 22 | /// 23 | TServiceInterface Client { get; } 24 | 25 | /// 26 | /// Make a based async call to a WCF method on TServiceInterface 27 | /// 28 | /// 29 | /// 30 | /// 31 | Task CallAsync(Expression> method); 32 | 33 | /// 34 | /// Make a based async call to a WCF method on TServiceInterface 35 | /// 36 | /// 37 | /// 38 | Task CallAsync(Expression> method); 39 | } 40 | 41 | class AsyncProxy : IAsyncProxy 42 | where TServiceInterface : class 43 | { 44 | internal static readonly ConcurrentDictionary> DelegateCache 45 | = new ConcurrentDictionary>(); 46 | 47 | private readonly TServiceInterface provider; 48 | 49 | public AsyncProxy(TServiceInterface provider) 50 | { 51 | this.provider = provider; 52 | } 53 | 54 | public TServiceInterface Client { get { return this.provider; } } 55 | 56 | public Task CallAsync(Expression> method) 57 | { 58 | return this.InvokeCallAsyncDelegate>(method.Body); 59 | } 60 | 61 | public Task CallAsync(Expression> method) 62 | { 63 | return this.InvokeCallAsyncDelegate(method.Body); 64 | } 65 | 66 | private TResponse InvokeCallAsyncDelegate(Expression expression) 67 | where TResponse : Task 68 | { 69 | var methodCall = expression as MethodCallExpression; 70 | if (methodCall == null) 71 | throw new NotSupportedException("Calls made to .CallAsync() must be of type 'MethodCallExpression'"); 72 | 73 | var methodParameters = methodCall.Method.GetParameters().ToArray(); 74 | 75 | if (methodParameters.Any(m => m.ParameterType.IsByRef)) 76 | { 77 | throw new NotSupportedException( 78 | string.Format("OperationContract method '{0}' has parameters '{1}' marked as out or ref. These are not currently supported in async calls.", 79 | methodCall.Method.Name, 80 | string.Join(", ", methodParameters.Where(m => m.ParameterType.IsByRef).Select(m => m.Name)))); 81 | } 82 | 83 | var cachedDelegate = this.GetCallAsyncDelegate(methodCall, methodParameters); 84 | var argumentValues = this.GetMethodCallArgumentValues(methodCall); 85 | 86 | var invokeArgs = new object[] { this.provider }.Concat(argumentValues); 87 | return cachedDelegate.DynamicInvoke(invokeArgs.ToArray()) as TResponse; 88 | } 89 | 90 | private Delegate GetCallAsyncDelegate(MethodCallExpression methodCall, ParameterInfo[] methodParameters) 91 | { 92 | int cacheKey = GetMethodCallExpressionHashCode(methodCall, methodParameters.Select(m => m.ParameterType)); 93 | return DelegateCache.GetOrAddSafe(cacheKey, _ => 94 | { 95 | var methodParameterTypes = methodParameters.Select(m => m.ParameterType).ToArray(); 96 | var methodInfo = this.provider.GetType().GetMethod(methodCall.Method.Name + "Async", methodParameterTypes) 97 | ?? this.provider.GetType().GetMethod(methodCall.Method.Name, methodParameterTypes); 98 | 99 | if (methodInfo == null) 100 | throw new NotSupportedException( 101 | string.Format("CallAsync could not locate the appropriate method based on '{0}' to call asynchronously", methodCall.Method.Name)); 102 | 103 | if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)) 104 | throw new NotSupportedException( 105 | string.Format("Method '{0}' has return type of '{1}' which is not based on 'Task' and cannot be used for asynchronous calls", 106 | methodInfo.Name, 107 | methodInfo.ReturnType.ToString())); 108 | 109 | var proxyParam = Expression.Parameter(this.provider.GetType(), "proxy"); 110 | 111 | var delegateParameters = new ParameterExpression[methodCall.Arguments.Count]; 112 | for (int i = 0; i < methodParameters.Length; i++) 113 | { 114 | delegateParameters[i] = Expression.Parameter( 115 | methodParameters[i].ParameterType, 116 | methodParameters[i].Name); 117 | } 118 | 119 | var invokeExpr = Expression.Call(proxyParam, methodInfo, delegateParameters); 120 | 121 | var lambdaParamaters = new[] { proxyParam }.Concat(delegateParameters); 122 | var lambdaExpression = Expression.Lambda(invokeExpr, lambdaParamaters); 123 | Delegate @delegate = lambdaExpression.Compile(); 124 | 125 | return @delegate; 126 | }); 127 | } 128 | 129 | private object[] GetMethodCallArgumentValues(MethodCallExpression methodCall) 130 | { 131 | var arguments = from arg in methodCall.Arguments 132 | let argAsObj = Expression.Convert(arg, typeof(object)) 133 | select Expression.Lambda>(argAsObj, null) 134 | .Compile()(); 135 | 136 | return arguments.ToArray(); 137 | } 138 | 139 | private static int GetMethodCallExpressionHashCode(MethodCallExpression methodCall, IEnumerable parameterTypes) 140 | { 141 | int offset = methodCall.Method.GetHashCode(); 142 | int key = offset; 143 | foreach (var parameterType in parameterTypes) 144 | key = key ^ (parameterType == null ? offset : parameterType.GetHashCode() << offset++); 145 | 146 | return key; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Async/GeneratedAsyncInterfaceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator.Async 4 | { 5 | /// 6 | /// Marker attribute placed on runtime generated *Async ServiceContract interfaces 7 | /// 8 | [AttributeUsage(AttributeTargets.Interface)] 9 | internal class GeneratedAsyncInterfaceAttribute : Attribute 10 | {} 11 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/ChannelFactoryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.ServiceModel; 8 | using System.ServiceModel.Channels; 9 | using System.ServiceModel.Configuration; 10 | using System.ServiceModel.Description; 11 | using WcfClientProxyGenerator.Util; 12 | 13 | namespace WcfClientProxyGenerator 14 | { 15 | internal static class ChannelFactoryProvider 16 | { 17 | private static readonly ConcurrentDictionary> ChannelFactoryCache 18 | = new ConcurrentDictionary>(); 19 | 20 | public static ChannelFactory GetChannelFactory(Type originalServiceInterfaceType = null) 21 | where TServiceInterface : class 22 | { 23 | if (originalServiceInterfaceType == null) 24 | originalServiceInterfaceType = typeof(TServiceInterface); 25 | 26 | string cacheKey = GetCacheKey(); 27 | return GetChannelFactory(cacheKey, () => 28 | { 29 | var clientEndpointConfig = GetClientEndpointConfiguration(originalServiceInterfaceType); 30 | return new ChannelFactory(clientEndpointConfig); 31 | }); 32 | } 33 | 34 | public static ChannelFactory GetChannelFactory(string endpointConfigurationName, Type originalServiceInterfaceType = null) 35 | where TServiceInterface : class 36 | { 37 | if (originalServiceInterfaceType == null) 38 | originalServiceInterfaceType = typeof(TServiceInterface); 39 | 40 | string cacheKey = GetCacheKey(endpointConfigurationName); 41 | return GetChannelFactory(cacheKey, () => 42 | { 43 | var clientEndpointConfig = GetClientEndpointConfiguration(originalServiceInterfaceType, endpointConfigurationName); 44 | return new ChannelFactory(clientEndpointConfig); 45 | }); 46 | } 47 | 48 | public static ChannelFactory GetChannelFactory(Binding binding, EndpointAddress endpointAddress) 49 | where TServiceInterface : class 50 | { 51 | string cacheKey = GetCacheKey(binding, endpointAddress); 52 | return GetChannelFactory(cacheKey, () => new ChannelFactory(binding, endpointAddress)); 53 | } 54 | 55 | public static ChannelFactory GetChannelFactory(Binding binding, EndpointAddress endpointAddress, object callbackObject) 56 | where TServiceInterface : class 57 | { 58 | string cacheKey = GetCacheKey(binding, endpointAddress, callbackObject.GetType()); 59 | return GetChannelFactory(cacheKey, () => new DuplexChannelFactory(callbackObject, binding, endpointAddress)); 60 | } 61 | 62 | public static ChannelFactory GetChannelFactory(Binding binding, EndpointAddress endpointAddress, InstanceContext instanceContext) 63 | where TServiceInterface : class 64 | { 65 | string cacheKey = GetCacheKey(binding, endpointAddress, typeof(TCallback)); 66 | return GetChannelFactory(cacheKey, () => new DuplexChannelFactory(instanceContext.Context, binding, endpointAddress)); 67 | } 68 | 69 | public static ChannelFactory GetChannelFactory(ServiceEndpoint endpoint) 70 | where TServiceInterface : class 71 | { 72 | string cacheKey = GetCacheKey(endpoint); 73 | return GetChannelFactory(cacheKey, () => 74 | { 75 | endpoint.Contract = ContractDescription.GetContract(typeof(TServiceInterface)); 76 | return new ChannelFactory(endpoint); 77 | }); 78 | } 79 | 80 | public static ChannelFactory GetChannelFactory(ServiceEndpoint endpoint, object callbackObject) 81 | where TServiceInterface : class 82 | { 83 | string cacheKey = GetCacheKey(endpoint, callbackObject.GetType()); 84 | return GetChannelFactory(cacheKey, () => 85 | { 86 | endpoint.Contract = ContractDescription.GetContract(typeof(TServiceInterface)); 87 | return new DuplexChannelFactory(callbackObject, endpoint); 88 | }); 89 | } 90 | 91 | public static ChannelFactory GetChannelFactory(ServiceEndpoint endpoint, InstanceContext instanceContext) 92 | where TServiceInterface : class 93 | { 94 | string cacheKey = GetCacheKey(endpoint, typeof(TCallback)); 95 | return GetChannelFactory(cacheKey, () => new DuplexChannelFactory(instanceContext.Context, endpoint)); 96 | } 97 | 98 | private static ChannelFactory GetChannelFactory(string cacheKey, Func> factory) 99 | where TServiceInterface : class 100 | { 101 | var channelFactory = ChannelFactoryCache.GetOrAddSafe( 102 | cacheKey, 103 | _ => factory()); 104 | 105 | return channelFactory as ChannelFactory; 106 | } 107 | 108 | private static string GetCacheKey() 109 | { 110 | return $"type:{typeof (TServiceInterface).FullName}"; 111 | } 112 | 113 | private static string GetCacheKey(string endpointConfigurationName) 114 | { 115 | return $"type:{typeof (TServiceInterface).FullName};config:{endpointConfigurationName}"; 116 | } 117 | 118 | private static string GetCacheKey(Binding binding, EndpointAddress endpointAddress) 119 | { 120 | return $"type:{typeof (TServiceInterface).FullName};binding:{binding.Name};uri:{endpointAddress}"; 121 | } 122 | 123 | private static string GetCacheKey(Binding binding, EndpointAddress endpointAddress, Type callbackType) 124 | { 125 | string nonDuplexKey = GetCacheKey(binding, endpointAddress); 126 | return nonDuplexKey + $";callback:{callbackType.FullName}"; 127 | } 128 | 129 | private static string GetCacheKey(ServiceEndpoint endpoint) 130 | { 131 | return GetCacheKey(endpoint.Binding, endpoint.Address); 132 | } 133 | 134 | private static string GetCacheKey(ServiceEndpoint endpoint, Type callbackType) 135 | { 136 | string nonDuplexKey = GetCacheKey(endpoint); 137 | return nonDuplexKey + $";callback:{callbackType.FullName}"; 138 | } 139 | 140 | private static ServiceEndpoint GetClientEndpointConfiguration( 141 | Type serviceInterfaceType, 142 | string endpointConfigurationName = null) 143 | where TServiceInterface : class 144 | { 145 | var configurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; 146 | if (string.IsNullOrEmpty(configurationFile)) 147 | throw new InvalidOperationException( 148 | "Could not determine the current configuration files being used in the AppDomain"); 149 | 150 | var mappedConfigurationFile = new ExeConfigurationFileMap { ExeConfigFilename = configurationFile }; 151 | var configuration = ConfigurationManager.OpenMappedExeConfiguration(mappedConfigurationFile, ConfigurationUserLevel.None); 152 | 153 | if (configuration == null) 154 | throw new InvalidOperationException( 155 | "Could not load the default configuration file. Unable to locate the default configuration for service type: " + 156 | serviceInterfaceType.Name); 157 | 158 | var serviceModelSection = configuration.GetSectionGroup("system.serviceModel") as ServiceModelSectionGroup; 159 | if (serviceModelSection == null) 160 | throw new InvalidOperationException("Could not find system.serviceModel section group in the configuration file."); 161 | 162 | var endpoint = GetDefaultEndpointForServiceType(serviceInterfaceType, endpointConfigurationName, serviceModelSection.Client.Endpoints); 163 | var binding = GetClientEndpointBinding(serviceInterfaceType, endpoint, serviceModelSection.Bindings.BindingCollections); 164 | 165 | var serviceEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(TServiceInterface))) 166 | { 167 | Binding = binding, 168 | Address = new EndpointAddress(endpoint.Address) 169 | }; 170 | 171 | foreach (var behavior in GetEndpointBehaviors(endpoint, serviceModelSection)) 172 | serviceEndpoint.Behaviors.Add(behavior); 173 | 174 | return serviceEndpoint; 175 | } 176 | 177 | private static ChannelEndpointElement GetDefaultEndpointForServiceType( 178 | Type serviceInterfaceType, 179 | string endpointConfigurationName, 180 | ChannelEndpointElementCollection endpoints) 181 | { 182 | var endpointsForServiceType = endpoints.Cast() 183 | .Where(e => e.Contract == serviceInterfaceType.FullName || serviceInterfaceType.FullName.EndsWith(e.Contract)) 184 | .ToList(); 185 | 186 | if (!string.IsNullOrEmpty(endpointConfigurationName)) 187 | endpointsForServiceType = endpointsForServiceType.Where(e => e.Name == endpointConfigurationName).ToList(); 188 | 189 | if (endpointsForServiceType.Count == 0) 190 | { 191 | string message = string.Format( 192 | "Could not find default endpoint element that references contract '{0}' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element.", 193 | serviceInterfaceType.FullName); 194 | 195 | throw new InvalidOperationException(message); 196 | } 197 | 198 | if (endpointsForServiceType.Count > 1) 199 | { 200 | string message = string.Format( 201 | "An endpoint configuration section for contract '{0}' could not be loaded because more than one endpoint configuration for that contract was found. Please indicate the preferred endpoint configuration section by name.", 202 | serviceInterfaceType.FullName); 203 | 204 | throw new InvalidOperationException(message); 205 | } 206 | 207 | return endpointsForServiceType[0]; 208 | } 209 | 210 | private static Binding GetClientEndpointBinding( 211 | Type serviceInterfaceType, 212 | ChannelEndpointElement endpoint, 213 | IEnumerable bindings) 214 | { 215 | foreach (var binding in bindings.Where(b => b.BindingName == endpoint.Binding)) 216 | { 217 | var bindingInstance = (Binding) Activator.CreateInstance(binding.BindingType); 218 | 219 | var configuration = binding.ConfiguredBindings.SingleOrDefault(cb => cb.Name == endpoint.BindingConfiguration); 220 | if (configuration != null) 221 | { 222 | bindingInstance.Name = configuration.Name; 223 | configuration.ApplyConfiguration(bindingInstance); 224 | } 225 | 226 | return bindingInstance; 227 | } 228 | 229 | var message = string.Format("Could not determine binding from configuration section for contract '{0}'", serviceInterfaceType.FullName); 230 | throw new InvalidOperationException(message); 231 | } 232 | 233 | private static IEnumerable GetEndpointBehaviors( 234 | ChannelEndpointElement endpoint, 235 | ServiceModelSectionGroup serviceModelSectionGroup) 236 | 237 | { 238 | if (string.IsNullOrEmpty(endpoint.BehaviorConfiguration) || serviceModelSectionGroup.Behaviors == null || serviceModelSectionGroup.Behaviors.EndpointBehaviors.Count == 0) 239 | yield break; 240 | 241 | 242 | var behaviorCollectionElement = serviceModelSectionGroup.Behaviors.EndpointBehaviors[endpoint.BehaviorConfiguration]; 243 | foreach (var behaviorExtension in behaviorCollectionElement) 244 | 245 | { 246 | object extension = behaviorExtension.GetType().InvokeMember("CreateBehavior", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, behaviorExtension, null); 247 | yield return ((IEndpointBehavior)extension); 248 | } 249 | } 250 | } 251 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/DefaultProxyConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WcfClientProxyGenerator.Policy; 3 | 4 | namespace WcfClientProxyGenerator 5 | { 6 | internal static class DefaultProxyConfigurator 7 | { 8 | public static void Configure(IRetryingProxyConfigurator proxy) 9 | where TServiceInterface : class 10 | { 11 | proxy.UseDefaultEndpoint(); 12 | } 13 | 14 | public static readonly Func DefaultDelayPolicyFactory 15 | = () => new LinearBackoffDelayPolicy(TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(10)); 16 | 17 | public static readonly RetryFailureExceptionFactoryDelegate DefaultRetryFailureExceptionFactory 18 | = (retryCount, lastException, invokeInfo) => new WcfRetryFailedException(string.Format("WCF call failed after {0} retries.", retryCount), lastException); 19 | } 20 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/IActionInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WcfClientProxyGenerator 5 | { 6 | internal interface IActionInvoker 7 | where TServiceInterface : class 8 | { 9 | void Invoke(Action method, InvokeInfo invokeInfo = null); 10 | TResponse Invoke(Func method, InvokeInfo invokeInfo = null); 11 | 12 | Task InvokeAsync(Func method, InvokeInfo invokeInfo = null); 13 | Task InvokeAsync(Func> method, InvokeInfo invokeInfo = null); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/IActionInvokerProvider.cs: -------------------------------------------------------------------------------- 1 | namespace WcfClientProxyGenerator 2 | { 3 | internal interface IActionInvokerProvider 4 | where TServiceInterface : class 5 | { 6 | IActionInvoker ActionInvoker { get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/IProxyConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ServiceModel; 3 | using System.ServiceModel.Channels; 4 | using System.ServiceModel.Description; 5 | 6 | namespace WcfClientProxyGenerator 7 | { 8 | /// 9 | /// Configuration options for the generated WCF proxy 10 | /// 11 | public interface IProxyConfigurator 12 | { 13 | /// 14 | /// Uses the default endpoint configuration from the web.config or app.config 15 | /// 16 | void UseDefaultEndpoint(); 17 | 18 | /// 19 | /// Specifies the endpoint configuration to use 20 | /// 21 | /// 22 | void SetEndpoint(string endpointConfigurationName); 23 | 24 | /// 25 | /// Specifies the binding and address to use 26 | /// 27 | /// 28 | /// 29 | void SetEndpoint(Binding binding, EndpointAddress endpointAddress); 30 | 31 | /// 32 | /// Specifies the binding, address to use and callbackInstance to use with DuplexChannel 33 | /// 34 | /// 35 | /// 36 | /// 37 | void SetEndpoint(Binding binding, EndpointAddress endpointAddress, object callbackInstance); 38 | 39 | /// 40 | /// Specifies the binding, address to use and callbackInstance to use with DuplexChannel 41 | /// 42 | /// 43 | /// 44 | /// 45 | void SetEndpoint(Binding binding, EndpointAddress endpointAddress, InstanceContext instanceContext); 46 | 47 | /// 48 | /// Specifies the endpoint configuration to use. 49 | /// 50 | /// 51 | void SetEndpoint(ServiceEndpoint endpoint); 52 | 53 | /// 54 | /// Specifies the endpoint configuration to use. 55 | /// 56 | /// 57 | /// 58 | void SetEndpoint(ServiceEndpoint endpoint, InstanceContext instanceContext); 59 | 60 | /// 61 | /// Event that is fired immediately before the service method will be called. This event 62 | /// is called only once per request. 63 | /// 64 | event OnCallBeginHandler OnCallBegin; 65 | 66 | /// 67 | /// Event that is fired immediately after the request successfully completes. 68 | /// 69 | event OnCallSuccessHandler OnCallSuccess; 70 | 71 | /// 72 | /// Event that is fired when the method is about to be called. 73 | /// The event is fired for every attempt to call the service method. 74 | /// 75 | event OnInvokeHandler OnBeforeInvoke; 76 | 77 | /// 78 | /// Event that is fired after the service method has been called. 79 | /// This event fires only when the method has been successfully called. 80 | /// 81 | event OnInvokeHandler OnAfterInvoke; 82 | 83 | /// 84 | /// Event that is fired if the service call fails with an exception. 85 | /// This event is fired for every failed attempt to call the service method. 86 | /// 87 | event OnExceptionHandler OnException; 88 | 89 | /// 90 | /// Allows access to WCF extensibility features. 91 | /// 92 | /// 93 | /// Make sure this is called after any other endpoint-modifying configuration operations, 94 | /// as not doing so will not produce expected results. 95 | /// 96 | ChannelFactory ChannelFactory { get; } 97 | 98 | #region HandleRequestArgument 99 | 100 | /// 101 | /// Allows inspection or modification of request arguments immediately before sending the request. 102 | /// 103 | /// Type or parent type/interface of the argument 104 | /// Predicate to filter the request arguments by properties of the request, or the parameter name 105 | /// Delegate that takes a 106 | void HandleRequestArgument(Func where, Action handler); 107 | 108 | /// 109 | /// Allows inspection or modification of request arguments immediately before sending the request. 110 | /// 111 | /// Type or parent type/interface of the argument 112 | /// Delegate that takes a 113 | void HandleRequestArgument(Action handler); 114 | 115 | /// 116 | /// Allows inspection or modification of request arguments immediately before sending the request. 117 | /// 118 | /// Type or parent type/interface of the argument 119 | /// Predicate to filter the request arguments by properties of the request, or the parameter name 120 | /// Delegate that takes a and returns a 121 | void HandleRequestArgument(Func where, Func handler); 122 | 123 | /// 124 | /// Allows inspection or modification of request arguments immediately before sending the request. 125 | /// 126 | /// Type or parent type/interface of the argument 127 | /// Delegate that takes a and returns a 128 | void HandleRequestArgument(Func handler); 129 | 130 | #endregion 131 | 132 | #region HandleResponse 133 | 134 | /// 135 | /// Allows inspecting and modifying the object 136 | /// before returning the response to the calling method. 137 | /// 138 | /// Type or parent type/interface of the response 139 | /// Predicate to filter responses based on its parameters 140 | /// 141 | /// Delegate that takes a 142 | /// 143 | void HandleResponse(Predicate where, Action handler); 144 | 145 | /// 146 | /// Allows inspecting and modifying the object 147 | /// before returning the response to the calling method. 148 | /// 149 | /// Type or parent type/interface of the response 150 | /// 151 | /// Delegate that takes a 152 | /// 153 | void HandleResponse(Action handler); 154 | 155 | /// 156 | /// Allows inspecting and modifying the object 157 | /// before returning the response to the calling method. 158 | /// 159 | /// Type or parent type/interface of the response 160 | /// Predicate to filter responses based on its parameters 161 | /// 162 | /// Delegate that takes a and returns a 163 | /// 164 | void HandleResponse(Predicate where, Func handler); 165 | 166 | /// 167 | /// Allows inspecting and modifying the object 168 | /// before returning the response to the calling method. 169 | /// 170 | /// Type or parent type/interface of the response 171 | /// 172 | /// Delegate that takes a and returns a 173 | /// 174 | void HandleResponse(Func handler); 175 | 176 | #endregion 177 | } 178 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/IRetryingProxyConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WcfClientProxyGenerator.Policy; 3 | 4 | namespace WcfClientProxyGenerator 5 | { 6 | /// 7 | /// Signature to use when defining a custom exception to use in place of the built in 8 | /// 9 | /// The total amount of retry attempts made before ultimately failing 10 | /// The last exception (if any) encountered when retrying the WCF calls 11 | /// Additional information about the WCF request 12 | public delegate Exception RetryFailureExceptionFactoryDelegate(int retryAttempts, Exception lastException, InvokeInfo invocationInfo); 13 | 14 | /// 15 | /// Proxy configuration options for managing retry logic 16 | /// 17 | public interface IRetryingProxyConfigurator : IProxyConfigurator 18 | { 19 | /// 20 | /// Specifies the maximum amount of retries that should be made 21 | /// when known failures happen during a service call. 22 | /// 23 | /// 24 | void MaximumRetries(int retryCount); 25 | 26 | /// 27 | /// Configures the proxy to use a specific 28 | /// to determine the amount of time to wait between call retries. 29 | /// 30 | /// 31 | void SetDelayPolicy(Func policyFactory); 32 | 33 | /// 34 | /// Specifies an exception case to trigger a call retry. 35 | /// 36 | /// 37 | /// 38 | void RetryOnException(Predicate where = null) 39 | where TException : Exception; 40 | 41 | /// 42 | /// Specifies an exception case to trigger a call retry. 43 | /// 44 | /// 45 | /// Predicate defining the case to retry on 46 | void RetryOnException(Type exceptionType, Predicate where = null); 47 | 48 | /// 49 | /// Specifies a case based on a service response to trigger a call retry. 50 | /// 51 | /// 52 | /// Predicate defining the case to retry on 53 | void RetryOnResponse(Predicate where); 54 | 55 | /// 56 | /// Overrides the default use of the type when 57 | /// the WCF call fails more than and uses the 58 | /// type generated by this in its place. 59 | /// 60 | /// Delegate with int, Exception and InvokeInfo arguments 61 | void RetryFailureExceptionFactory(RetryFailureExceptionFactoryDelegate factory); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/InstanceContext.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | 3 | namespace WcfClientProxyGenerator 4 | { 5 | 6 | /// 7 | /// Generic instance context for callback services. 8 | /// 9 | /// the type of the callback context. 10 | public class InstanceContext 11 | { 12 | private readonly InstanceContext _context; 13 | 14 | /// 15 | /// 16 | /// 17 | /// 18 | public InstanceContext(TCallback callbackInstance) 19 | { 20 | _context = new InstanceContext(callbackInstance); 21 | } 22 | 23 | /// 24 | /// Get the instance context. 25 | /// 26 | public InstanceContext Context => _context; 27 | 28 | /// 29 | /// Gets the service instance. 30 | /// 31 | public TCallback ServiceInstance => (TCallback)_context.GetServiceInstance(); 32 | 33 | /// 34 | /// Releases the underlying service instance. 35 | /// 36 | public void ReleaseServiceInstance() 37 | { 38 | _context.ReleaseServiceInstance(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/InvokeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WcfClientProxyGenerator 8 | { 9 | /// 10 | /// Provides information on the invoked method. 11 | /// 12 | public class InvokeInfo 13 | { 14 | /// 15 | /// Name of the called method. 16 | /// 17 | public string MethodName { get; set; } 18 | 19 | /// 20 | /// Parameters passed to the method. 21 | /// 22 | public object[] Parameters { get; set; } 23 | 24 | /// 25 | /// True if method has returned and it has a non-void return value. 26 | /// 27 | public bool MethodHasReturnValue { get; set; } 28 | 29 | /// 30 | /// Return value from the service call. Only applicable when used 31 | /// in the OnAfterInvoke event handler and it is a non-void method. 32 | /// Throws if method is not returned yet (used in OnBeforeInvoke), 33 | /// or it is a void method. 34 | /// Use to tell if there is a value to be read. 35 | /// 36 | /// If method has no return value 37 | public object ReturnValue 38 | { 39 | get 40 | { 41 | if (!MethodHasReturnValue) 42 | { 43 | throw new InvalidOperationException("Cannot get return value; method has not returned yet or is a void method"); 44 | } 45 | return _returnValue; 46 | } 47 | set 48 | { 49 | if (!MethodHasReturnValue) 50 | { 51 | throw new InvalidOperationException("Cannot set return value; method has not returned yet or is a void method"); 52 | } 53 | _returnValue = value; 54 | } 55 | } 56 | 57 | private object _returnValue; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/OnCallBeginHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator 4 | { 5 | /// 6 | /// Arguments given to the delegate 7 | /// 8 | public class OnCallBeginHandlerArguments 9 | { 10 | /// 11 | /// Information on which method was invoked and with what parameters. 12 | /// 13 | public InvokeInfo InvokeInfo { get; set; } 14 | 15 | /// 16 | /// Type of the service that was invoked. 17 | /// 18 | public Type ServiceType { get; set; } 19 | } 20 | 21 | /// 22 | /// Callback type used for the OnCallBegin event 23 | /// 24 | /// References the object that fired this event 25 | /// Event information including method name and service type 26 | public delegate void OnCallBeginHandler(object invoker, OnCallBeginHandlerArguments args); 27 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/OnCallSuccessHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator 4 | { 5 | /// 6 | /// Arguments given to the delegate 7 | /// 8 | public class OnCallSuccessHandlerArguments 9 | { 10 | /// 11 | /// Information on which method was invoked and with what parameters. 12 | /// 13 | public InvokeInfo InvokeInfo { get; set; } 14 | 15 | /// 16 | /// Type of the service that was invoked. 17 | /// 18 | public Type ServiceType { get; set; } 19 | 20 | /// 21 | /// Duration of the call 22 | /// 23 | public TimeSpan CallDuration { get; set; } 24 | 25 | /// 26 | /// Amount of tries made to the service before the 27 | /// request was successful 28 | /// 29 | public int RequestAttempts { get; set; } 30 | } 31 | 32 | /// 33 | /// Callback type used for the OnCallSuccess event 34 | /// 35 | /// References the object that fired this event 36 | /// Event information including method name and service type 37 | public delegate void OnCallSuccessHandler(object invoker, OnCallSuccessHandlerArguments args); 38 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/OnExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WcfClientProxyGenerator 9 | { 10 | /// 11 | /// Contains information about the exception that happened 12 | /// and the method invocation. 13 | /// 14 | public class OnExceptionHandlerArguments : OnInvokeHandlerArguments 15 | { 16 | /// 17 | /// Exception object 18 | /// 19 | public Exception Exception { get; set; } 20 | } 21 | 22 | /// 23 | /// Callback type used for the OnException method. 24 | /// 25 | /// References the object that fired this event 26 | /// Event information including method name, service type and the exception 27 | public delegate void OnExceptionHandler(object invoker, OnExceptionHandlerArguments args); 28 | } 29 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/OnInvokeHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WcfClientProxyGenerator 9 | { 10 | /// 11 | /// Contains information about the invoked method. 12 | /// 13 | public class OnInvokeHandlerArguments 14 | { 15 | /// 16 | /// Information on which method was invoked and with what parameters. 17 | /// 18 | public InvokeInfo InvokeInfo { get; set; } 19 | 20 | /// 21 | /// Type of the service that was invoked. 22 | /// 23 | public Type ServiceType { get; set; } 24 | 25 | /// 26 | /// True if this is a re-try of a service method invocation. 27 | /// 28 | /// 29 | public bool IsRetry 30 | { 31 | get 32 | { 33 | return RetryCounter > 0; 34 | } 35 | } 36 | 37 | /// 38 | /// How many times this method has been tried to be invoked. 39 | /// 40 | /// 41 | public int RetryCounter { get; set; } 42 | } 43 | 44 | /// 45 | /// Callback type used for the OnBeforeInvoke and OnAfterInvoke methods. 46 | /// 47 | /// References the object that fired this event 48 | /// Event information including method name and service type 49 | public delegate void OnInvokeHandler(object invoker, OnInvokeHandlerArguments args); 50 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Policy/ConstantDelayPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator.Policy 4 | { 5 | /// 6 | /// Delays failed calls by a constant amount of time 7 | /// 8 | public class ConstantDelayPolicy : IDelayPolicy 9 | { 10 | private readonly TimeSpan delay; 11 | 12 | /// 13 | /// Delays failed calls by 14 | /// regardless of the current iteration 15 | /// 16 | /// 17 | public ConstantDelayPolicy(TimeSpan delay) 18 | { 19 | this.delay = delay; 20 | } 21 | 22 | /// 23 | /// Gets the amount of time that failed calls to the WCF 24 | /// service will delay by 25 | /// 26 | /// 27 | public TimeSpan GetDelay(int iteration) 28 | { 29 | return this.delay; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Policy/ExponentialBackoffDelayPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator.Policy 4 | { 5 | /// 6 | /// Delays failed calls by an amount of time that 7 | /// grows exponentially (2^n) 8 | /// 9 | public class ExponentialBackoffDelayPolicy : IDelayPolicy 10 | { 11 | private readonly TimeSpan minimumDelay; 12 | private readonly TimeSpan maximumDelay; 13 | 14 | /// 15 | /// Delays failed calls by an amount of time starting 16 | /// at that grows exponentially 17 | /// based on the iteration count 18 | /// 19 | /// 20 | public ExponentialBackoffDelayPolicy(TimeSpan minimumDelay) 21 | : this(minimumDelay, TimeSpan.MaxValue) 22 | {} 23 | 24 | /// 25 | /// Delays failed calls by an amount of time starting 26 | /// at that grows exponentially 27 | /// based on the iteration count. The maximum amount of time that a single 28 | /// iteration will delay is defined by 29 | /// 30 | /// 31 | /// 32 | public ExponentialBackoffDelayPolicy(TimeSpan minimumDelay, TimeSpan maximumDelay) 33 | { 34 | this.minimumDelay = minimumDelay; 35 | this.maximumDelay = maximumDelay; 36 | } 37 | 38 | /// 39 | /// Gets the amount of time that failed calls to the WCF 40 | /// service will delay by 41 | /// 42 | /// 43 | public TimeSpan GetDelay(int iteration) 44 | { 45 | double delay = Math.Pow(2d, iteration) * this.minimumDelay.TotalMilliseconds; 46 | 47 | return delay < this.maximumDelay.TotalMilliseconds 48 | ? TimeSpan.FromMilliseconds(delay) 49 | : this.maximumDelay; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Policy/IDelayPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator.Policy 4 | { 5 | /// 6 | /// Defines how the delay between failed WCF calls is calculated 7 | /// 8 | public interface IDelayPolicy 9 | { 10 | /// 11 | /// Gets the amount of time that failed calls to the WCF 12 | /// service will delay by 13 | /// 14 | /// 15 | TimeSpan GetDelay(int iteration); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Policy/LinearBackoffDelayPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator.Policy 4 | { 5 | /// 6 | /// Delays failed calls by an amount of time that 7 | /// grows linearly (i*n) 8 | /// 9 | public class LinearBackoffDelayPolicy : IDelayPolicy 10 | { 11 | private readonly TimeSpan minimumDelay; 12 | private readonly TimeSpan maximumDelay; 13 | 14 | /// 15 | /// Delays failed calls by an amount of time starting 16 | /// at that grows linearly 17 | /// based on the iteration count 18 | /// 19 | /// 20 | public LinearBackoffDelayPolicy(TimeSpan minimumDelay) 21 | : this(minimumDelay, TimeSpan.MaxValue) 22 | {} 23 | 24 | /// 25 | /// Delays failed calls by an amount of time starting 26 | /// at that grows linearly 27 | /// based on the iteration count. The maximum amount of time that a single 28 | /// iteration will delay is defined by 29 | /// 30 | /// 31 | /// 32 | public LinearBackoffDelayPolicy(TimeSpan minimumDelay, TimeSpan maximumDelay) 33 | { 34 | this.minimumDelay = minimumDelay; 35 | this.maximumDelay = maximumDelay; 36 | } 37 | 38 | /// 39 | /// Gets the amount of time that failed calls to the WCF 40 | /// service will delay by 41 | /// 42 | /// 43 | public TimeSpan GetDelay(int iteration) 44 | { 45 | var delay = TimeSpan.FromMilliseconds(this.minimumDelay.TotalMilliseconds * (iteration + 1)); 46 | 47 | return delay < this.maximumDelay 48 | ? delay 49 | : this.maximumDelay; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WcfClientProxyGenerator")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyCopyright("Copyright © 2013")] 12 | [assembly: AssemblyTrademark("")] 13 | [assembly: AssemblyCulture("")] 14 | 15 | // Setting ComVisible to false makes the types in this assembly not visible 16 | // to COM components. If you need to access a type in this assembly from 17 | // COM, set the ComVisible attribute to true on that type. 18 | [assembly: ComVisible(false)] 19 | 20 | // The following GUID is for the ID of the typelib if this project is exposed to COM 21 | [assembly: Guid("4a4d82bf-bc01-4221-bfe1-7525627ba296")] 22 | 23 | [assembly: InternalsVisibleTo("WcfClientProxyGenerator.DynamicProxy")] 24 | [assembly: InternalsVisibleTo("WcfClientProxyGenerator.Tests")] -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/RetryingWcfActionInvokerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.ServiceModel; 7 | using System.ServiceModel.Channels; 8 | using System.ServiceModel.Description; 9 | using WcfClientProxyGenerator.Async; 10 | using WcfClientProxyGenerator.Policy; 11 | using WcfClientProxyGenerator.Util; 12 | 13 | namespace WcfClientProxyGenerator 14 | { 15 | class PredicateHandlerHolder 16 | { 17 | public object Predicate { get; set; } 18 | public object Handler { get; set; } 19 | } 20 | 21 | internal class RetryingWcfActionInvokerProvider : 22 | IActionInvokerProvider, 23 | IRetryingProxyConfigurator 24 | where TServiceInterface : class 25 | { 26 | private static ConcurrentDictionary>> TypeHierarchyCache 27 | = new ConcurrentDictionary>>(); 28 | 29 | private static ConcurrentDictionary> RequestParameterHandlerPredicateCache 30 | = new ConcurrentDictionary>(); 31 | 32 | private static ConcurrentDictionary> RequestParameterHandlerCache 33 | = new ConcurrentDictionary>(); 34 | 35 | private ChannelFactory channelFactory; 36 | private readonly RetryingWcfActionInvoker actionInvoker; 37 | 38 | private readonly IDictionary> requestArgumentHandlers 39 | = new Dictionary>(); 40 | 41 | public RetryingWcfActionInvokerProvider() 42 | { 43 | actionInvoker = new RetryingWcfActionInvoker(() => 44 | { 45 | if (channelFactory == null) 46 | this.UseDefaultEndpoint(); 47 | 48 | return channelFactory.CreateChannel(); 49 | }); 50 | } 51 | 52 | public IActionInvoker ActionInvoker 53 | { 54 | get { return actionInvoker; } 55 | } 56 | 57 | #region HandleRequestArgument 58 | 59 | /// 60 | /// Allows inspection or modification of request arguments immediately before sending the request. 61 | /// 62 | /// Type or parent type/interface of the argument 63 | /// Predicate to filter the request arguments by properties of the request, or the parameter name 64 | /// Delegate that takes a 65 | public void HandleRequestArgument(Func where, Action handler) 66 | { 67 | this.HandleRequestArgument(where, r => 68 | { 69 | handler(r); 70 | return r; 71 | }); 72 | } 73 | 74 | /// 75 | /// Allows inspection or modification of request arguments immediately before sending the request. 76 | /// 77 | /// Type or parent type/interface of the argument 78 | /// Delegate that takes a 79 | public void HandleRequestArgument(Action handler) 80 | { 81 | this.HandleRequestArgument(null, handler); 82 | } 83 | 84 | /// 85 | /// Allows inspection or modification of request arguments immediately before sending the request. 86 | /// 87 | /// Type or parent type/interface of the argument 88 | /// Predicate to filter the request arguments by properties of the request, or the parameter name 89 | /// Delegate that takes a and returns a 90 | public void HandleRequestArgument(Func where, Func handler) 91 | { 92 | if (!this.requestArgumentHandlers.ContainsKey(typeof(TArgument))) 93 | this.requestArgumentHandlers.Add(typeof(TArgument), new List()); 94 | 95 | this.requestArgumentHandlers[typeof(TArgument)].Add(new PredicateHandlerHolder 96 | { 97 | Predicate = where, 98 | Handler = handler 99 | }); 100 | } 101 | 102 | /// 103 | /// Allows inspection or modification of request arguments immediately before sending the request. 104 | /// 105 | /// Type or parent type/interface of the argument 106 | /// Delegate that takes a and returns a 107 | public void HandleRequestArgument(Func handler) 108 | { 109 | this.HandleRequestArgument(null, handler); 110 | } 111 | 112 | #region Runtime Handler Resolution 113 | 114 | /// 115 | /// Called into by the dynamically generated proxy 116 | /// 117 | protected TArgument HandleRequestArgument(TArgument argument, string parameterName) 118 | { 119 | // Don't attempt handler resolution if there aren't any registered 120 | if (!this.requestArgumentHandlers.Any()) 121 | return argument; 122 | 123 | argument = ExecuteRequestArgumentHandlers(argument, parameterName); 124 | return argument; 125 | } 126 | 127 | private TArgument ExecuteRequestArgumentHandlers(TArgument requestArgument, string parameterName) 128 | { 129 | Type @type = typeof(TArgument); 130 | var baseTypes = TypeHierarchyCache.GetOrAddSafe(@type, _ => 131 | { 132 | return @type.GetAllInheritedTypes(); 133 | }); 134 | 135 | foreach (var baseType in baseTypes) 136 | requestArgument = this.ExecuteRequestArgumentHandlers(requestArgument, parameterName, baseType); 137 | 138 | return requestArgument; 139 | } 140 | 141 | private TArgument ExecuteRequestArgumentHandlers(TArgument response, string parameterName, Type @type) 142 | { 143 | if (!this.requestArgumentHandlers.ContainsKey(@type)) 144 | return response; 145 | 146 | IList requestParameterHandlerHolders = this.requestArgumentHandlers[@type]; 147 | 148 | MethodInfo predicateInvokeMethod = RequestParameterHandlerPredicateCache.GetOrAddSafe(@type, _ => 149 | { 150 | Type predicateType = typeof(Func<,,>) 151 | .MakeGenericType(@type, typeof(string), typeof(bool)); 152 | 153 | return predicateType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); 154 | }); 155 | 156 | var handlers = requestParameterHandlerHolders 157 | .Where(m => m.Predicate == null 158 | || ((bool) predicateInvokeMethod.Invoke(m.Predicate, new object[] { response, parameterName }))) 159 | .ToList(); 160 | 161 | if (!handlers.Any()) 162 | return response; 163 | 164 | MethodInfo handlerMethod = RequestParameterHandlerCache.GetOrAddSafe(@type, _ => 165 | { 166 | Type actionType = typeof(Func<,>).MakeGenericType(@type, @type); 167 | return actionType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); 168 | }); 169 | 170 | foreach (var handler in handlers) 171 | { 172 | try 173 | { 174 | response = (TArgument) handlerMethod.Invoke(handler.Handler, new object[] { response }); 175 | } 176 | catch (TargetInvocationException ex) 177 | { 178 | throw ex.InnerException; 179 | } 180 | } 181 | 182 | return response; 183 | } 184 | 185 | #endregion 186 | 187 | #endregion 188 | 189 | #region HandleResponse 190 | 191 | /// 192 | /// Allows inspecting and modifying the object 193 | /// before returning the response to the calling method. 194 | /// 195 | /// Type or parent type/interface of the response 196 | /// Predicate to filter responses based on its parameters 197 | /// 198 | /// Delegate that takes a 199 | /// 200 | public void HandleResponse(Predicate @where, Action handler) 201 | { 202 | actionInvoker.AddResponseHandler(r => 203 | { 204 | handler(r); 205 | return r; 206 | }, @where); 207 | } 208 | 209 | /// 210 | /// Allows inspecting and modifying the object 211 | /// before returning the response to the calling method. 212 | /// 213 | /// Type or parent type/interface of the response 214 | /// 215 | /// Delegate that takes a 216 | /// 217 | public void HandleResponse(Action handler) 218 | { 219 | actionInvoker.AddResponseHandler(r => 220 | { 221 | handler(r); 222 | return r; 223 | }, null); 224 | } 225 | 226 | /// 227 | /// Allows inspecting and modifying the object 228 | /// before returning the response to the calling method. 229 | /// 230 | /// Type or parent type/interface of the response 231 | /// 232 | /// Delegate that takes a and returns a 233 | /// 234 | public void HandleResponse(Func handler) 235 | { 236 | actionInvoker.AddResponseHandler(handler, null); 237 | } 238 | 239 | /// 240 | /// Allows inspecting and modifying the object 241 | /// before returning the response to the calling method. 242 | /// 243 | /// Type or parent type/interface of the response 244 | /// Predicate to filter responses based on its parameters 245 | /// 246 | /// Delegate that takes a and returns a 247 | /// 248 | public void HandleResponse(Predicate @where, Func handler) 249 | { 250 | actionInvoker.AddResponseHandler(handler, @where); 251 | } 252 | 253 | #endregion 254 | 255 | #region IRetryingProxyConfigurator 256 | 257 | /// 258 | /// Event that is fired immediately before the service method will be called. This event 259 | /// is called only once per request. 260 | /// 261 | public event OnCallBeginHandler OnCallBegin 262 | { 263 | add { actionInvoker.OnCallBegin += value; } 264 | remove { actionInvoker.OnCallBegin -= value; } 265 | } 266 | 267 | /// 268 | /// Event that is fired immediately after the request successfully or unsuccessfully completes. 269 | /// 270 | public event OnCallSuccessHandler OnCallSuccess 271 | { 272 | add { actionInvoker.OnCallSuccess += value; } 273 | remove { actionInvoker.OnCallSuccess -= value; } 274 | } 275 | 276 | /// 277 | /// Fires before the invocation of a service method, at every retry. 278 | /// 279 | public event OnInvokeHandler OnBeforeInvoke 280 | { 281 | add { actionInvoker.OnBeforeInvoke += value; } 282 | remove { actionInvoker.OnBeforeInvoke -= value; } 283 | } 284 | 285 | /// 286 | /// Fires after the successful invocation of a method. 287 | /// 288 | public event OnInvokeHandler OnAfterInvoke 289 | { 290 | add { actionInvoker.OnAfterInvoke += value; } 291 | remove { actionInvoker.OnAfterInvoke -= value; } 292 | } 293 | 294 | /// 295 | /// Fires after the successful invocation of a method. 296 | /// 297 | public event OnExceptionHandler OnException 298 | { 299 | add { actionInvoker.OnException += value; } 300 | remove { actionInvoker.OnException -= value; } 301 | } 302 | 303 | /// 304 | /// Allows access to WCF extensibility features. 305 | /// 306 | public ChannelFactory ChannelFactory 307 | { 308 | get 309 | { 310 | // if requested without endpoint set, use default 311 | if (channelFactory == null) 312 | { 313 | UseDefaultEndpoint(); 314 | } 315 | 316 | return channelFactory; 317 | } 318 | } 319 | 320 | public void UseDefaultEndpoint() 321 | { 322 | // If TServiceInterface is our generated async interface, the ChannelFactory 323 | // will look for a config based on the *Async contract. We need to fix this manually. 324 | if (typeof(TServiceInterface).GetCustomAttribute() != null) 325 | { 326 | Type originalServiceInterfaceType = typeof(TServiceInterface).GetInterfaces()[0]; 327 | channelFactory = ChannelFactoryProvider.GetChannelFactory(originalServiceInterfaceType); 328 | } 329 | else 330 | { 331 | channelFactory = ChannelFactoryProvider.GetChannelFactory(typeof(TServiceInterface)); 332 | } 333 | } 334 | 335 | public void SetEndpoint(string endpointConfigurationName) 336 | { 337 | if (typeof(TServiceInterface).HasAttribute()) 338 | { 339 | Type originalServiceInterfaceType = typeof(TServiceInterface).GetInterfaces()[0]; 340 | channelFactory = ChannelFactoryProvider.GetChannelFactory(endpointConfigurationName, originalServiceInterfaceType); 341 | } 342 | else 343 | { 344 | channelFactory = ChannelFactoryProvider.GetChannelFactory(endpointConfigurationName); 345 | } 346 | } 347 | 348 | public void SetEndpoint(Binding binding, EndpointAddress endpointAddress) 349 | { 350 | channelFactory = ChannelFactoryProvider.GetChannelFactory(binding, endpointAddress); 351 | } 352 | 353 | public void SetEndpoint(Binding binding, EndpointAddress endpointAddress, object callbackObject) 354 | { 355 | channelFactory = ChannelFactoryProvider.GetChannelFactory(binding, endpointAddress, callbackObject); 356 | } 357 | 358 | public void SetEndpoint(Binding binding, EndpointAddress endpointAddress, InstanceContext instanceContext) 359 | { 360 | channelFactory = ChannelFactoryProvider.GetChannelFactory(binding, endpointAddress, instanceContext); 361 | } 362 | 363 | public void SetEndpoint(ServiceEndpoint endpoint) 364 | { 365 | channelFactory = ChannelFactoryProvider.GetChannelFactory(endpoint); 366 | } 367 | 368 | public void SetEndpoint(ServiceEndpoint endpoint, object callbackObject) 369 | { 370 | channelFactory = ChannelFactoryProvider.GetChannelFactory(endpoint, callbackObject); 371 | } 372 | 373 | public void SetEndpoint(ServiceEndpoint endpoint, InstanceContext instanceContext) 374 | { 375 | channelFactory = ChannelFactoryProvider.GetChannelFactory(endpoint, instanceContext); 376 | } 377 | 378 | public void MaximumRetries(int retryCount) 379 | { 380 | actionInvoker.RetryCount = retryCount; 381 | } 382 | 383 | public void SetDelayPolicy(Func policyFactory) 384 | { 385 | actionInvoker.DelayPolicyFactory = policyFactory; 386 | } 387 | 388 | public void RetryOnException(Predicate where = null) 389 | where TException : Exception 390 | { 391 | actionInvoker.AddExceptionToRetryOn(where); 392 | } 393 | 394 | public void RetryOnException(Type exceptionType, Predicate where = null) 395 | { 396 | actionInvoker.AddExceptionToRetryOn(exceptionType, where); 397 | } 398 | 399 | public void RetryOnResponse(Predicate where) 400 | { 401 | actionInvoker.AddResponseToRetryOn(where); 402 | } 403 | 404 | public void RetryFailureExceptionFactory(RetryFailureExceptionFactoryDelegate factory) 405 | { 406 | actionInvoker.RetryFailureExceptionFactory = factory; 407 | } 408 | 409 | #endregion 410 | } 411 | } -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Util/AttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace WcfClientProxyGenerator.Util 6 | { 7 | internal static class AttributeExtensions 8 | { 9 | public static bool HasAttribute(this ICustomAttributeProvider provider) 10 | where TAttribute : Attribute 11 | { 12 | return provider.GetCustomAttributes(typeof(TAttribute), false).Any(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Util/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace WcfClientProxyGenerator.Util 5 | { 6 | internal static class DictionaryExtensions 7 | { 8 | public static TValue GetOrAddSafe( 9 | this ConcurrentDictionary> dictionary, 10 | TKey key, 11 | Func valueFactory) 12 | where TValue : class 13 | { 14 | Lazy lazy = dictionary.GetOrAdd(key, new Lazy(() => valueFactory(key))); 15 | return lazy.Value; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Util/FastActivator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | 7 | namespace WcfClientProxyGenerator.Util 8 | { 9 | internal static class FastActivator 10 | { 11 | private static readonly ConcurrentDictionary>> ActivatorCache 12 | = new ConcurrentDictionary>>(); 13 | 14 | public static object CreateInstance(Type type, params object[] args) 15 | { 16 | int offset = type.GetHashCode(); 17 | int key = offset; 18 | foreach (object o in args) 19 | key = key ^ (o == null ? offset : o.GetType().GetHashCode() << offset++); 20 | 21 | var activator = ActivatorCache.GetOrAddSafe(key, _ => BuildActivatorLambda(type, args)); 22 | 23 | return activator(args); 24 | } 25 | 26 | internal static void ClearActivatorCache() 27 | { 28 | ActivatorCache.Clear(); 29 | } 30 | 31 | public static T CreateInstance() 32 | where T : class, new() 33 | { 34 | return new T(); 35 | } 36 | 37 | public static T CreateInstance(object[] args) 38 | where T : class 39 | { 40 | return CreateInstance(typeof(T), args) as T; 41 | } 42 | 43 | private static Func BuildActivatorLambda(Type type, IEnumerable args) 44 | { 45 | var parameterTypes = args.Select(m => m.GetType()).ToArray(); 46 | 47 | var constructorInfo = type.GetConstructor(parameterTypes); 48 | if (constructorInfo == null) 49 | throw new Exception( 50 | string.Format("Could not locate constructor on type {0} with params: {1}", 51 | type.Name, 52 | string.Join(", ", parameterTypes.Select(t => t.Name)))); 53 | 54 | var parameters = constructorInfo.GetParameters(); 55 | 56 | var parameterExpression = Expression.Parameter(typeof(object[]), "args"); 57 | 58 | 59 | var argumentExpression = new Expression[parameters.Length]; 60 | 61 | for (int i = 0; i < parameters.Length; i++) 62 | { 63 | var index = Expression.Constant(i); 64 | var parameterType = parameters[i].ParameterType; 65 | 66 | var parameterAccessorExpression = Expression.ArrayIndex(parameterExpression, index); 67 | 68 | var parameterCastExpression = Expression.Convert(parameterAccessorExpression, parameterType); 69 | 70 | argumentExpression[i] = parameterCastExpression; 71 | } 72 | 73 | var newExpression = Expression.New(constructorInfo, argumentExpression); 74 | var lambda = Expression.Lambda(typeof(Func), newExpression, parameterExpression); 75 | 76 | return (Func) lambda.Compile(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/Util/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WcfClientProxyGenerator.Util 5 | { 6 | internal static class TypeExtensions 7 | { 8 | public static IEnumerable GetAllInheritedTypes( 9 | this Type @type, 10 | bool includeInterfaces = true) 11 | { 12 | var types = new HashSet(); 13 | while (@type != null) 14 | { 15 | types.Add(@type); 16 | 17 | if (includeInterfaces) 18 | { 19 | foreach (var interfaceType in @type.GetInterfaces()) 20 | types.Add(interfaceType); 21 | } 22 | 23 | @type = @type.BaseType; 24 | } 25 | 26 | return types; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/WcfClientProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using WcfClientProxyGenerator.Async; 4 | using WcfClientProxyGenerator.Util; 5 | 6 | namespace WcfClientProxyGenerator 7 | { 8 | /// 9 | /// Factory class for generating WCF clients 10 | /// 11 | public static class WcfClientProxy 12 | { 13 | private static readonly ConcurrentDictionary> ProxyCache 14 | = new ConcurrentDictionary>(); 15 | 16 | /// 17 | /// Creates a proxy instance of that 18 | /// is used to initiate calls to a WCF service. 19 | /// 20 | /// Interface of the WCF service 21 | /// The WCF client proxy 22 | public static TServiceInterface Create() 23 | where TServiceInterface : class 24 | { 25 | return Create((Action)null); 26 | } 27 | 28 | /// 29 | /// Creates a proxy instance of that 30 | /// is used to initiate calls to a WCF service. 31 | /// 32 | /// Interface of the WCF service 33 | /// Name of the WCF service configuration 34 | /// The WCF client proxy 35 | public static TServiceInterface Create(string endpointConfigurationName) 36 | where TServiceInterface : class 37 | { 38 | return Create(c => c.SetEndpoint(endpointConfigurationName)); 39 | } 40 | 41 | /// 42 | /// Creates a proxy instance of that 43 | /// is used to initiate calls to a WCF service. 44 | /// 45 | /// Interface of the WCF service 46 | /// Lambda that defines how the proxy is configured 47 | /// The WCF client proxy 48 | public static TServiceInterface Create(Action configurator) 49 | where TServiceInterface : class 50 | { 51 | var proxy = CreateProxy< 52 | TServiceInterface, 53 | RetryingWcfActionInvokerProvider>(); 54 | 55 | if (configurator != null) 56 | { 57 | configurator(proxy as IRetryingProxyConfigurator); 58 | } 59 | else 60 | { 61 | DefaultProxyConfigurator.Configure(proxy as IRetryingProxyConfigurator); 62 | } 63 | 64 | return proxy; 65 | } 66 | 67 | #region Async Proxy 68 | 69 | /// 70 | /// Creates a wrapper for calling synchronously defined WCF methods asynchronously. 71 | /// 72 | /// Synchronous calls can still be made via the 73 | /// property. 74 | /// 75 | /// 76 | /// Interface of the WCF service 77 | /// Async friendly wrapper around 78 | public static IAsyncProxy CreateAsyncProxy() 79 | where TServiceInterface : class 80 | { 81 | return CreateAsyncProxy(c => c.UseDefaultEndpoint()); 82 | } 83 | 84 | /// 85 | /// Creates a wrapper for calling synchronously defined WCF methods asynchronously. 86 | /// 87 | /// Synchronous calls can still be made via the 88 | /// property. 89 | /// 90 | /// 91 | /// Interface of the WCF service 92 | /// Name of the WCF service configuration 93 | /// Async friendly wrapper around 94 | public static IAsyncProxy CreateAsyncProxy(string endpointConfigurationName) 95 | where TServiceInterface : class 96 | { 97 | return CreateAsyncProxy(c => c.SetEndpoint(endpointConfigurationName)); 98 | } 99 | 100 | /// 101 | /// Creates a wrapper for calling synchronously defined WCF methods asynchronously. 102 | /// 103 | /// Synchronous calls can still be made via the 104 | /// property. 105 | /// 106 | /// 107 | /// Interface of the WCF service 108 | /// Lambda that defines how the proxy is configured 109 | /// Async friendly wrapper around 110 | public static IAsyncProxy CreateAsyncProxy( 111 | Action configurator) 112 | where TServiceInterface : class 113 | { 114 | var proxy = Create(configurator); 115 | return new AsyncProxy(proxy); 116 | } 117 | 118 | #endregion 119 | 120 | private static TServiceInterface CreateProxy() 121 | where TServiceInterface : class 122 | where TActionInvokerProvider : IActionInvokerProvider 123 | { 124 | var types = GetGeneratedTypes(); 125 | return (TServiceInterface) FastActivator.CreateInstance(types.Proxy); 126 | } 127 | 128 | private static GeneratedTypes GetGeneratedTypes() 129 | where TServiceInterface : class 130 | where TActionInvokerProvider : IActionInvokerProvider 131 | { 132 | return ProxyCache.GetOrAddSafe( 133 | typeof(TServiceInterface), 134 | _ => DynamicProxyTypeGenerator.GenerateTypes()); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/WcfClientProxyGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | NET45 6 | Debug 7 | AnyCPU 8 | {56614D90-9EEA-4908-8BEB-7CD6E35BFCB0} 9 | Library 10 | Properties 11 | WcfClientProxyGenerator 12 | WcfClientProxyGenerator 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | 20 | 21 | prompt 22 | 4 23 | false 24 | bin\Debug\ 25 | bin\Debug\WcfClientProxyGenerator.xml 26 | 27 | 28 | pdbonly 29 | true 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\ 34 | bin\Release\WcfClientProxyGenerator.xml 35 | 36 | 37 | NET45 38 | bin\$(Configuration) 39 | v4.5 40 | bin\$(Configuration)\WcfClientProxyGenerator.xml 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Properties\VersionInfo.cs 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 94 | -------------------------------------------------------------------------------- /source/WcfClientProxyGenerator/WcfRetryFailedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WcfClientProxyGenerator 4 | { 5 | /// 6 | /// Thrown when the maximum amount of retried calls is exceeded 7 | /// 8 | public class WcfRetryFailedException : Exception 9 | { 10 | internal WcfRetryFailedException(string message) : base(message) 11 | {} 12 | 13 | internal WcfRetryFailedException(string message, Exception innerException) : base(message, innerException) 14 | {} 15 | } 16 | } 17 | --------------------------------------------------------------------------------