├── .gitattributes ├── .gitignore ├── LICENSE ├── Orleans.HttpGateway.AspNetCore.sln ├── README.md ├── src └── Orleans.HttpGateway.AspNetCore │ ├── CompositeGrainReferenceProvider.cs │ ├── ConfigurationExtensions.cs │ ├── DynamicGrainMethodInvoker.cs │ ├── ExpressionBasedGrainReferenceProvider.cs │ ├── GrainRouteValues.cs │ ├── GrainTypeProviders │ ├── AssemblyBasedGrainTypeProvider.cs │ ├── CachedGrainTypeProvider.cs │ ├── CompositeGrainTypeProvider.cs │ └── IGrainTypeProvider.cs │ ├── IDynamicGrainMethodInvoker.cs │ ├── IGrainReferenceProvider.cs │ ├── Internal │ ├── DelegateFactory.cs │ ├── Internals.cs │ └── ReflectionUtil.cs │ ├── Microsoft.Extensions.Internal │ ├── AwaitableInfo.cs │ ├── CoercedAwaitableInfo.cs │ ├── ObjectMethodExecutor.cs │ ├── ObjectMethodExecutorAwaitable.cs │ └── ObjectMethodExecutorFSharpSupport.cs │ ├── Orleans.HttpGateway.AspNetCore.csproj │ ├── OrleansHttpGatewayMiddleware.cs │ ├── OrleansHttpGatewayOptions.cs │ ├── OrleansHttpGatewayOptionsConfigurator.cs │ └── ParameterBinding │ ├── IParameterBinder.cs │ ├── ImmutableConverter.cs │ ├── JsonBodyParameterBinder.cs │ └── NamedQueryStringParameterBinder.cs └── test └── Orleans.HttpGateway.AspNetCore.Tests ├── AssemblyBasedGrainTypeProviderTests.cs ├── ExpressionBasedGrainReferenceProvider.cs ├── Internal └── ReflectionUtilTests.cs ├── Orleans.HttpGateway.AspNetCore.Tests.csproj ├── OrleansHttpGatewayMiddlewareTests.cs └── TestGrainInterfaces.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /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. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Orleans.HttpGateway.AspNetCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orleans.HttpGateway.AspNetCore", "src\Orleans.HttpGateway.AspNetCore\Orleans.HttpGateway.AspNetCore.csproj", "{85164C86-2F4A-4E36-857C-D572C138EBD4}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orleans.HttpGateway.AspNetCore.Tests", "test\Orleans.HttpGateway.AspNetCore.Tests\Orleans.HttpGateway.AspNetCore.Tests.csproj", "{4EE39394-47CE-4137-805D-159A00233D5A}" 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 | {85164C86-2F4A-4E36-857C-D572C138EBD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {85164C86-2F4A-4E36-857C-D572C138EBD4}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {85164C86-2F4A-4E36-857C-D572C138EBD4}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {85164C86-2F4A-4E36-857C-D572C138EBD4}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {4EE39394-47CE-4137-805D-159A00233D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {4EE39394-47CE-4137-805D-159A00233D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {4EE39394-47CE-4137-805D-159A00233D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {4EE39394-47CE-4137-805D-159A00233D5A}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Orleans.HttpGateway.AspNetCore 2 | a http gateway for Microsoft Orleans. 3 | 4 | [![Build status](https://ci.appveyor.com/api/projects/status/6omov0335yw8a9c5?svg=true)](https://ci.appveyor.com/project/rikbosch/orleans-httpgateway-aspnetcore) [![Nuget version](https://img.shields.io/nuget/v/Orleans.HttpGateway.AspNetCore.svg)](https://www.nuget.org/packages/Orleans.HttpGateway.AspNetCore) 5 | 6 | 7 | 8 | ## Installation 9 | 10 | `Install-Package Orleans.HttpGateway.AspNetCore` 11 | 12 | ## Configuring the gateway 13 | 14 | Startup.cs 15 | 16 | ``` csharp 17 | public void ConfigureService(IServiceCollection services) 18 | { 19 | // add known grain interface assemblies 20 | Assembly grainInterfaceAssembly = GetGrainInterfacesAssembly(); 21 | services.AddOrleansHttpGateway(c => c.AddAssemblies(grainInterfaceAssembly)); 22 | 23 | // ensure IGrainFactory is registered in the service container 24 | services.AddSingleton(GetGrainFactory()); 25 | } 26 | 27 | public void Configure(IApplicationBuilder app) 28 | { 29 | // register the middleware in the application 30 | // note: the middleware internally uses Microsoft.AspNetCore.Routing 31 | // default route is: "{grainInterface}/{grainId}/{grainMethod}" 32 | app.UseOrleansHttpGateway(); 33 | } 34 | 35 | ``` 36 | 37 | __IMPORTANT: Please note that ALL grains are accessible with this gateway, it is adviceable to not publicly expose the endpoint and use any of the default methods to securing your web app__ 38 | 39 | ## Calling grains with http client 40 | 41 | Grainmethods can be invoked using the following url format 42 | 43 | `http://your.aspnet.url/{grainInterface}/{grainId}/{grainMethod}` 44 | 45 | > example: `http://localhost:5000/Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/GetObjectWith2Parameters?one=1&two=okay` 46 | 47 | **Providing method parameters** 48 | 49 | given the following grain interface: 50 | 51 | ```csharp 52 | public interface ITestGrain : IGrainWithStringKey 53 | { 54 | Task TestGrainMethod(string p1, bool p2, string[]p3); 55 | } 56 | ``` 57 | 58 | parameters can be supplied by QueryParameters for `GET` requests, e.g. `?p1=one&p2=true&p3=[a,b,c]` 59 | 60 | complex parameters can also be provided in the request body using `PUT` or `POST`, names of the root elements must match the variable names in the GrainInterface and contenttype must be `application/json`: 61 | 62 | ```json 63 | { 64 | "p1":"one", 65 | "p2":true, 66 | "p3": ["a","b","c"] 67 | } 68 | ``` 69 | 70 | __note: not supplied values will default to null in the resulting grain call__ 71 | 72 | **Using Compound Grain Keys** 73 | 74 | Compound keys can be provided, seperated with a `,` 75 | e.g for an IGrainWithIntegerCompoundKey: `http://localhost:5000/Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/1234,myStringkey/GetObjectWith2Parameters?one=1&two=okay` 76 | 77 | 78 | 79 | 80 | ## Acknowledgements 81 | 82 | this project uses `ObjectMethodExecutor` from `https://github.com/aspnet/Common/tree/dev/shared/Microsoft.Extensions.ObjectMethodExecutor.Sources` 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/CompositeGrainReferenceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Orleans.HttpGateway.AspNetCore 6 | { 7 | internal class CompositeGrainReferenceProvider : IGrainReferenceProvider 8 | { 9 | private readonly IGrainReferenceProvider[] _internalProviders; 10 | 11 | public CompositeGrainReferenceProvider(IEnumerable providers) 12 | { 13 | _internalProviders = providers.ToArray(); 14 | } 15 | 16 | public object GetGrainReference(Type grainType, string id) 17 | { 18 | return _internalProviders.Select(x => x.GetGrainReference(grainType, id)) 19 | .FirstOrDefault(x => x != null); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Routing; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace Orleans.HttpGateway.AspNetCore 8 | { 9 | public static class ConfigurationExtensions 10 | { 11 | public static IServiceCollection AddOrleansHttpGateway(this IServiceCollection services, Action configure) 12 | { 13 | 14 | services.AddRouting(); 15 | services.AddSingleton, OrleansHttpGatewayOptionsConfigurator>(); 16 | services.Configure(options => 17 | { 18 | configure?.Invoke(options); 19 | }); 20 | 21 | return services; 22 | } 23 | 24 | public static IApplicationBuilder UseOrleansHttpGateway(this IApplicationBuilder app) 25 | { 26 | var routeBuilder = new RouteBuilder(app); 27 | 28 | routeBuilder.MapMiddlewareRoute("{grainInterface}/{grainId}/{grainMethod}", part => 29 | { 30 | part.UseMiddleware(); 31 | }); 32 | 33 | var routes = routeBuilder.Build(); 34 | 35 | app.UseRouter(routes); 36 | 37 | return app; 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/DynamicGrainMethodInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using System.Reflection; 7 | using System.Linq; 8 | using System.Runtime.ExceptionServices; 9 | using Microsoft.Extensions.Internal; 10 | using Orleans.HttpGateway.AspNetCore.ParameterBinding; 11 | 12 | namespace Orleans.HttpGateway.AspNetCore 13 | { 14 | public class DynamicGrainMethodInvoker : IDynamicGrainMethodInvoker 15 | { 16 | readonly IParameterBinder[] _parameterBinder; 17 | readonly ConcurrentDictionary _cachedExecutors = new ConcurrentDictionary(); 18 | 19 | public DynamicGrainMethodInvoker(IEnumerable parameterBinders) 20 | { 21 | _parameterBinder = parameterBinders.ToArray(); 22 | } 23 | 24 | public async Task Invoke(Type grainType, object grain, GrainRouteValues grainRouteValues, HttpContext context) 25 | { 26 | var executor = _cachedExecutors.GetOrAdd($"{grainType.FullName}.{grainRouteValues.GrainMethod}", 27 | (key) => 28 | { 29 | var mi = Internal.ReflectionUtil.GetMethodsIncludingBaseInterfaces(grainType) 30 | .FirstOrDefault(x => string.Equals(x.Name, grainRouteValues.GrainMethod)); 31 | return ObjectMethodExecutor.Create(mi, grainType.GetTypeInfo()); 32 | }); 33 | 34 | var parameters = await GetParameters(executor, context.Request); 35 | 36 | return await executor.ExecuteAsync(grain, parameters); 37 | } 38 | 39 | private async Task GetParameters(ObjectMethodExecutor executor, HttpRequest request) 40 | { 41 | //short circuit if no parameters 42 | if (executor.MethodParameters == null || executor.MethodParameters.Length == 0) 43 | { 44 | return Array.Empty(); 45 | } 46 | 47 | // loop through binders, in order 48 | // first suitable binder wins 49 | // so the order of registration is important 50 | 51 | ExceptionDispatchInfo lastException = null; 52 | foreach (var binder in _parameterBinder) 53 | { 54 | try 55 | { 56 | if (await binder.CanBind(executor.MethodParameters, request)) 57 | { 58 | return await binder.BindParameters(executor.MethodParameters, request); 59 | } 60 | } 61 | catch (Exception ex) 62 | { 63 | // continue on next suitable binder 64 | // but keep the exception when no other suitable binders are found 65 | lastException = ExceptionDispatchInfo.Capture(ex); 66 | } 67 | } 68 | 69 | lastException?.Throw(); 70 | 71 | throw new InvalidOperationException("No suitable parameter binder found for request"); 72 | } 73 | } 74 | 75 | 76 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/ExpressionBasedGrainReferenceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Orleans.HttpGateway.AspNetCore.Internal; 7 | 8 | namespace Orleans.HttpGateway.AspNetCore 9 | { 10 | internal class ExpressionBasedGrainReferenceProvider : IGrainReferenceProvider 11 | { 12 | 13 | private readonly IGrainFactory _grainFactory; 14 | private readonly ConcurrentDictionary> _cachedFactoryMethods = new ConcurrentDictionary>(); 15 | 16 | public ExpressionBasedGrainReferenceProvider(IGrainFactory grainFactory) 17 | { 18 | _grainFactory = grainFactory; 19 | } 20 | 21 | private readonly Tuple[] _grainIdentityInterfaceMap = 22 | typeof(IGrainFactory) 23 | .GetMethods() 24 | .Where(x => x.Name == "GetGrain" && x.IsGenericMethod) 25 | .Select(x => Tuple.Create(x.GetGenericArguments()[0].GetGenericParameterConstraints()[0], x)).ToArray(); 26 | 27 | 28 | private Func BuildFactoryMethod(Type grainType) 29 | { 30 | var mi = _grainIdentityInterfaceMap.FirstOrDefault(x => x.Item1.IsAssignableFrom(grainType)); 31 | 32 | if (mi != null) 33 | { 34 | var factoryDelegate = 35 | DelegateFactory.Create(mi.Item2.GetGenericMethodDefinition().MakeGenericMethod(grainType)); 36 | var idParser = GetArgumentParser(mi.Item2.GetParameters()); 37 | return (id) => factoryDelegate(_grainFactory, idParser(id)); 38 | } 39 | throw new NotSupportedException($"cannot construct grain {grainType.Name}"); 40 | } 41 | 42 | private Func GetArgumentParser(ParameterInfo[] parameters) 43 | { 44 | string[] idseperator = new[] { "," }; 45 | 46 | return (id) => 47 | { 48 | var idParts = id.Split(idseperator, StringSplitOptions.RemoveEmptyEntries); 49 | object[] values = new object[parameters.Length]; 50 | for (int i = 0; i < idParts.Length; i++) 51 | { 52 | values[i] = TryParse(idParts[i], parameters[i].ParameterType); 53 | } 54 | 55 | return values; 56 | }; 57 | } 58 | 59 | static object TryParse(string source, Type t) 60 | { 61 | TypeConverter converter = TypeDescriptor.GetConverter(t); 62 | if (converter.CanConvertTo(t) && converter.CanConvertFrom(typeof(string))) 63 | { 64 | return converter.ConvertFromString(source); 65 | } 66 | else if (t == typeof(Guid)) 67 | { 68 | return Guid.Parse(source); 69 | } 70 | throw new ArgumentException($"Can't parse '{source}' as {t.FullName}", nameof(source)); 71 | } 72 | 73 | public object GetGrainReference(Type grainType, string id) 74 | { 75 | return this.GetGrainFactoryMethod(grainType)(id); 76 | } 77 | 78 | private Func GetGrainFactoryMethod(Type grainType) 79 | { 80 | return _cachedFactoryMethods.GetOrAdd(grainType, BuildFactoryMethod); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/GrainRouteValues.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing; 2 | 3 | namespace Orleans.HttpGateway.AspNetCore 4 | { 5 | public class GrainRouteValues 6 | { 7 | public GrainRouteValues(RouteData data) 8 | { 9 | this.GrainInterface = (string)data.Values["grainInterface"]; 10 | this.GrainId = (string)data.Values["grainId"]; 11 | this.GrainMethod = (string)data.Values["grainMethod"]; 12 | } 13 | 14 | public string GrainMethod { get; } 15 | 16 | public string GrainId { get; } 17 | 18 | public string GrainInterface { get; } 19 | } 20 | 21 | 22 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/GrainTypeProviders/AssemblyBasedGrainTypeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace Orleans.HttpGateway.AspNetCore.GrainTypeProviders 7 | { 8 | public class AssemblyBasedGrainTypeProvider : IGrainTypeProvider 9 | { 10 | 11 | private Lazy> _grainInterfaces; 12 | 13 | 14 | public AssemblyBasedGrainTypeProvider(Assembly assembly) 15 | { 16 | this._grainInterfaces = new Lazy>(() => GetGrainTypesFromAssembly(assembly)); 17 | } 18 | 19 | static List GetGrainTypesFromAssembly(Assembly a) 20 | { 21 | return a.GetExportedTypes() 22 | .Where(x => x.IsInterface) 23 | .Where(x => x.GetInterfaces().Any(i => typeof(IGrain).IsAssignableFrom(i))) 24 | .ToList(); 25 | } 26 | 27 | 28 | public Type GetGrainType(string typename) 29 | { 30 | return this._grainInterfaces.Value.FirstOrDefault(x => string.Equals(x.FullName, typename, 31 | StringComparison.OrdinalIgnoreCase)); 32 | } 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/GrainTypeProviders/CachedGrainTypeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace Orleans.HttpGateway.AspNetCore.GrainTypeProviders 5 | { 6 | public class CachedGrainTypeProvider : IGrainTypeProvider 7 | { 8 | private readonly IGrainTypeProvider _inner; 9 | private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); 10 | 11 | public CachedGrainTypeProvider(IGrainTypeProvider inner) 12 | { 13 | _inner = inner; 14 | } 15 | 16 | public Type GetGrainType(string typename) 17 | { 18 | return _cache.GetOrAdd(typename, typename1 => _inner.GetGrainType(typename1)); 19 | } 20 | } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/GrainTypeProviders/CompositeGrainTypeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Orleans.HttpGateway.AspNetCore.GrainTypeProviders 6 | { 7 | public class CompositeGrainTypeProvider : IGrainTypeProvider 8 | { 9 | private readonly IGrainTypeProvider[] _grainTypeProviders; 10 | 11 | public CompositeGrainTypeProvider(IEnumerable grainTypeProviders) 12 | { 13 | _grainTypeProviders = grainTypeProviders.ToArray(); 14 | } 15 | 16 | 17 | public Type GetGrainType(string typename) 18 | { 19 | foreach (var provider in _grainTypeProviders) 20 | { 21 | var type = provider.GetGrainType(typename); 22 | if (type != null) 23 | { 24 | return type; 25 | } 26 | } 27 | 28 | throw new ArgumentException($"Can't find GrainInterface for {typename}", nameof(typename)); 29 | } 30 | } 31 | 32 | 33 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/GrainTypeProviders/IGrainTypeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Orleans.HttpGateway.AspNetCore.GrainTypeProviders 4 | { 5 | public interface IGrainTypeProvider 6 | { 7 | Type GetGrainType(string typename); 8 | } 9 | 10 | 11 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/IDynamicGrainMethodInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Orleans.HttpGateway.AspNetCore 6 | { 7 | public interface IDynamicGrainMethodInvoker 8 | { 9 | Task Invoke(Type grainType, object grain, GrainRouteValues grainRouteValues, HttpContext context); 10 | } 11 | 12 | 13 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/IGrainReferenceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Orleans.HttpGateway.AspNetCore 4 | { 5 | public interface IGrainReferenceProvider 6 | { 7 | object GetGrainReference(Type grainType, string id); 8 | } 9 | 10 | 11 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Internal/DelegateFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace Orleans.HttpGateway.AspNetCore.Internal 7 | { 8 | 9 | /// 10 | /// LateBoundMethod is a generic method signature that is passed an instance 11 | /// and an array of parameters and returns an object. It basically can be 12 | /// used to call any method. 13 | /// 14 | /// 15 | /// The instance that the dynamic method is called on 16 | /// 17 | /// 18 | internal delegate object LateBoundMethod(object target, object[] arguments); 19 | 20 | /// 21 | /// This class creates a generic method delegate from a MethodInfo signature 22 | /// converting the method call into a LateBoundMethod delegate call. Using 23 | /// this class allows making repeated calls very quickly. 24 | /// 25 | /// Note: this class will be very inefficient for individual dynamic method 26 | /// calls - compilation of the expression is very expensive up front, so using 27 | /// this delegate factory makes sense only if you re-use and cache the dynamicly 28 | /// loaded method repeatedly. 29 | /// 30 | /// Entirely based on Nate Kohari's blog post: 31 | /// http://kohari.org/2009/03/06/fast-late-bound-invocation-with-expression-trees/ 32 | /// 33 | internal static class DelegateFactory 34 | { 35 | 36 | /// 37 | /// Creates a LateBoundMethod delegate from a MethodInfo structure 38 | /// Basically creates a dynamic delegate on the fly. 39 | /// 40 | /// 41 | /// 42 | public static LateBoundMethod Create(MethodInfo method) 43 | { 44 | ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "target"); 45 | ParameterExpression argumentsParameter = Expression.Parameter(typeof(object[]), "arguments"); 46 | 47 | MethodCallExpression call = Expression.Call( 48 | Expression.Convert(instanceParameter, method.DeclaringType), 49 | method, 50 | CreateParameterExpressions(method, argumentsParameter)); 51 | 52 | Expression lambda = Expression.Lambda( 53 | Expression.Convert(call, typeof(object)), 54 | instanceParameter, 55 | argumentsParameter); 56 | 57 | return lambda.Compile(); 58 | } 59 | 60 | 61 | /// 62 | /// Creates a LateBoundMethod from type methodname and parameter signature that 63 | /// is turned into a MethodInfo structure and then parsed into a dynamic delegate 64 | /// 65 | /// 66 | /// 67 | /// 68 | /// 69 | public static LateBoundMethod Create(Type type, string methodName, params Type[] parameterTypes) 70 | { 71 | return Create(type.GetMethod(methodName, parameterTypes)); 72 | } 73 | 74 | private static Expression[] CreateParameterExpressions(MethodInfo method, Expression argumentsParameter) 75 | { 76 | return method.GetParameters().Select((parameter, index) => 77 | Expression.Convert( 78 | Expression.ArrayIndex(argumentsParameter, Expression.Constant(index)), 79 | parameter.ParameterType)).ToArray(); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Internal/Internals.cs: -------------------------------------------------------------------------------- 1 |  2 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Orleans.HttpGateway.AspNetCore.Tests")] -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Internal/ReflectionUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace Orleans.HttpGateway.AspNetCore.Internal 8 | { 9 | internal static class ReflectionUtil 10 | 11 | { 12 | public static IEnumerable GetMethodsIncludingBaseInterfaces(Type t) 13 | { 14 | const BindingFlags flags = BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance; 15 | 16 | 17 | foreach (var mi in t.GetMethods()) 18 | { 19 | yield return mi; 20 | } 21 | 22 | foreach (Type interf in t.GetInterfaces()) 23 | { 24 | foreach (MethodInfo method in interf.GetMethods(flags)) 25 | yield return method; 26 | } 27 | } 28 | 29 | public static Type GetAnyElementType(Type type) 30 | { 31 | // Type is Array 32 | // short-circuit if you expect lots of arrays 33 | if (type.IsArray) 34 | return type.GetElementType(); 35 | 36 | // type is IEnumerable; 37 | if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) 38 | return type.GetGenericArguments()[0]; 39 | 40 | // type implements/extends IEnumerable; 41 | var enumType = type.GetInterfaces() 42 | .Where(t => t.IsGenericType && 43 | t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) 44 | .Select(t => t.GenericTypeArguments[0]).FirstOrDefault(); 45 | return enumType ?? type; 46 | } 47 | 48 | public static Func GetObjectActivator(Type type, Type arg1) 49 | { 50 | var ctor = type.GetConstructor(new[] {arg1}); 51 | 52 | ParameterExpression param = 53 | Expression.Parameter(typeof(object), "arg"); 54 | 55 | Expression[] argsExp = 56 | { 57 | Expression.Convert(param, arg1) 58 | }; 59 | 60 | NewExpression newExp = Expression.New(ctor, argsExp); 61 | 62 | var convert = Expression.Convert(newExp, typeof(object)); 63 | 64 | // Create a lambda with the New expression as body and our param object[] as arg 65 | LambdaExpression lambda = Expression.Lambda(typeof(Func), convert, param); 66 | 67 | // Compile it 68 | return (Func)lambda.Compile(); 69 | } 70 | 71 | public static Func GetValueGetter(Type type,string propertyName) 72 | { 73 | var propertyInfo = type.GetProperty(propertyName); 74 | 75 | var instance = Expression.Parameter(typeof(object), "i"); 76 | 77 | var property = Expression.Property(Expression.Convert(instance,type), propertyInfo); 78 | var convert = Expression.Convert(property, typeof(object)); 79 | return (Func)Expression.Lambda(convert, instance).Compile(); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Microsoft.Extensions.Internal/AwaitableInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace Microsoft.Extensions.Internal 10 | { 11 | internal struct AwaitableInfo 12 | { 13 | public Type AwaiterType { get; } 14 | public PropertyInfo AwaiterIsCompletedProperty { get; } 15 | public MethodInfo AwaiterGetResultMethod { get; } 16 | public MethodInfo AwaiterOnCompletedMethod { get; } 17 | public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } 18 | public Type ResultType { get; } 19 | public MethodInfo GetAwaiterMethod { get; } 20 | 21 | public AwaitableInfo( 22 | Type awaiterType, 23 | PropertyInfo awaiterIsCompletedProperty, 24 | MethodInfo awaiterGetResultMethod, 25 | MethodInfo awaiterOnCompletedMethod, 26 | MethodInfo awaiterUnsafeOnCompletedMethod, 27 | Type resultType, 28 | MethodInfo getAwaiterMethod) 29 | { 30 | AwaiterType = awaiterType; 31 | AwaiterIsCompletedProperty = awaiterIsCompletedProperty; 32 | AwaiterGetResultMethod = awaiterGetResultMethod; 33 | AwaiterOnCompletedMethod = awaiterOnCompletedMethod; 34 | AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; 35 | ResultType = resultType; 36 | GetAwaiterMethod = getAwaiterMethod; 37 | } 38 | 39 | public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) 40 | { 41 | // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 42 | 43 | // Awaitable must have method matching "object GetAwaiter()" 44 | var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => 45 | m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) 46 | && m.GetParameters().Length == 0 47 | && m.ReturnType != null); 48 | if (getAwaiterMethod == null) 49 | { 50 | awaitableInfo = default(AwaitableInfo); 51 | return false; 52 | } 53 | 54 | var awaiterType = getAwaiterMethod.ReturnType; 55 | 56 | // Awaiter must have property matching "bool IsCompleted { get; }" 57 | var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => 58 | p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) 59 | && p.PropertyType == typeof(bool) 60 | && p.GetMethod != null); 61 | if (isCompletedProperty == null) 62 | { 63 | awaitableInfo = default(AwaitableInfo); 64 | return false; 65 | } 66 | 67 | // Awaiter must implement INotifyCompletion 68 | var awaiterInterfaces = awaiterType.GetInterfaces(); 69 | var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); 70 | if (!implementsINotifyCompletion) 71 | { 72 | awaitableInfo = default(AwaitableInfo); 73 | return false; 74 | } 75 | 76 | // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" 77 | var iNotifyCompletionMap = awaiterType 78 | .GetTypeInfo() 79 | .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); 80 | var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => 81 | m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) 82 | && m.ReturnType == typeof(void) 83 | && m.GetParameters().Length == 1 84 | && m.GetParameters()[0].ParameterType == typeof(Action)); 85 | 86 | // Awaiter optionally implements ICriticalNotifyCompletion 87 | var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); 88 | MethodInfo unsafeOnCompletedMethod; 89 | if (implementsICriticalNotifyCompletion) 90 | { 91 | // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" 92 | var iCriticalNotifyCompletionMap = awaiterType 93 | .GetTypeInfo() 94 | .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); 95 | unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => 96 | m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) 97 | && m.ReturnType == typeof(void) 98 | && m.GetParameters().Length == 1 99 | && m.GetParameters()[0].ParameterType == typeof(Action)); 100 | } 101 | else 102 | { 103 | unsafeOnCompletedMethod = null; 104 | } 105 | 106 | // Awaiter must have method matching "void GetResult" or "T GetResult()" 107 | var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => 108 | m.Name.Equals("GetResult") 109 | && m.GetParameters().Length == 0); 110 | if (getResultMethod == null) 111 | { 112 | awaitableInfo = default(AwaitableInfo); 113 | return false; 114 | } 115 | 116 | awaitableInfo = new AwaitableInfo( 117 | awaiterType, 118 | isCompletedProperty, 119 | getResultMethod, 120 | onCompletedMethod, 121 | unsafeOnCompletedMethod, 122 | getResultMethod.ReturnType, 123 | getAwaiterMethod); 124 | return true; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Microsoft.Extensions.Internal/CoercedAwaitableInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq.Expressions; 6 | 7 | namespace Microsoft.Extensions.Internal 8 | { 9 | internal struct CoercedAwaitableInfo 10 | { 11 | public AwaitableInfo AwaitableInfo { get; } 12 | public Expression CoercerExpression { get; } 13 | public Type CoercerResultType { get; } 14 | public bool RequiresCoercion => CoercerExpression != null; 15 | 16 | public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) 17 | { 18 | AwaitableInfo = awaitableInfo; 19 | CoercerExpression = null; 20 | CoercerResultType = null; 21 | } 22 | 23 | public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo) 24 | { 25 | CoercerExpression = coercerExpression; 26 | CoercerResultType = coercerResultType; 27 | AwaitableInfo = coercedAwaitableInfo; 28 | } 29 | 30 | public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) 31 | { 32 | if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) 33 | { 34 | info = new CoercedAwaitableInfo(directlyAwaitableInfo); 35 | return true; 36 | } 37 | 38 | // It's not directly awaitable, but maybe we can coerce it. 39 | // Currently we support coercing FSharpAsync. 40 | if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, 41 | out var coercerExpression, 42 | out var coercerResultType)) 43 | { 44 | if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) 45 | { 46 | info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); 47 | return true; 48 | } 49 | } 50 | 51 | info = default(CoercedAwaitableInfo); 52 | return false; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Microsoft.Extensions.Internal/ObjectMethodExecutor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | 9 | namespace Microsoft.Extensions.Internal 10 | { 11 | internal class ObjectMethodExecutor 12 | { 13 | private readonly object[] _parameterDefaultValues; 14 | private readonly MethodExecutorAsync _executorAsync; 15 | private readonly MethodExecutor _executor; 16 | 17 | private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor = 18 | typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[] { 19 | typeof(object), // customAwaitable 20 | typeof(Func), // getAwaiterMethod 21 | typeof(Func), // isCompletedMethod 22 | typeof(Func), // getResultMethod 23 | typeof(Action), // onCompletedMethod 24 | typeof(Action) // unsafeOnCompletedMethod 25 | }); 26 | 27 | private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) 28 | { 29 | if (methodInfo == null) 30 | { 31 | throw new ArgumentNullException(nameof(methodInfo)); 32 | } 33 | 34 | MethodInfo = methodInfo; 35 | MethodParameters = methodInfo.GetParameters(); 36 | TargetTypeInfo = targetTypeInfo; 37 | MethodReturnType = methodInfo.ReturnType; 38 | 39 | var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo); 40 | 41 | IsMethodAsync = isAwaitable; 42 | AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null; 43 | 44 | // Upstream code may prefer to use the sync-executor even for async methods, because if it knows 45 | // that the result is a specific Task where T is known, then it can directly cast to that type 46 | // and await it without the extra heap allocations involved in the _executorAsync code path. 47 | _executor = GetExecutor(methodInfo, targetTypeInfo); 48 | 49 | if (IsMethodAsync) 50 | { 51 | _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); 52 | } 53 | 54 | _parameterDefaultValues = parameterDefaultValues; 55 | } 56 | 57 | private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, object[] parameters); 58 | 59 | private delegate object MethodExecutor(object target, object[] parameters); 60 | 61 | private delegate void VoidMethodExecutor(object target, object[] parameters); 62 | 63 | public MethodInfo MethodInfo { get; } 64 | 65 | public ParameterInfo[] MethodParameters { get; } 66 | 67 | public TypeInfo TargetTypeInfo { get; } 68 | 69 | public Type AsyncResultType { get; } 70 | 71 | // This field is made internal set because it is set in unit tests. 72 | public Type MethodReturnType { get; internal set; } 73 | 74 | public bool IsMethodAsync { get; } 75 | 76 | public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) 77 | { 78 | return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null); 79 | } 80 | 81 | public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) 82 | { 83 | if (parameterDefaultValues == null) 84 | { 85 | throw new ArgumentNullException(nameof(parameterDefaultValues)); 86 | } 87 | 88 | return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); 89 | } 90 | 91 | /// 92 | /// Executes the configured method on . This can be used whether or not 93 | /// the configured method is asynchronous. 94 | /// 95 | /// 96 | /// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than 97 | /// ExecuteAsync if you know at compile time what the return type is, because then you can directly 98 | /// "await" that value (via a cast), and then the generated code will be able to reference the 99 | /// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated 100 | /// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at 101 | /// compile time what type it would be. 102 | /// 103 | /// The object whose method is to be executed. 104 | /// Parameters to pass to the method. 105 | /// The method return value. 106 | public object Execute(object target, object[] parameters) 107 | { 108 | return _executor(target, parameters); 109 | } 110 | 111 | /// 112 | /// Executes the configured method on . This can only be used if the configured 113 | /// method is asynchronous. 114 | /// 115 | /// 116 | /// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, 117 | /// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations 118 | /// as compared with using Execute and then using "await" on the result value typecasted to the known 119 | /// awaitable type. The possible extra heap allocations are for: 120 | /// 121 | /// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally 122 | /// it's a reference type, and you normally create a new instance per call). 123 | /// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance 124 | /// of it, and if it is, it will have to be boxed so the calling code can reference it as an object). 125 | /// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling 126 | /// code doesn't know what type it's going to be). 127 | /// 128 | /// The object whose method is to be executed. 129 | /// Parameters to pass to the method. 130 | /// An object that you can "await" to get the method return value. 131 | public ObjectMethodExecutorAwaitable ExecuteAsync(object target, object[] parameters) 132 | { 133 | return _executorAsync(target, parameters); 134 | } 135 | 136 | public object GetDefaultValueForParameter(int index) 137 | { 138 | if (_parameterDefaultValues == null) 139 | { 140 | throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); 141 | } 142 | 143 | if (index < 0 || index > MethodParameters.Length - 1) 144 | { 145 | throw new ArgumentOutOfRangeException(nameof(index)); 146 | } 147 | 148 | return _parameterDefaultValues[index]; 149 | } 150 | 151 | private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) 152 | { 153 | // Parameters to executor 154 | var targetParameter = Expression.Parameter(typeof(object), "target"); 155 | var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); 156 | 157 | // Build parameter list 158 | var parameters = new List(); 159 | var paramInfos = methodInfo.GetParameters(); 160 | for (int i = 0; i < paramInfos.Length; i++) 161 | { 162 | var paramInfo = paramInfos[i]; 163 | var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); 164 | var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); 165 | 166 | // valueCast is "(Ti) parameters[i]" 167 | parameters.Add(valueCast); 168 | } 169 | 170 | // Call method 171 | var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); 172 | var methodCall = Expression.Call(instanceCast, methodInfo, parameters); 173 | 174 | // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" 175 | // Create function 176 | if (methodCall.Type == typeof(void)) 177 | { 178 | var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); 179 | var voidExecutor = lambda.Compile(); 180 | return WrapVoidMethod(voidExecutor); 181 | } 182 | else 183 | { 184 | // must coerce methodCall to match ActionExecutor signature 185 | var castMethodCall = Expression.Convert(methodCall, typeof(object)); 186 | var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); 187 | return lambda.Compile(); 188 | } 189 | } 190 | 191 | private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor) 192 | { 193 | return delegate (object target, object[] parameters) 194 | { 195 | executor(target, parameters); 196 | return null; 197 | }; 198 | } 199 | 200 | private static MethodExecutorAsync GetExecutorAsync( 201 | MethodInfo methodInfo, 202 | TypeInfo targetTypeInfo, 203 | CoercedAwaitableInfo coercedAwaitableInfo) 204 | { 205 | // Parameters to executor 206 | var targetParameter = Expression.Parameter(typeof(object), "target"); 207 | var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); 208 | 209 | // Build parameter list 210 | var parameters = new List(); 211 | var paramInfos = methodInfo.GetParameters(); 212 | for (int i = 0; i < paramInfos.Length; i++) 213 | { 214 | var paramInfo = paramInfos[i]; 215 | var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); 216 | var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); 217 | 218 | // valueCast is "(Ti) parameters[i]" 219 | parameters.Add(valueCast); 220 | } 221 | 222 | // Call method 223 | var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); 224 | var methodCall = Expression.Call(instanceCast, methodInfo, parameters); 225 | 226 | // Using the method return value, construct an ObjectMethodExecutorAwaitable based on 227 | // the info we have about its implementation of the awaitable pattern. Note that all 228 | // the funcs/actions we construct here are precompiled, so that only one instance of 229 | // each is preserved throughout the lifetime of the ObjectMethodExecutor. 230 | 231 | // var getAwaiterFunc = (object awaitable) => 232 | // (object)((CustomAwaitableType)awaitable).GetAwaiter(); 233 | var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable"); 234 | var awaitableInfo = coercedAwaitableInfo.AwaitableInfo; 235 | var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType; 236 | var getAwaiterFunc = Expression.Lambda>( 237 | Expression.Convert( 238 | Expression.Call( 239 | Expression.Convert(customAwaitableParam, postCoercionMethodReturnType), 240 | awaitableInfo.GetAwaiterMethod), 241 | typeof(object)), 242 | customAwaitableParam).Compile(); 243 | 244 | // var isCompletedFunc = (object awaiter) => 245 | // ((CustomAwaiterType)awaiter).IsCompleted; 246 | var isCompletedParam = Expression.Parameter(typeof(object), "awaiter"); 247 | var isCompletedFunc = Expression.Lambda>( 248 | Expression.MakeMemberAccess( 249 | Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), 250 | awaitableInfo.AwaiterIsCompletedProperty), 251 | isCompletedParam).Compile(); 252 | 253 | var getResultParam = Expression.Parameter(typeof(object), "awaiter"); 254 | Func getResultFunc; 255 | if (awaitableInfo.ResultType == typeof(void)) 256 | { 257 | // var getResultFunc = (object awaiter) => 258 | // { 259 | // ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions 260 | // return (object)null; 261 | // }; 262 | getResultFunc = Expression.Lambda>( 263 | Expression.Block( 264 | Expression.Call( 265 | Expression.Convert(getResultParam, awaitableInfo.AwaiterType), 266 | awaitableInfo.AwaiterGetResultMethod), 267 | Expression.Constant(null) 268 | ), 269 | getResultParam).Compile(); 270 | } 271 | else 272 | { 273 | // var getResultFunc = (object awaiter) => 274 | // (object)((CustomAwaiterType)awaiter).GetResult(); 275 | getResultFunc = Expression.Lambda>( 276 | Expression.Convert( 277 | Expression.Call( 278 | Expression.Convert(getResultParam, awaitableInfo.AwaiterType), 279 | awaitableInfo.AwaiterGetResultMethod), 280 | typeof(object)), 281 | getResultParam).Compile(); 282 | } 283 | 284 | // var onCompletedFunc = (object awaiter, Action continuation) => { 285 | // ((CustomAwaiterType)awaiter).OnCompleted(continuation); 286 | // }; 287 | var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); 288 | var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); 289 | var onCompletedFunc = Expression.Lambda>( 290 | Expression.Call( 291 | Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), 292 | awaitableInfo.AwaiterOnCompletedMethod, 293 | onCompletedParam2), 294 | onCompletedParam1, 295 | onCompletedParam2).Compile(); 296 | 297 | Action unsafeOnCompletedFunc = null; 298 | if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null) 299 | { 300 | // var unsafeOnCompletedFunc = (object awaiter, Action continuation) => { 301 | // ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation); 302 | // }; 303 | var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); 304 | var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); 305 | unsafeOnCompletedFunc = Expression.Lambda>( 306 | Expression.Call( 307 | Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType), 308 | awaitableInfo.AwaiterUnsafeOnCompletedMethod, 309 | unsafeOnCompletedParam2), 310 | unsafeOnCompletedParam1, 311 | unsafeOnCompletedParam2).Compile(); 312 | } 313 | 314 | // If we need to pass the method call result through a coercer function to get an 315 | // awaitable, then do so. 316 | var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion 317 | ? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall) 318 | : (Expression)methodCall; 319 | 320 | // return new ObjectMethodExecutorAwaitable( 321 | // (object)coercedMethodCall, 322 | // getAwaiterFunc, 323 | // isCompletedFunc, 324 | // getResultFunc, 325 | // onCompletedFunc, 326 | // unsafeOnCompletedFunc); 327 | var returnValueExpression = Expression.New( 328 | _objectMethodExecutorAwaitableConstructor, 329 | Expression.Convert(coercedMethodCall, typeof(object)), 330 | Expression.Constant(getAwaiterFunc), 331 | Expression.Constant(isCompletedFunc), 332 | Expression.Constant(getResultFunc), 333 | Expression.Constant(onCompletedFunc), 334 | Expression.Constant(unsafeOnCompletedFunc, typeof(Action))); 335 | 336 | var lambda = Expression.Lambda(returnValueExpression, targetParameter, parametersParameter); 337 | return lambda.Compile(); 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Microsoft.Extensions.Internal/ObjectMethodExecutorAwaitable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Microsoft.Extensions.Internal 8 | { 9 | /// 10 | /// Provides a common awaitable structure that can 11 | /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an 12 | /// application-defined custom awaitable. 13 | /// 14 | internal struct ObjectMethodExecutorAwaitable 15 | { 16 | private readonly object _customAwaitable; 17 | private readonly Func _getAwaiterMethod; 18 | private readonly Func _isCompletedMethod; 19 | private readonly Func _getResultMethod; 20 | private readonly Action _onCompletedMethod; 21 | private readonly Action _unsafeOnCompletedMethod; 22 | 23 | // Perf note: since we're requiring the customAwaitable to be supplied here as an object, 24 | // this will trigger a further allocation if it was a value type (i.e., to box it). We can't 25 | // fix this by making the customAwaitable type generic, because the calling code typically 26 | // does not know the type of the awaitable/awaiter at compile-time anyway. 27 | // 28 | // However, we could fix it by not passing the customAwaitable here at all, and instead 29 | // passing a func that maps directly from the target object (e.g., controller instance), 30 | // target method (e.g., action method info), and params array to the custom awaiter in the 31 | // GetAwaiter() method below. In effect, by delaying the actual method call until the 32 | // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. 33 | // This optimization is not currently implemented because: 34 | // [1] It would make no difference when the awaitable was an object type, which is 35 | // by far the most common scenario (e.g., System.Task). 36 | // [2] It would be complex - we'd need some kind of object pool to track all the parameter 37 | // arrays until we needed to use them in GetAwaiter(). 38 | // We can reconsider this in the future if there's a need to optimize for ValueTask 39 | // or other value-typed awaitables. 40 | 41 | public ObjectMethodExecutorAwaitable( 42 | object customAwaitable, 43 | Func getAwaiterMethod, 44 | Func isCompletedMethod, 45 | Func getResultMethod, 46 | Action onCompletedMethod, 47 | Action unsafeOnCompletedMethod) 48 | { 49 | _customAwaitable = customAwaitable; 50 | _getAwaiterMethod = getAwaiterMethod; 51 | _isCompletedMethod = isCompletedMethod; 52 | _getResultMethod = getResultMethod; 53 | _onCompletedMethod = onCompletedMethod; 54 | _unsafeOnCompletedMethod = unsafeOnCompletedMethod; 55 | } 56 | 57 | public Awaiter GetAwaiter() 58 | { 59 | var customAwaiter = _getAwaiterMethod(_customAwaitable); 60 | return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod); 61 | } 62 | 63 | public struct Awaiter : ICriticalNotifyCompletion 64 | { 65 | private readonly object _customAwaiter; 66 | private readonly Func _isCompletedMethod; 67 | private readonly Func _getResultMethod; 68 | private readonly Action _onCompletedMethod; 69 | private readonly Action _unsafeOnCompletedMethod; 70 | 71 | public Awaiter( 72 | object customAwaiter, 73 | Func isCompletedMethod, 74 | Func getResultMethod, 75 | Action onCompletedMethod, 76 | Action unsafeOnCompletedMethod) 77 | { 78 | _customAwaiter = customAwaiter; 79 | _isCompletedMethod = isCompletedMethod; 80 | _getResultMethod = getResultMethod; 81 | _onCompletedMethod = onCompletedMethod; 82 | _unsafeOnCompletedMethod = unsafeOnCompletedMethod; 83 | } 84 | 85 | public bool IsCompleted => _isCompletedMethod(_customAwaiter); 86 | 87 | public object GetResult() => _getResultMethod(_customAwaiter); 88 | 89 | public void OnCompleted(Action continuation) 90 | { 91 | _onCompletedMethod(_customAwaiter, continuation); 92 | } 93 | 94 | public void UnsafeOnCompleted(Action continuation) 95 | { 96 | // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. 97 | // If not, fall back on using its OnCompleted. 98 | // 99 | // Why this is safe: 100 | // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it 101 | // needs the execution context to be preserved (which it signals by calling OnCompleted), or 102 | // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* 103 | // to preserve and restore the context, so we prefer that where possible. 104 | // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, 105 | // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen 106 | // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to 107 | // pass the call on to the underlying awaitable's OnCompleted method. 108 | 109 | var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; 110 | underlyingMethodToUse(_customAwaiter, continuation); 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Microsoft.Extensions.Internal/ObjectMethodExecutorFSharpSupport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Microsoft.Extensions.Internal 12 | { 13 | /// 14 | /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying 15 | /// an for mapping instances of that type to a C# awaitable. 16 | /// 17 | /// 18 | /// The main design goal here is to avoid taking a compile-time dependency on 19 | /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references 20 | /// to FSharp types have to be constructed dynamically at runtime. 21 | /// 22 | internal static class ObjectMethodExecutorFSharpSupport 23 | { 24 | private static object _fsharpValuesCacheLock = new object(); 25 | private static Assembly _fsharpCoreAssembly; 26 | private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod; 27 | private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; 28 | private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; 29 | 30 | public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( 31 | Type possibleFSharpAsyncType, 32 | out Expression coerceToAwaitableExpression, 33 | out Type awaitableType) 34 | { 35 | var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType 36 | ? possibleFSharpAsyncType.GetGenericTypeDefinition() 37 | : null; 38 | 39 | if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType)) 40 | { 41 | coerceToAwaitableExpression = null; 42 | awaitableType = null; 43 | return false; 44 | } 45 | 46 | var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single(); 47 | awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType); 48 | 49 | // coerceToAwaitableExpression = (object fsharpAsync) => 50 | // { 51 | // return (object)FSharpAsync.StartAsTask( 52 | // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, 53 | // FSharpOption.None, 54 | // FSharpOption.None); 55 | // }; 56 | var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod 57 | .MakeGenericMethod(awaiterResultType); 58 | var coerceToAwaitableParam = Expression.Parameter(typeof(object)); 59 | coerceToAwaitableExpression = Expression.Lambda( 60 | Expression.Convert( 61 | Expression.Call( 62 | startAsTaskClosedMethod, 63 | Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType), 64 | Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty), 65 | Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)), 66 | typeof(object)), 67 | coerceToAwaitableParam); 68 | 69 | return true; 70 | } 71 | 72 | private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType) 73 | { 74 | var typeFullName = possibleFSharpAsyncGenericType?.FullName; 75 | if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) 76 | { 77 | return false; 78 | } 79 | 80 | lock (_fsharpValuesCacheLock) 81 | { 82 | if (_fsharpCoreAssembly != null) 83 | { 84 | // Since we've already found the real FSharpAsync.Core assembly, we just have 85 | // to check that the supplied FSharpAsync`1 type is the one from that assembly. 86 | return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; 87 | } 88 | else 89 | { 90 | // We'll keep trying to find the FSharp types/values each time any type called 91 | // FSharpAsync`1 is supplied. 92 | return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); 93 | } 94 | } 95 | } 96 | 97 | private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType) 98 | { 99 | var assembly = possibleFSharpAsyncGenericType.Assembly; 100 | var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1"); 101 | var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); 102 | 103 | if (fsharpOptionType == null || fsharpAsyncType == null) 104 | { 105 | return false; 106 | } 107 | 108 | // Get a reference to FSharpOption.None 109 | var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType 110 | .MakeGenericType(typeof(TaskCreationOptions)); 111 | _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType 112 | .GetTypeInfo() 113 | .GetRuntimeProperty("None"); 114 | 115 | // Get a reference to FSharpOption.None 116 | var fsharpOptionOfCancellationTokenType = fsharpOptionType 117 | .MakeGenericType(typeof(CancellationToken)); 118 | _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType 119 | .GetTypeInfo() 120 | .GetRuntimeProperty("None"); 121 | 122 | // Get a reference to FSharpAsync.StartAsTask<> 123 | var fsharpAsyncMethods = fsharpAsyncType 124 | .GetRuntimeMethods() 125 | .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal)); 126 | foreach (var candidateMethodInfo in fsharpAsyncMethods) 127 | { 128 | var parameters = candidateMethodInfo.GetParameters(); 129 | if (parameters.Length == 3 130 | && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType) 131 | && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType 132 | && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType) 133 | { 134 | // This really does look like the correct method (and hence assembly). 135 | _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo; 136 | _fsharpCoreAssembly = assembly; 137 | break; 138 | } 139 | } 140 | 141 | return _fsharpCoreAssembly != null; 142 | } 143 | 144 | private static bool TypesHaveSameIdentity(Type type1, Type type2) 145 | { 146 | return type1.Assembly == type2.Assembly 147 | && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal) 148 | && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/Orleans.HttpGateway.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Rik Bosch 6 | OrleansContrib 7 | An http gateway for Microsoft Orleans, implemented as standarad aspnet core middleware. 8 | 0.3.0 9 | http://dotnet.github.io/orleans/assets/logo.png 10 | https://github.com/OrleansContrib/Orleans.HttpGateway.AspNetCore 11 | https://github.com/OrleansContrib/Orleans.HttpGateway.AspNetCore 12 | https://github.com/OrleansContrib/Orleans.HttpGateway.AspNetCore/blob/master/LICENSE 13 | Copyright © 2018 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/OrleansHttpGatewayMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Routing; 6 | using System.Linq; 7 | using Microsoft.Extensions.Options; 8 | using Newtonsoft.Json; 9 | using Orleans.HttpGateway.AspNetCore.GrainTypeProviders; 10 | using Orleans.HttpGateway.AspNetCore.ParameterBinding; 11 | 12 | namespace Orleans.HttpGateway.AspNetCore 13 | { 14 | public class OrleansHttpGatewayMiddleware 15 | { 16 | private readonly IGrainTypeProvider _grainTypeProvider; 17 | private readonly IGrainReferenceProvider _grainReferenceProvider; 18 | private readonly IDynamicGrainMethodInvoker _grainInvoker; 19 | private readonly JsonSerializer _serializer; 20 | private readonly RequestDelegate _next; 21 | 22 | public OrleansHttpGatewayMiddleware(RequestDelegate next,IOptions config, IGrainFactory grainFactory) 23 | { 24 | if (grainFactory == null) throw new ArgumentNullException(nameof(grainFactory)); 25 | if (config == null) throw new ArgumentNullException(nameof(config)); 26 | 27 | var compositeProvider = new CompositeGrainTypeProvider(config.Value.Assemblies.Select(x => new AssemblyBasedGrainTypeProvider(x))); 28 | 29 | _grainTypeProvider = new CachedGrainTypeProvider(compositeProvider); 30 | _serializer = JsonSerializer.Create(config.Value.JsonSerializerSettings); 31 | _grainReferenceProvider = new ExpressionBasedGrainReferenceProvider(grainFactory); 32 | _grainInvoker = new DynamicGrainMethodInvoker( 33 | new IParameterBinder[] 34 | { 35 | new JsonBodyParameterBinder(_serializer), //order is important here, we expect application/json requests 36 | new NamedQueryStringParameterBinder(_serializer), 37 | }); 38 | _next = next; 39 | } 40 | 41 | public async Task Invoke(HttpContext context) 42 | { 43 | var data = context.GetRouteData(); 44 | var grainRouteValues = new GrainRouteValues(data); 45 | var grainType = _grainTypeProvider.GetGrainType(grainRouteValues.GrainInterface); 46 | var grain = _grainReferenceProvider.GetGrainReference(grainType, grainRouteValues.GrainId); 47 | var result = await _grainInvoker.Invoke(grainType, grain, grainRouteValues, context); 48 | 49 | context.Response.StatusCode = 200; 50 | context.Response.ContentType = "application/json"; 51 | using (var writer = new StreamWriter(context.Response.Body)) 52 | { 53 | _serializer.Serialize(writer, result); 54 | await writer.FlushAsync(); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/OrleansHttpGatewayOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using Newtonsoft.Json; 5 | using Orleans.HttpGateway.AspNetCore.ParameterBinding; 6 | 7 | namespace Orleans.HttpGateway.AspNetCore 8 | { 9 | public class OrleansHttpGatewayOptions 10 | { 11 | public List Assemblies { get; } = new List(); 12 | 13 | public JsonSerializerSettings JsonSerializerSettings { get; set; } 14 | 15 | public OrleansHttpGatewayOptions AddAssemblies(params Assembly[] assemblies) 16 | { 17 | if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); 18 | 19 | foreach (var a in assemblies) 20 | { 21 | if (Assemblies.Contains(a)) 22 | { 23 | continue; 24 | } 25 | Assemblies.Add(a); 26 | } 27 | 28 | return this; 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/OrleansHttpGatewayOptionsConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Routing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Options; 8 | using Newtonsoft.Json; 9 | using Orleans.HttpGateway.AspNetCore.ParameterBinding; 10 | 11 | namespace Orleans.HttpGateway.AspNetCore 12 | { 13 | 14 | internal class OrleansHttpGatewayOptionsConfigurator : IConfigureOptions 15 | { 16 | private readonly IServiceProvider _services; 17 | 18 | public OrleansHttpGatewayOptionsConfigurator(IServiceProvider services) 19 | { 20 | _services = services; 21 | } 22 | 23 | public void Configure(OrleansHttpGatewayOptions options) 24 | { 25 | if (options.JsonSerializerSettings == null) 26 | { 27 | //get the serializer settings from service container 28 | options.JsonSerializerSettings = _services.GetService() 29 | ?? new JsonSerializerSettings(); 30 | } 31 | 32 | if (!options.JsonSerializerSettings.Converters.OfType().Any()) 33 | { 34 | options.JsonSerializerSettings.Converters.Add(new ImmutableConverter()); 35 | } 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/ParameterBinding/IParameterBinder.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Orleans.HttpGateway.AspNetCore.ParameterBinding 6 | { 7 | public interface IParameterBinder 8 | { 9 | Task CanBind(ParameterInfo[] parameters, HttpRequest context); 10 | 11 | Task BindParameters(ParameterInfo[] parameters, HttpRequest context); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/ParameterBinding/ImmutableConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Reflection; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using Orleans.Concurrency; 7 | using Orleans.HttpGateway.AspNetCore.Internal; 8 | 9 | namespace Orleans.HttpGateway.AspNetCore.ParameterBinding 10 | { 11 | public class ImmutableConverter : JsonConverter 12 | { 13 | public override bool CanConvert(Type objectType) 14 | { 15 | if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Immutable<>)) 16 | return true; 17 | 18 | return false; 19 | } 20 | 21 | public override bool CanRead { get; } = true; 22 | public override bool CanWrite { get; } = true; 23 | 24 | 25 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 26 | { 27 | var token = JToken.Load(reader); 28 | var valueType = GetValueType(objectType); 29 | 30 | // need to see if this is formatted as 31 | // 'regular' json, thus with a Value: property 32 | var value = token.ToObject(valueType, serializer); 33 | 34 | 35 | return GetImmutableTInstance(objectType, value); 36 | } 37 | 38 | static readonly ConcurrentDictionary> _activatorCache = new ConcurrentDictionary>(); 39 | 40 | static object GetImmutableTInstance(Type type, object value) 41 | { 42 | var activator = _activatorCache.GetOrAdd(type, (t) => 43 | ReflectionUtil.GetObjectActivator(type, GetValueType(type))); 44 | 45 | return activator(value); 46 | } 47 | 48 | static Type GetValueType(Type immutableType) 49 | { 50 | return immutableType.GetGenericArguments()[0]; 51 | } 52 | 53 | static readonly ConcurrentDictionary> _valueGetterCache = new ConcurrentDictionary>(); 54 | 55 | 56 | static object GetValue(object immutableType) 57 | { 58 | var getter = _valueGetterCache.GetOrAdd(immutableType.GetType(), type =>ReflectionUtil.GetValueGetter(type,"Value")); 59 | return getter(immutableType); 60 | } 61 | 62 | 63 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 64 | { 65 | var token = JToken.FromObject(GetValue(value),serializer); 66 | token.WriteTo(writer); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/ParameterBinding/JsonBodyParameterBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Http.Internal; 9 | using Microsoft.AspNetCore.WebUtilities; 10 | using Microsoft.Net.Http.Headers; 11 | using Newtonsoft.Json; 12 | using Newtonsoft.Json.Linq; 13 | using Orleans.Concurrency; 14 | 15 | namespace Orleans.HttpGateway.AspNetCore.ParameterBinding 16 | { 17 | public class JsonBodyParameterBinder : IParameterBinder 18 | { 19 | private readonly JsonSerializer _serializer; 20 | 21 | public JsonBodyParameterBinder(JsonSerializer serializer) 22 | { 23 | _serializer = serializer; 24 | } 25 | 26 | 27 | public Task CanBind(ParameterInfo[] parameters, HttpRequest request) 28 | { 29 | //some form of content negotiation here... 30 | 31 | //parse mediatype 32 | var requestContentType = request.GetTypedHeaders().ContentType; 33 | if (requestContentType?.MediaType == "application/json" && parameters.Length > 0) 34 | return Task.FromResult(true); 35 | 36 | return Task.FromResult(false); 37 | } 38 | 39 | public async Task BindParameters(ParameterInfo[] parameters, HttpRequest request) 40 | { 41 | var result = new object[parameters.Length]; 42 | 43 | if (request.ContentLength == 0) 44 | { 45 | return result; 46 | } 47 | 48 | if (!request.Body.CanSeek) 49 | { 50 | // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously 51 | // read everything into a buffer, and then seek back to the beginning. 52 | request.EnableRewind(); 53 | 54 | } 55 | request.Body.Seek(0L, SeekOrigin.Begin); 56 | 57 | // parse encoding 58 | // default to UTF8 59 | var encoding = request.GetTypedHeaders().ContentType.Encoding ?? Encoding.UTF8; 60 | 61 | 62 | using (var reader = new JsonTextReader(new StreamReader(request.Body, encoding))) 63 | { 64 | reader.CloseInput = false; 65 | 66 | var root = await JObject.LoadAsync(reader); 67 | 68 | for (int i = 0; i < parameters.Length; i++) 69 | { 70 | var parameter = parameters[i]; 71 | 72 | if (root.TryGetValue(parameter.Name, StringComparison.OrdinalIgnoreCase, out JToken value)) 73 | { 74 | result[i] = value.ToObject(parameter.ParameterType, _serializer); 75 | } 76 | } 77 | } 78 | 79 | return result; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/Orleans.HttpGateway.AspNetCore/ParameterBinding/NamedQueryStringParameterBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Threading.Tasks; 11 | using Microsoft.AspNetCore.Http; 12 | using Microsoft.Extensions.Primitives; 13 | using Newtonsoft.Json; 14 | 15 | namespace Orleans.HttpGateway.AspNetCore.ParameterBinding 16 | { 17 | class NamedQueryStringParameterBinder : IParameterBinder 18 | { 19 | readonly JsonSerializer _serializer; 20 | 21 | public NamedQueryStringParameterBinder(JsonSerializer serializer) 22 | { 23 | this._serializer = serializer; 24 | } 25 | 26 | 27 | public Task CanBind(ParameterInfo[] parameters, HttpRequest request) 28 | { 29 | if (parameters.Length == request.Query.Count) 30 | { 31 | //check parameter names 32 | var source = parameters.Select(x => x.Name).ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); 33 | return Task.FromResult(source.SetEquals(request.Query.Select(x => x.Key))); 34 | } 35 | 36 | return Task.FromResult(false); 37 | } 38 | 39 | public Task BindParameters(ParameterInfo[] parameters, HttpRequest request) 40 | { 41 | var result = new object[parameters.Length]; 42 | 43 | for (int i = 0; i < parameters.Length; i++) 44 | { 45 | var parameter = parameters[i]; 46 | 47 | //support named parameters in querystring 48 | if (request.Query.TryGetValue(parameter.Name, out StringValues value)) 49 | { 50 | if (parameter.ParameterType.IsArray) 51 | { 52 | var elementType = Internal.ReflectionUtil.GetAnyElementType(parameter.ParameterType); 53 | Array array = Array.CreateInstance(elementType, value.Count); 54 | for (int p = 0; p < value.Count; p++) 55 | { 56 | array.SetValue( Convert(value[p], elementType),p); 57 | } 58 | result[i] = array; 59 | } 60 | else 61 | { 62 | result[i] = Convert(value[0], parameter.ParameterType); 63 | } 64 | } 65 | } 66 | return Task.FromResult(result); 67 | } 68 | 69 | object Convert(string source, Type t) 70 | { 71 | TypeConverter converter = TypeDescriptor.GetConverter(t); 72 | if (converter.CanConvertTo(t) && converter.CanConvertFrom(typeof(string))) 73 | { 74 | return converter.ConvertFromString(source); 75 | } 76 | else if (t == typeof(Guid)) 77 | { 78 | return Guid.Parse(source); 79 | } 80 | 81 | //fallback to json serializer.. 82 | using (var jsonTextReader = new JsonTextReader(new StringReader(source))) 83 | { 84 | return _serializer.Deserialize(jsonTextReader, t); 85 | } 86 | } 87 | } 88 | 89 | 90 | } -------------------------------------------------------------------------------- /test/Orleans.HttpGateway.AspNetCore.Tests/AssemblyBasedGrainTypeProviderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Orleans.HttpGateway.AspNetCore; 3 | using Orleans.HttpGateway.AspNetCore.GrainTypeProviders; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace Orleans.HttpGateway.AspNetCore.Tests 8 | { 9 | public class AssemblyBasedGrainTypeProviderTests 10 | { 11 | [Theory 12 | ,InlineData("Orleans.HttpGateway.AspNetCore.Tests.ITestGrain1", typeof(ITestGrain1)) 13 | ,InlineData("Orleans.HttpGateway.AspNetCore.Tests.ITestGrain2", typeof(ITestGrain2)) 14 | ,InlineData("Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3", typeof(ITestGrain3)) 15 | ,InlineData("Orleans.HttpGateway.AspNetCore.Tests.ITestGrainNONEXISTENT", null)] 16 | public void Can_provide_TestGrainInterfaces(string name, Type excpected) 17 | { 18 | var sut = new AssemblyBasedGrainTypeProvider(typeof(AssemblyBasedGrainTypeProviderTests).Assembly); 19 | 20 | sut.GetGrainType(name).ShouldBe(excpected); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /test/Orleans.HttpGateway.AspNetCore.Tests/ExpressionBasedGrainReferenceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moq; 3 | using Orleans.HttpGateway.AspNetCore; 4 | using Xunit; 5 | 6 | namespace Orleans.HttpGateway.AspNetCore.Tests 7 | { 8 | public class ExpressionBasedGrainReferenceProviderTests 9 | { 10 | 11 | [Fact] 12 | public void Should_invoke_correct_method_for_IGrainWithStringKey() 13 | { 14 | var grainFactory = new Mock(); 15 | 16 | var sut = new ExpressionBasedGrainReferenceProvider(grainFactory.Object); 17 | 18 | var result = sut.GetGrainReference(typeof(ITestGrain1), "key"); 19 | 20 | grainFactory.Verify(x => x.GetGrain("key", null), Times.Once); 21 | } 22 | 23 | [Fact] 24 | public void Should_invoke_correct_method_for_IGrainWithGuidKey() 25 | { 26 | var grainFactory = new Mock(); 27 | 28 | var sut = new ExpressionBasedGrainReferenceProvider(grainFactory.Object); 29 | 30 | var result = sut.GetGrainReference(typeof(ITestGrain2), "E3C2E8DA-88EF-4F78-B761-0AA442E6C9CC"); 31 | 32 | grainFactory.Verify(x => x.GetGrain(new Guid("E3C2E8DA-88EF-4F78-B761-0AA442E6C9CC"), null), Times.Once); 33 | } 34 | 35 | 36 | [Fact] 37 | public void Should_invoke_correct_method_for_IGrainWithIntegerKey() 38 | { 39 | var grainFactory = new Mock(); 40 | 41 | var sut = new ExpressionBasedGrainReferenceProvider(grainFactory.Object); 42 | 43 | var result = sut.GetGrainReference(typeof(ITestGrain3), "100"); 44 | 45 | grainFactory.Verify(x => x.GetGrain(100, null), Times.Once); 46 | } 47 | 48 | [Fact] 49 | public void Should_invoke_correct_method_for_IGrainWithIntegerCompoundKey() 50 | { 51 | var grainFactory = new Mock(); 52 | 53 | var sut = new ExpressionBasedGrainReferenceProvider(grainFactory.Object); 54 | 55 | var result = sut.GetGrainReference(typeof(ITestGrain4), "100,key"); 56 | 57 | grainFactory.Verify(x => x.GetGrain(100, "key",null), Times.Once); 58 | 59 | } 60 | 61 | [Fact] 62 | public void Should_invoke_correct_method_for_IGrainWithGuidCompoundKey() 63 | { 64 | var grainFactory = new Mock(); 65 | 66 | var sut = new ExpressionBasedGrainReferenceProvider(grainFactory.Object); 67 | 68 | var result = sut.GetGrainReference(typeof(ITestGrain5), "3FB93850-8806-4278-A476-CBE57A83FB50,key"); 69 | 70 | grainFactory.Verify(x => x.GetGrain(new Guid("3FB93850-8806-4278-A476-CBE57A83FB50"), "key", null), Times.Once); 71 | 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /test/Orleans.HttpGateway.AspNetCore.Tests/Internal/ReflectionUtilTests.cs: -------------------------------------------------------------------------------- 1 | using Orleans.Concurrency; 2 | using Orleans.HttpGateway.AspNetCore.Internal; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace Orleans.HttpGateway.AspNetCore.Tests.Internal 7 | { 8 | public class ReflectionUtilTests 9 | { 10 | [Fact] 11 | public void Should_return_interface_method() 12 | { 13 | ReflectionUtil.GetMethodsIncludingBaseInterfaces(typeof(ITestGrain3)) 14 | .ShouldContain(x => x.Name == "ExplicitTestMethod", 1); 15 | } 16 | 17 | [Fact] 18 | public void Should_return_inherited_interface_method() 19 | { 20 | ReflectionUtil.GetMethodsIncludingBaseInterfaces(typeof(ITestGrain3)) 21 | .ShouldContain(x => x.Name == "IntNoParameters", 1); 22 | } 23 | 24 | [Fact] 25 | public void Can_create_immutableT() 26 | { 27 | var creator = ReflectionUtil.GetObjectActivator(typeof(Immutable<>).MakeGenericType(typeof(int)), 28 | typeof(int)); 29 | 30 | var instance = creator(5); 31 | 32 | ((Immutable)instance).Value.ShouldBe(5); 33 | 34 | } 35 | 36 | [Fact] 37 | public void Can_get_value_dynamic() 38 | { 39 | var getter = ReflectionUtil.GetValueGetter(typeof(Immutable<>).MakeGenericType(typeof(int)), "Value"); 40 | 41 | var value = getter(new Immutable(5)); 42 | 43 | value.ShouldBe(5); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /test/Orleans.HttpGateway.AspNetCore.Tests/Orleans.HttpGateway.AspNetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | full 9 | True 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/Orleans.HttpGateway.AspNetCore.Tests/OrleansHttpGatewayMiddlewareTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.TestHost; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Moq; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Linq; 11 | using Orleans.Concurrency; 12 | using Orleans.HttpGateway.AspNetCore.ParameterBinding; 13 | using Shouldly; 14 | using Xunit; 15 | 16 | namespace Orleans.HttpGateway.AspNetCore.Tests 17 | { 18 | 19 | 20 | 21 | public class OrleansHttpGatewayMiddlewareTests : IDisposable 22 | { 23 | private readonly TestServer _server; 24 | private readonly HttpClient _client; 25 | private readonly Mock _factoryMock; 26 | 27 | public OrleansHttpGatewayMiddlewareTests() 28 | { 29 | _factoryMock = new Mock(); 30 | _server = new TestServer(new WebHostBuilder() 31 | .ConfigureServices(services => 32 | { 33 | services.AddOrleansHttpGateway(c => c.AddAssemblies(typeof(ITestGrain1).Assembly)); 34 | services.AddSingleton(_factoryMock.Object); 35 | }) 36 | .Configure(app => app.UseOrleansHttpGateway()) 37 | ); 38 | _client = _server.CreateClient(); 39 | } 40 | 41 | [Fact] 42 | public async Task Invoke_IntNoParameters_Success() 43 | { 44 | var testGrain = new Mock(); 45 | testGrain.Setup(x => x.IntNoParameters()).Returns(() => Task.FromResult(5)).Verifiable(); 46 | 47 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 48 | 49 | var response = await _client.GetAsync( 50 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/IntNoParameters"); 51 | 52 | dynamic token = JToken.Parse(await response.Content.ReadAsStringAsync()); 53 | 54 | int result = (int)token; 55 | result.ShouldBe(5); 56 | testGrain.Verify(); 57 | } 58 | 59 | [Fact] 60 | public async Task Invoke_GetObjectWith2Parameters_Success() 61 | { 62 | var testGrain = new Mock(); 63 | testGrain.Setup(x => x.GetObjectWith2Parameters(1, "okay")).Returns(() => Task.FromResult(new 64 | { 65 | success = true 66 | })).Verifiable(); 67 | 68 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 69 | 70 | var response = await _client.GetAsync( 71 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/GetObjectWith2Parameters?one=1&two=okay"); 72 | 73 | response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); 74 | dynamic json = JObject.Parse(await response.Content.ReadAsStringAsync()); 75 | 76 | ((bool)json.success).ShouldBeTrue(); 77 | 78 | testGrain.Verify(); 79 | } 80 | 81 | [Fact] 82 | public async Task Invoke_GetObjectWith2ArrayParameters_Success() 83 | { 84 | var testGrain = new Mock(); 85 | testGrain.Setup(x => x.GetObjectWith2ArrayParameters(new[] { 1, 2 }, new[] { "okay", "dan" })).Returns(() => Task.FromResult(new 86 | { 87 | success = true 88 | })).Verifiable(); 89 | 90 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 91 | 92 | var response = await _client.GetAsync( 93 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/GetObjectWith2ArrayParameters?one=1&one=2&two=okay&two=dan"); 94 | 95 | response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); 96 | dynamic json = JObject.Parse(await response.Content.ReadAsStringAsync()); 97 | 98 | ((bool)json.success).ShouldBeTrue(); 99 | 100 | testGrain.Verify(); 101 | } 102 | 103 | 104 | [Fact] 105 | public async Task Invoke_GetObjectWithEnumerableParameters_Success() 106 | { 107 | var testGrain = new Mock(); 108 | testGrain.Setup(x => x.GetObjectWithEnumerableParameters(new[] { 1, 2 })).Returns(() => Task.FromResult(new 109 | { 110 | success = true 111 | })).Verifiable(); 112 | 113 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 114 | 115 | var response = await _client.GetAsync( 116 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/GetObjectWithEnumerableParameters?one=[1,2]"); 117 | 118 | response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); 119 | dynamic json = JObject.Parse(await response.Content.ReadAsStringAsync()); 120 | 121 | ((bool)json.success).ShouldBeTrue(); 122 | 123 | testGrain.Verify(); 124 | } 125 | 126 | [Fact] 127 | public async Task Invoke_PostObjectWithComplexParameters_Success() 128 | { 129 | var testGrain = new Mock(); 130 | testGrain.Setup(x => x.PostObjectWithComplexParameters( 131 | It.Is(p => p.Key == "sleutel" && p.Value == "waarde"), 132 | "somestring")) 133 | .Returns(() => Task.FromResult(new 134 | { 135 | success = true 136 | })).Verifiable(); 137 | 138 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 139 | 140 | string jsonRequest = @"{ 141 | ""p1"":{ 142 | ""key"":""sleutel"", 143 | ""value"":""waarde"" 144 | }, 145 | ""p2"":""somestring"" 146 | }"; 147 | var response = await _client.PostAsync( 148 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/PostObjectWithComplexParameters", 149 | new StringContent(jsonRequest, Encoding.UTF8, "application/json") 150 | ); 151 | 152 | 153 | response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); 154 | testGrain.Verify(); 155 | 156 | var responseJson = await response.Content.ReadAsStringAsync(); 157 | dynamic json = JObject.Parse(responseJson); 158 | 159 | ((bool)json.success).ShouldBeTrue(); 160 | 161 | } 162 | 163 | [Fact] 164 | public async Task Invoke_PostObjectWithComplexImmutableParameters_Success() 165 | { 166 | var testGrain = new Mock(); 167 | testGrain.Setup(x => x.PostObjectWithComplexImmutableParameters( 168 | It.Is>(p => p.Value.Key == "sleutel" && p.Value.Value == "waarde"))) 169 | .Returns(() => Task.FromResult(new 170 | { 171 | success = new Immutable(5) 172 | })).Verifiable(); 173 | 174 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 175 | 176 | string jsonRequest = @"{ 177 | ""p1"":{ 178 | ""key"":""sleutel"", 179 | ""value"":""waarde"" 180 | }}"; 181 | var response = await _client.PostAsync( 182 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/PostObjectWithComplexImmutableParameters", 183 | new StringContent(jsonRequest, Encoding.UTF8, "application/json") 184 | ); 185 | 186 | response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); 187 | 188 | testGrain.Verify(); 189 | 190 | var responseJson = await response.Content.ReadAsStringAsync(); 191 | var json = JToken.Parse(responseJson); 192 | 193 | var settings = new JsonSerializerSettings(); 194 | settings.Converters.Add(new ImmutableConverter()); 195 | var serializer = JsonSerializer.Create(settings); 196 | 197 | 198 | var result = json["success"].ToObject>(serializer); 199 | result.Value.ShouldBe(5); 200 | } 201 | 202 | [Fact] 203 | public async Task Invoke_PostNoParametersNoResponse_GET_Success() 204 | { 205 | var testGrain = new Mock(); 206 | testGrain.Setup(x => x.PostNoParametersNoResponse()).Returns(() => Task.CompletedTask).Verifiable(); 207 | 208 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 209 | 210 | var response = await _client.GetAsync( 211 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/PostNoParametersNoResponse"); 212 | 213 | response.EnsureSuccessStatusCode(); 214 | 215 | 216 | 217 | testGrain.Verify(); 218 | } 219 | 220 | [Fact] 221 | public async Task Invoke_PostNoParametersNoResponse_POST_Success() 222 | { 223 | var testGrain = new Mock(); 224 | testGrain.Setup(x => x.PostNoParametersNoResponse()).Returns(() => Task.CompletedTask).Verifiable(); 225 | 226 | _factoryMock.Setup(x => x.GetGrain(6, null)).Returns(testGrain.Object); 227 | 228 | var response = await _client.PostAsync( 229 | "Orleans.HttpGateway.AspNetCore.Tests.ITestGrain3/6/PostNoParametersNoResponse", null); 230 | 231 | response.EnsureSuccessStatusCode(); 232 | 233 | 234 | 235 | testGrain.Verify(); 236 | } 237 | 238 | public void Dispose() 239 | { 240 | _server.Dispose(); 241 | _client.Dispose(); 242 | } 243 | } 244 | 245 | } -------------------------------------------------------------------------------- /test/Orleans.HttpGateway.AspNetCore.Tests/TestGrainInterfaces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Orleans.Concurrency; 5 | 6 | namespace Orleans.HttpGateway.AspNetCore.Tests 7 | { 8 | 9 | public interface ITestGrainMethods 10 | { 11 | Task IntNoParameters(); 12 | 13 | Task GetObjectWith2Parameters(int one, string two); 14 | 15 | Task GetObjectWith2ArrayParameters(int[] one, string[] two); 16 | 17 | Task GetObjectWithEnumerableParameters(IEnumerable one); 18 | 19 | Task PostObjectWithComplexImmutableParameters(Immutable p1); 20 | 21 | Task PostObjectWithComplexParameters(ComplexParameter1 p1, string p2); 22 | 23 | Task PostNoParametersNoResponse(); 24 | } 25 | 26 | 27 | public class ComplexParameter1 28 | { 29 | public string Key { get; set; } 30 | public string Value { get; set; } 31 | } 32 | 33 | public interface ITestGrain1 : IGrainWithStringKey, ITestGrainMethods 34 | { 35 | } 36 | 37 | public interface ITestGrain2 : IGrainWithGuidKey, ITestGrainMethods { } 38 | 39 | public interface ITestGrain3 : IGrainWithIntegerKey, ITestGrainMethods 40 | { 41 | Task ExplicitTestMethod(); 42 | } 43 | 44 | public interface ITestGrain4 : IGrainWithIntegerCompoundKey, ITestGrainMethods { } 45 | public interface ITestGrain5 : IGrainWithGuidCompoundKey, ITestGrainMethods { } 46 | } --------------------------------------------------------------------------------