├── .gitattributes ├── .github └── workflows │ └── codesee-arch-diagram.yml ├── .gitignore ├── NetCoreStack.Proxy.sln ├── NuGet.config ├── README.md ├── src └── NetCoreStack.Proxy │ ├── Binders │ ├── BodyContentBinder.cs │ ├── ContentBinderFactory.cs │ ├── ContentModelBinder.cs │ ├── HttpDeleteContentBinder.cs │ ├── HttpGetContentBinder.cs │ ├── HttpPostContentBinder.cs │ └── HttpPutContentBinder.cs │ ├── DefaultHeaderProvider.cs │ ├── DefaultHeaderValues.cs │ ├── DefaultHttpClientAccessor.cs │ ├── DefaultModelContentResolver.cs │ ├── DefaultProxyContentStreamProvider.cs │ ├── DefaultProxyContextFilter.cs │ ├── DefaultProxyEndpointManager.cs │ ├── Extensions │ ├── CollectionExtensions.cs │ ├── ContentModelBindingContextExtensions.cs │ ├── ContextBaseExtensions.cs │ ├── DictionaryExtensions.cs │ ├── ObjectExtensions.cs │ ├── ServiceCollectionExtensions.cs │ ├── StringExtensions.cs │ └── TypeCoreExtensions.cs │ ├── HttpDispatchProxy.cs │ ├── Interfaces │ ├── IContentModelBinder.cs │ ├── ICultureFactory.cs │ ├── IDefaultHeaderProvider.cs │ ├── IHeaderProvider.cs │ ├── IHttpClientAccessor.cs │ ├── IModelContentResolver.cs │ ├── IModelJsonSerializer.cs │ ├── IModelResolver.cs │ ├── IModelSerializer.cs │ ├── IModelXmlSerializer.cs │ ├── IProxyContentStreamProvider.cs │ ├── IProxyContextFilter.cs │ ├── IProxyEndpointManager.cs │ ├── IProxyManager.cs │ ├── IProxyRequestFilter.cs │ └── IProxyTypeManager.cs │ ├── Internal │ ├── ClosedGenericMatcher.cs │ ├── ConfigurationHelper.cs │ ├── Constants.cs │ ├── DefaultCultureFactory.cs │ ├── DefaultModelJsonSerializer.cs │ ├── DefaultModelXmlSerializer.cs │ ├── DefaultProxyTypeManager.cs │ ├── HashCodeCombiner.cs │ ├── ProxyContext.cs │ ├── ProxyException.cs │ ├── ProxyHelper.cs │ ├── ProxyManager.cs │ └── ProxyResultExecutor.cs │ ├── NetCoreStack.Proxy.csproj │ ├── Options │ ├── ProxyBuilderOptions.cs │ └── ProxyOptions.cs │ ├── ProxyMetadataProvider.cs │ ├── Resolvers │ ├── ComplexModelResolver.cs │ ├── EnumerableModelResolver.cs │ ├── FormFileModelResolver.cs │ ├── ModelResolverBase.cs │ ├── ResolverFactory.cs │ ├── SimpleModelResolver.cs │ └── SystemObjectModelResolver.cs │ ├── RoundRobinManager.cs │ └── Types │ ├── ContentModelBindingContext.cs │ ├── ContentModelBindingResult.cs │ ├── EnsureTemplateResult.cs │ ├── ModelDictionaryContext.cs │ ├── ModelDictionaryResult.cs │ ├── ModelResolverResult.cs │ ├── ProxyDescriptor.cs │ ├── ProxyMethodDescriptor.cs │ ├── ProxyModelMetadata.cs │ ├── ProxyModelMetadataIdentity.cs │ ├── ProxyModelMetadataKind.cs │ ├── RequestContext.cs │ ├── RequestDescriptor.cs │ └── ResponseContext.cs └── test ├── NetCoreStack.Proxy.Mvc.Hosting ├── Controllers │ ├── GuidelineController.cs │ └── SelfController.cs ├── NetCoreStack.Proxy.Mvc.Hosting.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── SampleĞüişçWord.docx ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── NetCoreStack.Proxy.Test.Contracts ├── Agent.cs ├── Attachment.cs ├── ContextBaseExtensions.cs ├── EntityAuditInsert.cs ├── FileProxyUploadContext.cs ├── IAttachmentApi.cs ├── IConsulApi.cs ├── IFileProxyApi.cs ├── IGuidelineApi.cs ├── ISelfApi.cs ├── ISmsApi.cs ├── NetCoreStack.Proxy.Test.Contracts.csproj ├── SampleModel.cs ├── SolutionPathUtility.cs ├── SomeFormComplexViewModel.cs ├── SomeFormFileCollectionViewModel.cs ├── SomeFormFileViewModel.cs ├── TypesModel.cs └── TypesModelHelper.cs └── NetCoreStack.Proxy.Tests ├── CustomProxyContextFilter.cs ├── ModelTypeTests.cs ├── NetCoreStack.Proxy.Tests.csproj ├── ObjFile.txt ├── ProxyCreationTests.cs ├── SampleĞüişçWord.docx ├── TestHelper.cs ├── appsettings.json └── httplive.db /.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 | -------------------------------------------------------------------------------- /.github/workflows/codesee-arch-diagram.yml: -------------------------------------------------------------------------------- 1 | # This workflow was added by CodeSee. Learn more at https://codesee.io/ 2 | # This is v2.0 of this workflow file 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request_target: 8 | types: [opened, synchronize, reopened] 9 | 10 | name: CodeSee 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | codesee: 16 | runs-on: ubuntu-latest 17 | continue-on-error: true 18 | name: Analyze the repo with CodeSee 19 | steps: 20 | - uses: Codesee-io/codesee-action@v2 21 | with: 22 | codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 23 | codesee-url: https://app.codesee.io 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | 244 | # DB 245 | musicdb.sqlite 246 | -------------------------------------------------------------------------------- /NetCoreStack.Proxy.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2020 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9754E8AD-F544-4C44-939D-95164DC29671}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E65756E9-13C3-4F24-8818-A6CD2E5E99D8}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C1562457-1BAD-4E9E-BFED-499501BBD717}" 11 | ProjectSection(SolutionItems) = preProject 12 | NuGet.config = NuGet.config 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreStack.Proxy", "src\NetCoreStack.Proxy\NetCoreStack.Proxy.csproj", "{80583F01-4793-4A33-9945-09397C1FCBA8}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreStack.Proxy.Tests", "test\NetCoreStack.Proxy.Tests\NetCoreStack.Proxy.Tests.csproj", "{00CEA1AF-05CC-4D39-9118-267683DEDCD1}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreStack.Proxy.Test.Contracts", "test\NetCoreStack.Proxy.Test.Contracts\NetCoreStack.Proxy.Test.Contracts.csproj", "{65CF1A10-0E97-479E-9D8A-2C7741BF608C}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCoreStack.Proxy.Mvc.Hosting", "test\NetCoreStack.Proxy.Mvc.Hosting\NetCoreStack.Proxy.Mvc.Hosting.csproj", "{76748B9C-AC8D-49C2-BE89-F427EC4BD20A}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {80583F01-4793-4A33-9945-09397C1FCBA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {80583F01-4793-4A33-9945-09397C1FCBA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {80583F01-4793-4A33-9945-09397C1FCBA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {80583F01-4793-4A33-9945-09397C1FCBA8}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {00CEA1AF-05CC-4D39-9118-267683DEDCD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {00CEA1AF-05CC-4D39-9118-267683DEDCD1}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {00CEA1AF-05CC-4D39-9118-267683DEDCD1}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {00CEA1AF-05CC-4D39-9118-267683DEDCD1}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {65CF1A10-0E97-479E-9D8A-2C7741BF608C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {65CF1A10-0E97-479E-9D8A-2C7741BF608C}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {65CF1A10-0E97-479E-9D8A-2C7741BF608C}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {65CF1A10-0E97-479E-9D8A-2C7741BF608C}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {76748B9C-AC8D-49C2-BE89-F427EC4BD20A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {76748B9C-AC8D-49C2-BE89-F427EC4BD20A}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {76748B9C-AC8D-49C2-BE89-F427EC4BD20A}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {76748B9C-AC8D-49C2-BE89-F427EC4BD20A}.Release|Any CPU.Build.0 = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {80583F01-4793-4A33-9945-09397C1FCBA8} = {9754E8AD-F544-4C44-939D-95164DC29671} 52 | {00CEA1AF-05CC-4D39-9118-267683DEDCD1} = {E65756E9-13C3-4F24-8818-A6CD2E5E99D8} 53 | {65CF1A10-0E97-479E-9D8A-2C7741BF608C} = {E65756E9-13C3-4F24-8818-A6CD2E5E99D8} 54 | {76748B9C-AC8D-49C2-BE89-F427EC4BD20A} = {E65756E9-13C3-4F24-8818-A6CD2E5E99D8} 55 | EndGlobalSection 56 | GlobalSection(ExtensibilityGlobals) = postSolution 57 | SolutionGuid = {1E0B4A17-1DEE-4EDF-979F-4C1B12D95CC8} 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetCoreStack Proxy 2 | [![NuGet](https://img.shields.io/nuget/v/NetCoreStack.Proxy.svg?longCache=true&style=flat-square)](https://www.nuget.org/packages/NetCoreStack.Proxy) 3 | [![NuGet](https://img.shields.io/nuget/dt/NetCoreStack.Proxy.svg?longCache=true&style=flat-square)](https://www.nuget.org/packages/NetCoreStack.Proxy) 4 | 5 | 6 | ### Cross-Platform .NET Standard HTTP Base Flying Proxy 7 | 8 | This project is demonstrating manage and consume distributed HTTP APIs and Micro Services 9 | from different regions (hosts) with ease. 10 | 11 | Flying Proxy allows the management of scalable applications, trigger many operations at the same time from your clients (Desktop, Web or Mobile App) and start to consume your new resources that you can simply add. 12 | 13 | Flying Proxy aims to: 14 | - Simple scalability 15 | - Effective and type-safe management of distributed architecture 16 | - Better performance 17 | - Maintainability 18 | 19 | ## Sample Client (Web) 20 | 21 | ### Add ProxySettings section to the appsettings.json 22 | ```json 23 | "ProxySettings": { 24 | "RegionKeys": { 25 | "Main": "http://localhost:5000/,http://localhost:5001/", 26 | "Authorization": "http://localhost:5002/", 27 | "Integrations": "http://localhost:5003/" 28 | } 29 | } 30 | ``` 31 | 32 | ### APIs Definitions 33 | ```csharp 34 | // This API expose methods from localhost:5000 and localhost:5001 as configured on ProxySettings 35 | [ApiRoute("api/[controller]", regionKey: "Main")] 36 | public interface IGuidelineApi : IApiContract 37 | { 38 | [HttpHeaders("X-Method-Header: Some Value")] 39 | Task TaskOperation(); 40 | 41 | int PrimitiveReturn(int i, string s, long l, DateTime dt); 42 | 43 | Task> GetEnumerableModels(); 44 | 45 | Task GetWithReferenceType(SimpleModel model); 46 | 47 | /// 48 | /// Default Content-Type is ModelAware. 49 | /// If the any parameter(s) has FormFile type property that will be MultipartFormData 50 | /// if not will be JSON 51 | /// 52 | /// 53 | /// 54 | [HttpPostMarker] 55 | Task TaskActionPost(ComplexTypeModel model); 56 | 57 | [HttpPostMarker(ContentType = ContentType.MultipartFormData)] 58 | Task TaskActionBarMultipartFormData(Bar model); 59 | 60 | [HttpPostMarker(ContentType = ContentType.Xml)] 61 | Task TaskActionBarSimpleXml(BarSimple model); 62 | 63 | /// 64 | /// Template and parameter usage, key parameter will be part of the request Url 65 | /// and extracting it as api/guideline/kv/ 66 | /// 67 | /// 68 | /// 69 | /// 70 | [HttpPutMarker(Template = "kv/{key}")] 71 | Task CreateOrUpdateKey(string key, Bar body); 72 | } 73 | ``` 74 | 75 | ### Startup ConfigureServices 76 | ```csharp 77 | public void ConfigureServices(IServiceCollection services) 78 | { 79 | services.AddNetCoreProxy(Configuration, options => 80 | { 81 | // Register the API to use as a Proxy 82 | options.Register(); 83 | }); 84 | 85 | // Add framework services. 86 | services.AddMvc(); 87 | } 88 | ``` 89 | 90 | ### Proxy Usage (DI) 91 | ```csharp 92 | public class HomeController : Controller 93 | { 94 | private readonly IGuidelineApi _api; 95 | 96 | public TestController(IGuidelineApi api) 97 | { 98 | _api = api; 99 | } 100 | 101 | public async Task> GetModels() 102 | { 103 | var items = await _api.GetEnumerableModels(); 104 | return items; 105 | } 106 | } 107 | ``` 108 | 109 | ## Sample Server 110 | 111 | ### API Implementation 112 | ```csharp 113 | [Route("api/[controller]")] 114 | public class GuidelineController : Controller, IGuidelineApi 115 | { 116 | [HttpGet(nameof(GetEnumerableModels))] 117 | public Task> GetEnumerableModels() 118 | { 119 | ... 120 | return items; 121 | } 122 | 123 | [HttpPost(nameof(TaskComplexTypeModel))] 124 | public async Task TaskComplexTypeModel([FromBody]ComplexTypeModel model) 125 | { 126 | ... 127 | } 128 | 129 | [HttpPost(nameof(TaskActionBarMultipartFormData))] 130 | public Task TaskActionBarMultipartFormData(Bar model) 131 | { 132 | ... 133 | } 134 | 135 | [HttpPut("kv")] 136 | public async Task CreateOrUpdateKey(string key, Bar body) 137 | { 138 | ... 139 | } 140 | } 141 | ``` 142 | 143 | ### Unit Testing 144 | Use [HttpLive](https://github.com/gencebay/httplive) 145 | 146 | httplive -p 5003,5004 -d test/NetCoreStack.Proxy.Tests/httplive.db 147 | 148 | [Latest release on Nuget](https://www.nuget.org/packages/NetCoreStack.Proxy/) 149 | 150 | ### Prerequisites 151 | > [ASP.NET Core](https://github.com/aspnet/Home) 152 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Binders/BodyContentBinder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using NetCoreStack.Contracts; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Net.Http.Headers; 10 | using System.Text; 11 | 12 | namespace NetCoreStack.Proxy 13 | { 14 | public abstract class BodyContentBinder : ContentModelBinder 15 | { 16 | private const string formData = "form-data"; 17 | protected IModelSerializer ModelSerializer { get; } 18 | 19 | public BodyContentBinder(HttpMethod httpMethod, IModelSerializer modelSerializer) 20 | :base(httpMethod) 21 | { 22 | ModelSerializer = modelSerializer; 23 | } 24 | 25 | protected virtual void AddFile(string key, MultipartFormDataContent multipartFormDataContent, IFormFile formFile) 26 | { 27 | using (var ms = new MemoryStream()) 28 | { 29 | formFile.CopyTo(ms); 30 | var fileContent = new ByteArrayContent(ms.ToArray()); 31 | 32 | if (MediaTypeHeaderValue.TryParse(formFile.ContentType, out MediaTypeHeaderValue media)) 33 | { 34 | if (media.CharSet == null) 35 | { 36 | media.CharSet = Encoding.UTF8.WebName; 37 | } 38 | 39 | fileContent.Headers.ContentType = media; 40 | } 41 | else 42 | { 43 | // TODO Log 44 | } 45 | 46 | fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue(formData) 47 | { 48 | Name = formFile.Name, 49 | FileName = formFile.FileName, 50 | Size = formFile.Length 51 | }; 52 | 53 | multipartFormDataContent.Add(fileContent, key, formFile.FileName); 54 | } 55 | } 56 | 57 | protected virtual byte[] Serialize(object value) 58 | { 59 | return Encoding.UTF8.GetBytes(ModelSerializer.Serialize(value)); 60 | } 61 | 62 | protected virtual StringContent SerializeToString(ContentType contentType, ArraySegment args) 63 | { 64 | var mediaType = "application/json"; 65 | if (contentType == ContentType.Xml) 66 | mediaType = "application/xml"; 67 | 68 | if (args.Count == 1) 69 | { 70 | return new StringContent(ModelSerializer.Serialize(args.First()), Encoding.UTF8, mediaType); 71 | } 72 | 73 | return new StringContent(ModelSerializer.Serialize(args), Encoding.UTF8, mediaType); 74 | } 75 | 76 | protected virtual MultipartFormDataContent GetMultipartFormDataContent(ModelDictionaryResult contentResult) 77 | { 78 | MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent(); 79 | foreach (KeyValuePair entry in contentResult.Dictionary) 80 | { 81 | multipartFormDataContent.Add(new StringContent(entry.Value), entry.Key); 82 | } 83 | 84 | if (contentResult.Files != null) 85 | { 86 | foreach (KeyValuePair entry in contentResult.Files) 87 | { 88 | AddFile(entry.Key, multipartFormDataContent, entry.Value); 89 | } 90 | } 91 | 92 | return multipartFormDataContent; 93 | } 94 | 95 | protected virtual FormUrlEncodedContent GetUrlEncodedContent(ModelDictionaryResult contentResult) 96 | { 97 | FormUrlEncodedContent formUrlEncodedContent = new FormUrlEncodedContent(contentResult.Dictionary); 98 | return formUrlEncodedContent; 99 | } 100 | 101 | public override void BindContent(ContentModelBindingContext bindingContext) 102 | { 103 | EnsureTemplateResult ensureTemplateResult = EnsureTemplate(bindingContext); 104 | if (ensureTemplateResult.BindingCompleted) 105 | return; 106 | 107 | var parameterOffset = ensureTemplateResult.ParameterOffset; 108 | ModelDictionaryResult result = bindingContext.ModelContentResolver.Resolve(bindingContext.Parameters, 109 | bindingContext.Args, 110 | parameterOffset, 111 | ensureTemplateResult.IgnoreModelPrefix); 112 | 113 | var contentType = bindingContext.ContentType; 114 | if (contentType == ContentType.MultipartFormData) 115 | { 116 | var content = GetMultipartFormDataContent(result); 117 | bindingContext.ContentResult = ContentModelBindingResult.Success(content); 118 | return; 119 | } 120 | 121 | if (contentType == ContentType.FormUrlEncoded) 122 | { 123 | var content = GetUrlEncodedContent(result); 124 | bindingContext.ContentResult = ContentModelBindingResult.Success(content); 125 | return; 126 | } 127 | 128 | var remainingParameterCount = bindingContext.ArgsLength - parameterOffset; 129 | if (remainingParameterCount <= 0) 130 | { 131 | // all parameters are resolved as Key - Url 132 | return; 133 | } 134 | 135 | var segments = new ArraySegment(bindingContext.Args, parameterOffset, remainingParameterCount); 136 | bindingContext.ContentResult = ContentModelBindingResult.Success(SerializeToString(contentType, segments)); 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Binders/ContentBinderFactory.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using System; 3 | using System.Net.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace NetCoreStack.Proxy 7 | { 8 | public static class ContentBinderFactory 9 | { 10 | public static IContentModelBinder GetContentModelBinder(IServiceProvider serviceProvider, 11 | HttpMethod httpMethod, 12 | ContentType contentType) 13 | { 14 | IModelSerializer modelSerializer = null; 15 | if (contentType == ContentType.Xml) 16 | { 17 | modelSerializer = serviceProvider.GetService(); 18 | } 19 | else 20 | { 21 | modelSerializer = serviceProvider.GetService(); 22 | } 23 | 24 | if (httpMethod == HttpMethod.Get) 25 | { 26 | return new HttpGetContentBinder(httpMethod); 27 | } 28 | 29 | if (httpMethod == HttpMethod.Post) 30 | { 31 | return new HttpPostContentBinder(httpMethod, modelSerializer); 32 | } 33 | 34 | if (httpMethod == HttpMethod.Put) 35 | { 36 | return new HttpPutContentBinder(httpMethod, modelSerializer); 37 | } 38 | 39 | if (httpMethod == HttpMethod.Delete) 40 | { 41 | return new HttpDeleteContentBinder(httpMethod); 42 | } 43 | 44 | throw new NotImplementedException($"{httpMethod.Method} is not supported or not implemented yet."); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Binders/ContentModelBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Net.Http; 5 | 6 | namespace NetCoreStack.Proxy 7 | { 8 | public abstract class ContentModelBinder : IContentModelBinder 9 | { 10 | protected HttpMethod HttpMethod { get; } 11 | 12 | public ContentModelBinder(HttpMethod httpMethod) 13 | { 14 | HttpMethod = httpMethod; 15 | } 16 | 17 | protected virtual EnsureTemplateResult EnsureTemplate(ContentModelBindingContext bindingContext) 18 | { 19 | int parameterOffset = 0; 20 | if (bindingContext.HasAnyTemplateParameterKey) 21 | { 22 | for (int i = 0; i < bindingContext.TemplateParameterKeys.Count; i++) 23 | { 24 | var keyParameter = bindingContext.TemplateParameterKeys[i]; 25 | var keyModelMetadata = bindingContext.Parameters.FirstOrDefault(x => x.PropertyName == keyParameter); 26 | if (keyModelMetadata == null) 27 | { 28 | throw new ArgumentOutOfRangeException("Key parameter name does not match the template key. Please check template key(s) of the method."); 29 | } 30 | 31 | var value = bindingContext.ModelContentResolver.ResolveParameter(keyModelMetadata, bindingContext.Args[i], false); 32 | bindingContext.UriBuilder.Path += ($"/{Uri.EscapeUriString(value)}"); 33 | parameterOffset++; 34 | } 35 | } 36 | 37 | bool ignorePrefix = false; 38 | if (parameterOffset == bindingContext.ArgsLength - 1) 39 | { 40 | ignorePrefix = true; 41 | } 42 | 43 | if (parameterOffset == bindingContext.ArgsLength) 44 | { 45 | // all parameters are resolved 46 | return EnsureTemplateResult.Completed(parameterOffset, ignorePrefix); 47 | } 48 | 49 | return EnsureTemplateResult.Continue(parameterOffset, ignorePrefix); 50 | } 51 | 52 | public abstract void BindContent(ContentModelBindingContext bindingContext); 53 | } 54 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Binders/HttpDeleteContentBinder.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Extensions; 2 | using System.Net.Http; 3 | 4 | namespace NetCoreStack.Proxy 5 | { 6 | public class HttpDeleteContentBinder : ContentModelBinder 7 | { 8 | public HttpDeleteContentBinder(HttpMethod httpMethod) 9 | :base(httpMethod) 10 | { 11 | 12 | } 13 | 14 | public override void BindContent(ContentModelBindingContext bindingContext) 15 | { 16 | EnsureTemplateResult ensureTemplateResult = EnsureTemplate(bindingContext); 17 | if (ensureTemplateResult.BindingCompleted) 18 | return; 19 | 20 | ModelDictionaryResult result = bindingContext.ModelContentResolver.Resolve(bindingContext.Parameters, 21 | bindingContext.Args, 22 | ensureTemplateResult.ParameterOffset); 23 | 24 | bindingContext.TryUpdateUri(result.Dictionary); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Binders/HttpGetContentBinder.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Extensions; 2 | using System.Net.Http; 3 | 4 | namespace NetCoreStack.Proxy 5 | { 6 | public class HttpGetContentBinder : ContentModelBinder 7 | { 8 | public HttpGetContentBinder(HttpMethod httpMethod) 9 | : base(httpMethod) 10 | { 11 | 12 | } 13 | 14 | public override void BindContent(ContentModelBindingContext bindingContext) 15 | { 16 | EnsureTemplateResult ensureTemplateResult = EnsureTemplate(bindingContext); 17 | if (ensureTemplateResult.BindingCompleted) 18 | return; 19 | 20 | ModelDictionaryResult result = bindingContext.ModelContentResolver.Resolve(bindingContext.Parameters, 21 | bindingContext.Args, 22 | ensureTemplateResult.ParameterOffset, 23 | ensureTemplateResult.IgnoreModelPrefix); 24 | 25 | bindingContext.TryUpdateUri(result.Dictionary); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Binders/HttpPostContentBinder.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public class HttpPostContentBinder : BodyContentBinder 6 | { 7 | public HttpPostContentBinder(HttpMethod httpMethod, IModelSerializer modelSerializer) 8 | :base(httpMethod, modelSerializer) 9 | { 10 | 11 | } 12 | 13 | public override void BindContent(ContentModelBindingContext bindingContext) 14 | { 15 | base.BindContent(bindingContext); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Binders/HttpPutContentBinder.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public class HttpPutContentBinder : BodyContentBinder 6 | { 7 | public HttpPutContentBinder(HttpMethod httpMethod, IModelSerializer modelSerializer) 8 | : base(httpMethod, modelSerializer) 9 | { 10 | 11 | } 12 | 13 | public override void BindContent(ContentModelBindingContext bindingContext) 14 | { 15 | base.BindContent(bindingContext); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/DefaultHeaderProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System.Collections.Generic; 3 | 4 | namespace NetCoreStack.Proxy 5 | { 6 | public class DefaultHeaderProvider : IDefaultHeaderProvider 7 | { 8 | public IDictionary Headers { get; set; } 9 | 10 | public DefaultHeaderProvider(IOptions options) 11 | { 12 | Headers = options.Value.Headers; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/DefaultHeaderValues.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public class DefaultHeaderValues 6 | { 7 | public IDictionary Headers { get; set; } 8 | 9 | public DefaultHeaderValues() 10 | { 11 | Headers = new Dictionary(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/DefaultHttpClientAccessor.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public class DefaultHttpClientAccessor : IHttpClientAccessor 6 | { 7 | public HttpClient HttpClient { get; set; } 8 | 9 | public DefaultHttpClientAccessor() 10 | { 11 | HttpClient = new HttpClient(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/DefaultModelContentResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | 7 | namespace NetCoreStack.Proxy 8 | { 9 | public class DefaultModelContentResolver : IModelContentResolver 10 | { 11 | public ProxyMetadataProvider MetadataProvider { get; } 12 | 13 | public DefaultModelContentResolver(ProxyMetadataProvider metadataProvider) 14 | { 15 | MetadataProvider = metadataProvider; 16 | } 17 | 18 | private void TrySetFormFile(string prefix, ProxyModelMetadata modelMetadata, ModelDictionaryResult result, object value) 19 | { 20 | if (modelMetadata.IsNullableValueType) 21 | { 22 | // recall with prefix 23 | TrySetFormFile(prefix, modelMetadata.ElementType, result, value); 24 | return; 25 | } 26 | 27 | if (modelMetadata.IsEnumerableType) 28 | { 29 | var enumerable = value as IEnumerable; 30 | int index = 0; 31 | foreach (var item in enumerable) 32 | { 33 | if (item != null) 34 | { 35 | result.Files.Add($"{prefix}[{index}]", item); 36 | } 37 | index++; 38 | } 39 | 40 | return; 41 | } 42 | 43 | if (modelMetadata.IsFormFile) 44 | { 45 | result.Files.Add(prefix, (IFormFile)value); 46 | } 47 | } 48 | 49 | private void SetSimpleEnumerable(string key, Dictionary dictionary, object value) 50 | { 51 | var enumerable = value as IEnumerable; 52 | int index = 0; 53 | foreach (var item in enumerable) 54 | { 55 | if (item != null) 56 | { 57 | dictionary.Add($"{key}[{index}]", Convert.ToString(item)); 58 | } 59 | index++; 60 | } 61 | } 62 | 63 | private void TrySetSystemObjectValue(string key, 64 | PropertyInfo propertyInfo, 65 | string propertyName, 66 | Type containerType, 67 | ModelDictionaryResult result, 68 | object value) 69 | { 70 | var objModelMetadata = new ProxyModelMetadata(propertyInfo, 71 | ProxyModelMetadataIdentity.ForProperty(value.GetType(), 72 | propertyName, 73 | containerType)); 74 | 75 | 76 | if (objModelMetadata.IsSimpleType) 77 | { 78 | result.Dictionary.Add(key, Convert.ToString(value)); 79 | return; 80 | } 81 | 82 | if (objModelMetadata.IsEnumerableType) 83 | { 84 | if (objModelMetadata.ElementType.IsSimpleType) 85 | { 86 | SetSimpleEnumerable(key, result.Dictionary, value); 87 | } 88 | else 89 | { 90 | TrySetValueInner(key, objModelMetadata, result, value); 91 | } 92 | 93 | return; 94 | } 95 | 96 | // Anonymous object resolver 97 | for (int i = 0; i < objModelMetadata.Properties.Count; i++) 98 | { 99 | var modelMetadata = objModelMetadata.Properties[i]; 100 | var v = modelMetadata.PropertyInfo.GetValue(value); 101 | if (v != null) 102 | { 103 | ResolveInternal(modelMetadata, result, v, isTopLevelObject: false, prefix: key); 104 | } 105 | } 106 | } 107 | 108 | private void TrySetValueInner(string key, ProxyModelMetadata modelMetadata, ModelDictionaryResult result, object value) 109 | { 110 | var count = modelMetadata.ElementType.PropertiesCount; 111 | var elementProperties = modelMetadata.ElementType.Properties; 112 | for (int i = 0; i < count; i++) 113 | { 114 | var elementModelMetadata = elementProperties[i]; 115 | var propertyInfo = elementModelMetadata.PropertyInfo; 116 | if (value is IEnumerable values) 117 | { 118 | var index = 0; 119 | foreach (var v in values) 120 | { 121 | if (v == null) 122 | { 123 | continue; 124 | } 125 | 126 | var propKey = $"{key}[{index}]"; 127 | var propValue = propertyInfo?.GetValue(v); 128 | ResolveInternal(elementModelMetadata, result, propValue, isTopLevelObject:false, prefix: propKey); 129 | index++; 130 | } 131 | } 132 | } 133 | } 134 | 135 | private void TrySetValue(string prefix, ProxyModelMetadata modelMetadata, ModelDictionaryResult result, object value) 136 | { 137 | var key = prefix; 138 | 139 | if (modelMetadata.IsNullableValueType) 140 | { 141 | // recall with prefix 142 | TrySetValue(prefix, modelMetadata.ElementType, result, value); 143 | return; 144 | } 145 | 146 | if (modelMetadata.IsSimpleType) 147 | { 148 | result.Dictionary.Add(key, Convert.ToString(value)); 149 | return; 150 | } 151 | 152 | if (modelMetadata.IsEnumerableType) 153 | { 154 | if (modelMetadata.ElementType.IsSimpleType) 155 | { 156 | SetSimpleEnumerable(key, result.Dictionary, value); 157 | } 158 | else if(modelMetadata.ElementType.IsSystemObject) 159 | { 160 | TrySetSystemObjectValue(key, 161 | modelMetadata.ElementType.PropertyInfo, 162 | modelMetadata.ElementType.PropertyName, 163 | modelMetadata.ContainerType, 164 | result, value); 165 | } 166 | else 167 | { 168 | TrySetValueInner(key, modelMetadata, result, value); 169 | } 170 | 171 | return; 172 | } 173 | 174 | if (modelMetadata.IsSystemObject) 175 | { 176 | TrySetSystemObjectValue(key, modelMetadata.PropertyInfo, modelMetadata.PropertyName, modelMetadata.ContainerType, result, value); 177 | } 178 | } 179 | 180 | private void ResolveInternal(ProxyModelMetadata modelMetadata, ModelDictionaryResult result, object value, bool isTopLevelObject = false, string prefix = "") 181 | { 182 | var key = isTopLevelObject ? string.Empty : modelMetadata.PropertyName; 183 | if (!string.IsNullOrEmpty(prefix)) 184 | { 185 | if (string.IsNullOrEmpty(key)) 186 | { 187 | key = prefix; 188 | } 189 | else 190 | { 191 | key = $"{prefix}.{key}"; 192 | } 193 | } 194 | 195 | if (modelMetadata.IsFormFile || (modelMetadata.IsEnumerableType && modelMetadata.ElementType.IsFormFile)) 196 | { 197 | TrySetFormFile(key, modelMetadata, result, value); 198 | return; 199 | } 200 | 201 | var dictionary = result.Dictionary; 202 | var count = modelMetadata.PropertiesCount; 203 | if (count == 0) 204 | { 205 | TrySetValue(key, modelMetadata, result, value); 206 | return; 207 | } 208 | 209 | for (int i = 0; i < modelMetadata.PropertiesCount; i++) 210 | { 211 | var metadata = modelMetadata.Properties[i]; 212 | if (metadata.ContainerType != null) 213 | { 214 | if (value != null) 215 | { 216 | var v = metadata.PropertyInfo.GetValue(value); 217 | if (v != null) 218 | { 219 | ResolveInternal(metadata, result, v, isTopLevelObject: false, prefix: key); 220 | } 221 | } 222 | } 223 | else 224 | { 225 | ResolveInternal(metadata, result, value); 226 | } 227 | } 228 | } 229 | 230 | // Per request parameter context resolver 231 | public ModelDictionaryResult Resolve(List parameters, object[] args, int parameterOffset = 0, bool ignoreModelPrefix = false) 232 | { 233 | var result = new ModelDictionaryResult(new Dictionary(StringComparer.Ordinal), 234 | new Dictionary(StringComparer.Ordinal)); 235 | 236 | for (int i = parameterOffset; i < parameters.Count; i++) 237 | { 238 | var modelMetadata = parameters[i]; 239 | var modelPrefix = string.Empty; 240 | if (!ignoreModelPrefix) 241 | { 242 | if (modelMetadata.ContainerType == null && !string.IsNullOrEmpty(modelMetadata.PropertyName)) 243 | { 244 | modelPrefix = modelMetadata.PropertyName; 245 | } 246 | } 247 | ResolveInternal(modelMetadata, result, args[i], isTopLevelObject: true, prefix: modelPrefix); 248 | } 249 | 250 | return result; 251 | } 252 | 253 | public string ResolveParameter(ProxyModelMetadata modelMetadata, object value, bool isTopLevelObject, string prefix = "") 254 | { 255 | var result = new ModelDictionaryResult(new Dictionary(StringComparer.Ordinal), 256 | new Dictionary(StringComparer.Ordinal)); 257 | 258 | ResolveInternal(modelMetadata, result, value, isTopLevelObject: isTopLevelObject, prefix: prefix); 259 | 260 | if(result.Dictionary.TryGetValue(modelMetadata.PropertyName, out string propValue)) 261 | { 262 | return propValue; 263 | } 264 | else 265 | { 266 | throw new InvalidOperationException($"The parameter does not found in resolver dictionary! " + 267 | $"Name: \"{modelMetadata.PropertyName}\", Type: \"{modelMetadata.ModelType.Name}\""); 268 | } 269 | } 270 | } 271 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/DefaultProxyContentStreamProvider.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Extensions; 2 | using System; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace NetCoreStack.Proxy 7 | { 8 | public class DefaultProxyContentStreamProvider : IProxyContentStreamProvider 9 | { 10 | private readonly IServiceProvider _serviceProvider; 11 | public ProxyMetadataProvider MetadataProvider { get; } 12 | public IModelContentResolver ContentResolver { get; } 13 | 14 | public DefaultProxyContentStreamProvider(IServiceProvider serviceProvider, 15 | ProxyMetadataProvider metadataProvider, 16 | IModelContentResolver contentResolver) 17 | { 18 | _serviceProvider = serviceProvider; 19 | MetadataProvider = metadataProvider; 20 | ContentResolver = contentResolver; 21 | } 22 | 23 | public async Task CreateRequestContentAsync(RequestDescriptor requestContext, 24 | HttpRequestMessage request, 25 | ProxyMethodDescriptor descriptor, 26 | UriBuilder uriBuilder) 27 | { 28 | await Task.CompletedTask; 29 | 30 | var httpMethod = descriptor.HttpMethod; 31 | var contentModelBinder = ContentBinderFactory.GetContentModelBinder(_serviceProvider, httpMethod, descriptor.ContentType); 32 | 33 | var bindingContext = new ContentModelBindingContext(httpMethod, descriptor, uriBuilder) 34 | { 35 | ContentType = descriptor.ContentType, 36 | ModelContentResolver = ContentResolver, 37 | Args = requestContext.Args, 38 | }; 39 | 40 | contentModelBinder.BindContent(bindingContext); 41 | bindingContext.TrySetContent(request); 42 | request.RequestUri = bindingContext.Uri; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/DefaultProxyContextFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Sockets; 3 | 4 | namespace NetCoreStack.Proxy.Internal 5 | { 6 | public class DefaultProxyContextFilter : IProxyContextFilter 7 | { 8 | public const string NetCoreStackUserAgent = "NetCoreStack-Proxy-UserAgent"; 9 | public readonly static string LocalIpAddress = GetLocalIPAddress(); 10 | 11 | public static string GetLocalIPAddress() 12 | { 13 | var host = Dns.GetHostEntry(Dns.GetHostName()); 14 | foreach (var ip in host.AddressList) 15 | { 16 | if (ip.AddressFamily == AddressFamily.InterNetwork) 17 | { 18 | return ip.ToString(); 19 | } 20 | } 21 | 22 | return string.Empty; 23 | } 24 | 25 | public void Invoke(ProxyContext proxyContext) 26 | { 27 | proxyContext.ClientIp = LocalIpAddress; 28 | proxyContext.UserAgent = NetCoreStackUserAgent; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/DefaultProxyEndpointManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace NetCoreStack.Proxy 5 | { 6 | public class DefaultProxyEndpointManager : IProxyEndpointManager 7 | { 8 | protected RoundRobinManager RoundRobinManager { get; } 9 | 10 | public DefaultProxyEndpointManager(RoundRobinManager roundRobinManager) 11 | { 12 | RoundRobinManager = roundRobinManager; 13 | } 14 | 15 | private string ConcatRoute(string route, string methodName) 16 | { 17 | string path = string.Empty; 18 | if (string.IsNullOrEmpty(route)) 19 | { 20 | path = methodName; 21 | } 22 | else 23 | { 24 | path = $"{route}/{methodName}"; 25 | } 26 | 27 | return path; 28 | } 29 | 30 | private void ResolveTemplate(UriBuilder uriBuilder, ProxyMethodDescriptor methodDescriptor, string route, string targetMethodName) 31 | { 32 | var path = uriBuilder.Path ?? string.Empty; 33 | if (methodDescriptor.RouteTemplate != null) 34 | { 35 | if (methodDescriptor.RouteTemplate.Parameters.Count > 0) 36 | { 37 | var methodName = string.Join("/", methodDescriptor.TemplateKeys); 38 | path += ConcatRoute(route, methodName); 39 | uriBuilder.Path = path; 40 | return; 41 | } 42 | } 43 | 44 | path += ConcatRoute(route, targetMethodName); 45 | uriBuilder.Path = path; 46 | } 47 | 48 | public UriBuilder CreateUriBuilder(ProxyMethodDescriptor methodDescriptor, string route, string regionKey, string targetMethodName) 49 | { 50 | var uriBuilder = RoundRobinManager.RoundRobinUri(regionKey); 51 | if (uriBuilder == null) 52 | { 53 | throw new ArgumentNullException(nameof(uriBuilder)); 54 | } 55 | 56 | if (targetMethodName.ToLower() == HttpMethod.Get.Method.ToLower()) 57 | { 58 | var path = uriBuilder.Path ?? string.Empty; 59 | path += string.IsNullOrEmpty(route) ? "" : $"{route}/"; 60 | uriBuilder.Path = path; 61 | } 62 | else 63 | { 64 | if (targetMethodName.StartsWith("/")) 65 | targetMethodName = targetMethodName.Substring(1); 66 | 67 | ResolveTemplate(uriBuilder, methodDescriptor, route, targetMethodName); 68 | } 69 | 70 | return uriBuilder; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy.Extensions 4 | { 5 | internal static class CollectionExtensions 6 | { 7 | internal static IEnumerable ToFlat(this ProxyModelMetadata modelMetadata) 8 | { 9 | List list = new List(); 10 | var stack = new Stack>(); 11 | var enumerator = modelMetadata.Properties.GetEnumerator(); 12 | while (true) 13 | { 14 | if (enumerator.MoveNext()) 15 | { 16 | ProxyModelMetadata element = enumerator.Current; 17 | yield return element; 18 | 19 | stack.Push(enumerator); 20 | enumerator = element.Properties.GetEnumerator(); 21 | } 22 | else if (stack.Count > 0) 23 | { 24 | enumerator.Dispose(); 25 | enumerator = stack.Pop(); 26 | } 27 | else 28 | { 29 | yield break; 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/ContentModelBindingContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | 5 | namespace NetCoreStack.Proxy.Extensions 6 | { 7 | public static class ContentModelBindingContextExtensions 8 | { 9 | public static void TrySetContent(this ContentModelBindingContext bindingContext, HttpRequestMessage httpRequest) 10 | { 11 | if (bindingContext.ContentResult != null && bindingContext.ContentResult.IsContentSet) 12 | { 13 | httpRequest.Content = bindingContext.ContentResult.Content; 14 | } 15 | } 16 | 17 | public static void TryUpdateUri(this ContentModelBindingContext bindingContext, Dictionary dictionary) 18 | { 19 | if (bindingContext.UriBuilder != null) 20 | { 21 | var uriBuilder = new UriBuilder(bindingContext.Uri.ToQueryString(dictionary)); 22 | bindingContext.UriBuilder = uriBuilder; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/ContextBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Features; 3 | using NetCoreStack.Proxy.Internal; 4 | 5 | namespace NetCoreStack.Proxy.Extensions 6 | { 7 | internal static class ContextBaseExtensions 8 | { 9 | internal static string GetIp(this HttpContext context) 10 | { 11 | return context?.Features?.Get()?.RemoteIpAddress?.ToString(); 12 | } 13 | 14 | internal static string GetUserAgent(this HttpRequest request) 15 | { 16 | var userAgent = request.Headers[Constants.ClientUserAgentHeader].ToString(); 17 | if (userAgent.HasValue()) 18 | { 19 | return userAgent; 20 | } 21 | 22 | return string.Empty; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Encodings.Web; 5 | 6 | namespace NetCoreStack.Proxy.Extensions 7 | { 8 | internal static class DictionaryExtensions 9 | { 10 | // Copyright (c) .NET Foundation. All rights reserved. 11 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 12 | private static string AddQueryString(string uri, IEnumerable> queryString) 13 | { 14 | if (uri == null) 15 | { 16 | throw new ArgumentNullException(nameof(uri)); 17 | } 18 | 19 | if (queryString == null) 20 | { 21 | throw new ArgumentNullException(nameof(queryString)); 22 | } 23 | 24 | var anchorIndex = uri.IndexOf('#'); 25 | var uriToBeAppended = uri; 26 | var anchorText = ""; 27 | // If there is an anchor, then the query string must be inserted before its first occurance. 28 | if (anchorIndex != -1) 29 | { 30 | anchorText = uri.Substring(anchorIndex); 31 | uriToBeAppended = uri.Substring(0, anchorIndex); 32 | } 33 | 34 | var queryIndex = uriToBeAppended.IndexOf('?'); 35 | var hasQuery = queryIndex != -1; 36 | 37 | var sb = new StringBuilder(); 38 | sb.Append(uriToBeAppended); 39 | foreach (var parameter in queryString) 40 | { 41 | sb.Append(hasQuery ? '&' : '?'); 42 | sb.Append(UrlEncoder.Default.Encode(parameter.Key)); 43 | sb.Append('='); 44 | sb.Append(UrlEncoder.Default.Encode(parameter.Value)); 45 | hasQuery = true; 46 | } 47 | 48 | sb.Append(anchorText); 49 | return sb.ToString(); 50 | } 51 | 52 | // Copyright (c) .NET Foundation. All rights reserved. 53 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 54 | private static string AddQueryString(string uri, IDictionary queryString) 55 | { 56 | if (uri == null) 57 | { 58 | throw new ArgumentNullException(nameof(uri)); 59 | } 60 | 61 | if (queryString == null) 62 | { 63 | throw new ArgumentNullException(nameof(queryString)); 64 | } 65 | 66 | return AddQueryString(uri, (IEnumerable>)queryString); 67 | } 68 | 69 | internal static string ToQueryString(this Uri baseUrl, IDictionary dictionary) 70 | { 71 | if (dictionary == null || dictionary.Count == 0) 72 | return baseUrl.AbsoluteUri; 73 | 74 | // encode and create url 75 | return AddQueryString(baseUrl.AbsoluteUri, dictionary); 76 | } 77 | 78 | internal static void Merge(this IDictionary instance, IDictionary from, bool replaceExisting) 79 | { 80 | foreach (KeyValuePair entry in from) 81 | { 82 | if (replaceExisting || !instance.ContainsKey(entry.Key)) 83 | { 84 | instance[entry.Key] = entry.Value; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace NetCoreStack.Proxy.Extensions 6 | { 7 | internal static class ObjectExtensions 8 | { 9 | public static IDictionary ToDictionary(this object value) 10 | { 11 | Dictionary dictionary = new Dictionary(StringComparer.CurrentCultureIgnoreCase); 12 | if (value != null) 13 | { 14 | PropertyInfo[] properties = value.GetType().GetProperties(); 15 | 16 | for (int i = 0; i < properties.Length; i++) 17 | { 18 | PropertyInfo propertyInfo = properties[i]; 19 | dictionary.Add(propertyInfo.Name.Replace("_", "-"), propertyInfo.GetValue(value)); 20 | } 21 | } 22 | return dictionary; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using Microsoft.Extensions.Options; 5 | using NetCoreStack.Contracts; 6 | using NetCoreStack.Proxy.Internal; 7 | using System; 8 | using System.Reflection; 9 | 10 | namespace NetCoreStack.Proxy 11 | { 12 | public static class ServiceCollectionExtensions 13 | { 14 | private static readonly MethodInfo registryDelegate = 15 | typeof(ServiceCollectionExtensions).GetTypeInfo().GetDeclaredMethod("RegisterProxy"); 16 | 17 | public static void AddNetCoreProxy(this IServiceCollection services, 18 | IConfiguration configuration, 19 | Action setup) 20 | { 21 | services.AddOptions(); 22 | 23 | if (services == null) 24 | { 25 | throw new ArgumentNullException(nameof(services)); 26 | } 27 | 28 | if (configuration == null) 29 | { 30 | throw new ArgumentNullException(nameof(configuration)); 31 | } 32 | 33 | if (setup == null) 34 | { 35 | throw new ArgumentNullException(nameof(setup)); 36 | } 37 | 38 | services.Configure(configuration.GetSection(Constants.ProxySettings)); 39 | services.AddSingleton(); 40 | services.AddSingleton(); 41 | 42 | services.TryAdd(ServiceDescriptor.Scoped()); 43 | 44 | services.TryAdd(ServiceDescriptor.Singleton()); 45 | services.TryAdd(ServiceDescriptor.Singleton()); 46 | services.TryAdd(ServiceDescriptor.Singleton()); 47 | services.TryAdd(ServiceDescriptor.Singleton()); 48 | services.TryAdd(ServiceDescriptor.Singleton()); 49 | services.TryAdd(ServiceDescriptor.Singleton()); 50 | services.TryAdd(ServiceDescriptor.Singleton()); 51 | services.TryAdd(ServiceDescriptor.Singleton()); 52 | 53 | services.TryAddSingleton(); 54 | 55 | var options = new ProxyBuilderOptions(); 56 | options.ModelResolvers.Add(new SimpleModelResolver()); 57 | options.ModelResolvers.Add(new EnumerableModelResolver()); 58 | options.ModelResolvers.Add(new ComplexModelResolver()); 59 | options.ModelResolvers.Add(new SystemObjectModelResolver()); 60 | options.ModelResolvers.Add(new FormFileModelResolver()); 61 | setup?.Invoke(options); 62 | foreach (var item in options.ProxyList) 63 | { 64 | var type = item.GetTypeInfo().AsType(); 65 | var genericRegistry = registryDelegate.MakeGenericMethod(type); 66 | genericRegistry.Invoke(null, new object[] { services }); 67 | } 68 | 69 | if (options.ProxyContextFilter != null) 70 | { 71 | services.TryAdd(ServiceDescriptor.Scoped(typeof(IProxyContextFilter), options.ProxyContextFilter)); 72 | } 73 | 74 | var headerValues = new DefaultHeaderValues { Headers = options.DefaultHeaders }; 75 | services.AddSingleton(Options.Create(headerValues)); 76 | services.AddSingleton(options); 77 | 78 | if (options.CultureFactory != null) 79 | { 80 | var defaultCultureFactory = new DefaultCultureFactory(options.CultureFactory); 81 | services.AddSingleton(defaultCultureFactory); 82 | } 83 | } 84 | 85 | internal static void RegisterProxy(IServiceCollection services) where TProxy : IApiContract 86 | { 87 | services.AddScoped(typeof(TProxy), x => ProxyHelper.CreateProxy(x)); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace NetCoreStack.Proxy.Extensions 5 | { 6 | internal static class StringExtensions 7 | { 8 | const string rawControllerDefinition = "[Controller]"; 9 | const string regexForApi = "^I(.*)Api$"; 10 | 11 | private static string regexControllerDefinition => rawControllerDefinition.Replace("[", "\\[") 12 | .Replace("]", "\\]"); 13 | 14 | public static bool HasValue(this string value) 15 | { 16 | return !string.IsNullOrEmpty(value); 17 | } 18 | 19 | public static string GetApiRootPath(this string name, string template) 20 | { 21 | var defaultRootPath = $"api/{rawControllerDefinition}"; 22 | if (template.Equals(defaultRootPath, StringComparison.OrdinalIgnoreCase)) 23 | { 24 | var apiPath = Regex.Match(name, regexForApi); 25 | if (!apiPath.Success) 26 | throw new InvalidOperationException($"API - Proxy name format is invalid." + 27 | $"The valid format for API - Proxy Regex is: \"{regexForApi}\"!"); 28 | 29 | var rootName = apiPath.Groups[1].Value; 30 | return Regex.Replace(template, regexControllerDefinition, rootName, RegexOptions.IgnoreCase); 31 | } 32 | 33 | return template; 34 | } 35 | 36 | public static bool IsJson(this string input) 37 | { 38 | input = input.Trim(); 39 | return input.StartsWith("{") && input.EndsWith("}") 40 | || input.StartsWith("[") && input.EndsWith("]"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Extensions/TypeCoreExtensions.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using System; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | 6 | namespace NetCoreStack.Proxy.Extensions 7 | { 8 | internal static class TypeCoreExtensions 9 | { 10 | internal static Type BaseType(this Type type) 11 | { 12 | return IntrospectionExtensions.GetTypeInfo(type).BaseType; 13 | } 14 | 15 | internal static bool IsValueType(this Type type) 16 | { 17 | return IntrospectionExtensions.GetTypeInfo(type).IsValueType; 18 | } 19 | 20 | internal static bool IsGenericType(this Type type) 21 | { 22 | return IntrospectionExtensions.GetTypeInfo(type).IsGenericType; 23 | } 24 | 25 | internal static bool ContainsGenericParameters(this Type type) 26 | { 27 | return IntrospectionExtensions.GetTypeInfo(type).ContainsGenericParameters; 28 | } 29 | 30 | internal static bool IsGenericTask(this Type type) 31 | { 32 | if (type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(Task<>)) 33 | return true; 34 | 35 | return false; 36 | } 37 | 38 | internal static bool IsDirectStreamTransport(this Type returnType) 39 | { 40 | var result = false; 41 | 42 | if (returnType.IsAssignableFrom(typeof(CollectionResult))) 43 | { 44 | result = true; 45 | } 46 | 47 | return result; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/HttpDispatchProxy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using NetCoreStack.Proxy.Internal; 3 | using System; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | 9 | namespace NetCoreStack.Proxy 10 | { 11 | public class HttpDispatchProxy : DispatchProxyAsync 12 | { 13 | private ProxyContext _proxyContext; 14 | 15 | private IProxyManager _proxyManager; 16 | 17 | public HttpDispatchProxy() 18 | { 19 | } 20 | 21 | private object CreateContextProperties(RequestContext context, MethodInfo targetMethod) 22 | { 23 | var properties = new 24 | { 25 | EnvironmentName = Environment.MachineName, 26 | Message = $"Proxy call from Sandbox: {Environment.MachineName} to API method: {targetMethod?.Name}", 27 | Method = context?.Request?.Method, 28 | Authority = context?.Request?.RequestUri?.Authority, 29 | LocalPath = context?.Request?.RequestUri?.LocalPath, 30 | RequestRegion = context?.RegionKey 31 | }; 32 | 33 | return properties; 34 | } 35 | 36 | /// 37 | /// Call after resolve the proxy 38 | /// 39 | /// 40 | public void Initialize(ProxyContext context, IProxyManager proxyManager) 41 | { 42 | _proxyContext = context; 43 | _proxyManager = proxyManager; 44 | } 45 | 46 | private async Task InternalInvokeAsync(MethodInfo targetMethod, object[] args, Type genericReturnType = null) 47 | { 48 | var culture = _proxyContext.CultureFactory?.Invoke(); 49 | 50 | var descriptor = new RequestDescriptor(targetMethod, 51 | _proxyContext.ProxyType, 52 | _proxyContext.ClientIp, 53 | _proxyContext.UserAgent, 54 | _proxyContext.Query, 55 | culture, 56 | args); 57 | 58 | RequestContext requestContext = await _proxyManager.CreateRequestAsync(descriptor); 59 | ResponseContext responseContext = null; 60 | try 61 | { 62 | HttpResponseMessage response = null; 63 | string metadata = string.Empty; 64 | 65 | if (_proxyManager.HasFilter) 66 | { 67 | await Task.WhenAll(_proxyManager.RequestFilters.Select(t => t.InvokeAsync(requestContext))); 68 | } 69 | 70 | response = await _proxyManager.HttpClient.SendAsync(requestContext.Request); 71 | responseContext = await ProxyResultExecutor.ExecuteAsync(response, 72 | requestContext, 73 | genericReturnType); 74 | 75 | } 76 | catch (Exception ex) 77 | { 78 | var properties = CreateContextProperties(requestContext, targetMethod); 79 | var result = properties.GetConfigurationContextDetail(properties); 80 | throw new ProxyException(result, ex); 81 | } 82 | 83 | if ((int)responseContext.Response.StatusCode == StatusCodes.Status404NotFound) 84 | { 85 | var properties = CreateContextProperties(requestContext, targetMethod); 86 | var result = properties.GetConfigurationContextDetail(properties); 87 | throw new ProxyException(result, new HttpRequestException()); 88 | } 89 | 90 | return responseContext; 91 | } 92 | 93 | public override async Task InvokeAsync(MethodInfo method, object[] args) 94 | { 95 | if (method.Name == "get_ControllerContext") 96 | { 97 | throw new NotSupportedException($"\"ActionContext\" is not supported for proxy instance"); 98 | } 99 | 100 | using (ResponseContext responseContext = await InternalInvokeAsync(method, args)) { } 101 | } 102 | 103 | public override async Task InvokeAsyncT(MethodInfo method, object[] args) 104 | { 105 | if (method.Name == "get_ControllerContext") 106 | { 107 | throw new NotSupportedException($"\"ActionContext\" is not supported for proxy instance"); 108 | } 109 | 110 | using (ResponseContext responseContext = await InternalInvokeAsync(method, args, typeof(T))) 111 | { 112 | return (T)responseContext.Value; 113 | } 114 | } 115 | 116 | public override object Invoke(MethodInfo method, object[] args) 117 | { 118 | if (method.Name == "get_ControllerContext") 119 | { 120 | throw new NotSupportedException($"\"ActionContext\" is not supported for proxy instance"); 121 | } 122 | 123 | ResponseContext context = Task.Run(async () => await InternalInvokeAsync(method, args).ConfigureAwait(false)).Result; 124 | return context.Value; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IContentModelBinder.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public interface IContentModelBinder 4 | { 5 | void BindContent(ContentModelBindingContext bindingContext); 6 | } 7 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/ICultureFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace NetCoreStack.Proxy.Internal 5 | { 6 | public interface ICultureFactory 7 | { 8 | CultureInfo Invoke(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IDefaultHeaderProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IDefaultHeaderProvider 6 | { 7 | IDictionary Headers { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IHeaderProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IHeaderProvider 6 | { 7 | IDictionary Headers { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IHttpClientAccessor.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IHttpClientAccessor 6 | { 7 | HttpClient HttpClient { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IModelContentResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IModelContentResolver 6 | { 7 | ModelDictionaryResult Resolve(List parameters, object[] args, int parameterOffset = 0, bool ignoreModelPrefix = false); 8 | 9 | string ResolveParameter(ProxyModelMetadata parameter, object value, bool isTopLevelObject, string prefix = ""); 10 | } 11 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IModelJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public interface IModelJsonSerializer : IModelSerializer 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IModelResolver.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public interface IModelResolver 4 | { 5 | ModelResolverResult Resolve(ModelDictionaryContext context, ModelDictionaryResult result); 6 | } 7 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IModelSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public interface IModelSerializer 4 | { 5 | string Serialize(object value); 6 | } 7 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IModelXmlSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public interface IModelXmlSerializer : IModelSerializer 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IProxyContentStreamProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public interface IProxyContentStreamProvider 8 | { 9 | Task CreateRequestContentAsync(RequestDescriptor requestDescriptor, 10 | HttpRequestMessage request, 11 | ProxyMethodDescriptor descriptor, 12 | UriBuilder uriBuilder); 13 | } 14 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IProxyContextFilter.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Internal; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IProxyContextFilter 6 | { 7 | void Invoke(ProxyContext proxyContext); 8 | } 9 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IProxyEndpointManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IProxyEndpointManager 6 | { 7 | UriBuilder CreateUriBuilder(ProxyMethodDescriptor methodDescriptor, string route, string regionKey, string targetMethodName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IProxyManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public interface IProxyManager 8 | { 9 | bool HasFilter { get; } 10 | HttpClient HttpClient { get; } 11 | List RequestFilters { get; } 12 | Task CreateRequestAsync(RequestDescriptor context); 13 | } 14 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IProxyRequestFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IProxyRequestFilter 6 | { 7 | Task InvokeAsync(RequestContext context); 8 | } 9 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Interfaces/IProxyTypeManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public interface IProxyTypeManager 6 | { 7 | IList ProxyDescriptors { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/ClosedGenericMatcher.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.Reflection; 6 | 7 | namespace NetCoreStack.Proxy.Internal 8 | { 9 | internal static class ClosedGenericMatcher 10 | { 11 | /// 12 | /// Determine whether is or implements a closed generic 13 | /// created from . 14 | /// 15 | /// The of interest. 16 | /// The open generic to match. Usually an interface. 17 | /// 18 | /// The closed generic created from that 19 | /// is or implements. null if the two s have no such 20 | /// relationship. 21 | /// 22 | /// 23 | /// This method will return if is 24 | /// typeof(KeyValuePair{,}), and is 25 | /// typeof(KeyValuePair{string, object}). 26 | /// 27 | public static Type ExtractGenericInterface(Type queryType, Type interfaceType) 28 | { 29 | if (queryType == null) 30 | { 31 | throw new ArgumentNullException(nameof(queryType)); 32 | } 33 | 34 | if (interfaceType == null) 35 | { 36 | throw new ArgumentNullException(nameof(interfaceType)); 37 | } 38 | 39 | if (IsGenericInstantiation(queryType, interfaceType)) 40 | { 41 | // queryType matches (i.e. is a closed generic type created from) the open generic type. 42 | return queryType; 43 | } 44 | 45 | // Otherwise check all interfaces the type implements for a match. 46 | // - If multiple different generic instantiations exists, we want the most derived one. 47 | // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic. 48 | // 49 | // We do this by looking at interfaces on the type, and recursing to the base type 50 | // if we don't find any matches. 51 | return GetGenericInstantiation(queryType, interfaceType); 52 | } 53 | 54 | private static bool IsGenericInstantiation(Type candidate, Type interfaceType) 55 | { 56 | return 57 | candidate.GetTypeInfo().IsGenericType && 58 | candidate.GetGenericTypeDefinition() == interfaceType; 59 | } 60 | 61 | private static Type GetGenericInstantiation(Type queryType, Type interfaceType) 62 | { 63 | Type bestMatch = null; 64 | var interfaces = queryType.GetInterfaces(); 65 | foreach (var @interface in interfaces) 66 | { 67 | if (IsGenericInstantiation(@interface, interfaceType)) 68 | { 69 | if (bestMatch == null) 70 | { 71 | bestMatch = @interface; 72 | } 73 | else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) 74 | { 75 | bestMatch = @interface; 76 | } 77 | else 78 | { 79 | // There are two matches at this level of the class hierarchy, but @interface is after 80 | // bestMatch in the sort order. 81 | } 82 | } 83 | } 84 | 85 | if (bestMatch != null) 86 | { 87 | return bestMatch; 88 | } 89 | 90 | // BaseType will be null for object and interfaces, which means we've reached 'bottom'. 91 | var baseType = queryType?.GetTypeInfo().BaseType; 92 | if (baseType == null) 93 | { 94 | return null; 95 | } 96 | else 97 | { 98 | return GetGenericInstantiation(baseType, interfaceType); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/ConfigurationHelper.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Extensions; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NetCoreStack.Proxy.Internal 7 | { 8 | public static class ConfigurationHelper 9 | { 10 | public static string GetConfigurationContextDetail(this object configuration, object properties) 11 | { 12 | StringBuilder sb = new StringBuilder(); 13 | var dictionary = properties.ToDictionary(); 14 | foreach (KeyValuePair entry in dictionary) 15 | { 16 | sb.AppendLine(entry.Value?.ToString()); 17 | } 18 | 19 | return sb.ToString(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy.Internal 2 | { 3 | public class Constants 4 | { 5 | public static string Prefix = "NetCoreStack"; 6 | public const string ProxySettings = "ProxySettings"; 7 | public const string ContentTypeJsonWithEncoding = "application/json; charset=utf-8"; 8 | public const string ContentTypeMultipartFormData = "multipart/form-data"; 9 | public readonly static string ClientUserAgentHeader = "User-Agent"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/DefaultCultureFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace NetCoreStack.Proxy.Internal 5 | { 6 | public class DefaultCultureFactory : ICultureFactory 7 | { 8 | private readonly Func _func; 9 | 10 | public DefaultCultureFactory(Func func) 11 | { 12 | _func = func ?? throw new ArgumentNullException(nameof(func)); 13 | } 14 | 15 | public CultureInfo Invoke() 16 | { 17 | return _func(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/DefaultModelJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace NetCoreStack.Proxy.Internal 4 | { 5 | public class DefaultModelJsonSerializer : IModelJsonSerializer 6 | { 7 | public string Serialize(object value) 8 | { 9 | return JsonConvert.SerializeObject(value, new JsonSerializerSettings { 10 | ReferenceLoopHandling = ReferenceLoopHandling.Ignore 11 | }); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/DefaultModelXmlSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Xml; 4 | using System.Xml.Serialization; 5 | 6 | namespace NetCoreStack.Proxy.Internal 7 | { 8 | public class DefaultModelXmlSerializer : IModelXmlSerializer 9 | { 10 | public class Utf8StringWriter : StringWriter 11 | { 12 | public override Encoding Encoding => Encoding.UTF8; 13 | } 14 | 15 | public string Serialize(object value) 16 | { 17 | XmlWriterSettings settings = new XmlWriterSettings 18 | { 19 | NewLineHandling = NewLineHandling.None, 20 | Indent = false 21 | }; 22 | 23 | Utf8StringWriter stringWriter = new Utf8StringWriter(); 24 | XmlWriter writer = XmlWriter.Create(stringWriter, settings); 25 | XmlSerializer xmlSerializer = new XmlSerializer(value.GetType()); 26 | xmlSerializer.Serialize(writer, value); 27 | return stringWriter.ToString(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/DefaultProxyTypeManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing.Template; 2 | using NetCoreStack.Contracts; 3 | using NetCoreStack.Proxy.Extensions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Reflection; 9 | 10 | namespace NetCoreStack.Proxy.Internal 11 | { 12 | public class DefaultProxyTypeManager : IProxyTypeManager 13 | { 14 | private readonly ProxyBuilderOptions _options; 15 | 16 | public IList ProxyDescriptors { get; set; } 17 | public ProxyMetadataProvider MetadataProvider { get; } 18 | 19 | public DefaultProxyTypeManager(ProxyMetadataProvider metadataProvider, ProxyBuilderOptions options) 20 | { 21 | MetadataProvider = metadataProvider; 22 | _options = options; 23 | ProxyDescriptors = GetProxyDescriptors(); 24 | } 25 | 26 | private IList GetProxyDescriptors() 27 | { 28 | var baseMethods = typeof(IApiContract).GetMethods().Where(x => !x.IsSpecialName).ToList(); 29 | var types = _options.ProxyList; 30 | var descriptors = new List(); 31 | foreach (var proxyType in types) 32 | { 33 | var pathAttr = proxyType.GetTypeInfo().GetCustomAttribute(); 34 | if (pathAttr == null) 35 | throw new ProxyException($"{nameof(ApiRouteAttribute)} required for Proxy - Api interface."); 36 | 37 | if (!pathAttr.RegionKey.HasValue()) 38 | throw new ProxyException($"Specify the \"{nameof(pathAttr.RegionKey)}\"!"); 39 | 40 | var route = proxyType.Name.GetApiRootPath(pathAttr.RouteTemplate); 41 | if (route.StartsWith("/")) 42 | route = route.Substring(1); 43 | 44 | ProxyDescriptor descriptor = new ProxyDescriptor(proxyType, pathAttr.RegionKey, route); 45 | 46 | var interfaces = proxyType.GetInterfaces() 47 | .Except(new List { typeof(IApiContract) }).ToList(); 48 | 49 | var interfaceMethods = new List(); 50 | if (interfaces.Any()) 51 | { 52 | foreach (var type in interfaces) 53 | { 54 | interfaceMethods.AddRange(type.GetMethods().Where(x => !x.IsSpecialName).ToList()); 55 | } 56 | } 57 | 58 | var methods = proxyType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).ToList(); 59 | interfaceMethods.AddRange(baseMethods); 60 | methods.AddRange(interfaceMethods); 61 | 62 | foreach (var method in methods) 63 | { 64 | var proxyMethodDescriptor = new ProxyMethodDescriptor(method); 65 | bool isMultipartFormData = false; 66 | foreach (var parameter in method.GetParameters()) 67 | { 68 | var modelMetadata = MetadataProvider.GetMetadataForParameter(parameter); 69 | if (modelMetadata.IsFormFile) 70 | { 71 | isMultipartFormData = true; 72 | if (parameter.CustomAttributes 73 | .Any(a => a.AttributeType.Name == "FromBodyAttribute")) 74 | { 75 | throw new ProxyException($"Parameter: \"{parameter.ParameterType.Name} as {parameter.Name}\" " + 76 | "contains IFormFile type property. " + 77 | "Remove FromBody attribute to proper model binding."); 78 | } 79 | } 80 | else 81 | { 82 | var properties = modelMetadata.ToFlat().ToList(); 83 | if (properties.Any(p => p.IsFormFile)) 84 | { 85 | isMultipartFormData = true; 86 | } 87 | } 88 | 89 | proxyMethodDescriptor.Parameters.Add(modelMetadata); 90 | } 91 | 92 | var httpMethodAttribute = method.GetCustomAttributes(inherit: true) 93 | .OfType().FirstOrDefault(); 94 | 95 | if (httpMethodAttribute is HttpPostMarkerAttribute postMarker) 96 | { 97 | proxyMethodDescriptor.ContentType = postMarker.ContentType; 98 | } 99 | 100 | if (httpMethodAttribute is HttpPutMarkerAttribute putMarker) 101 | { 102 | proxyMethodDescriptor.ContentType = putMarker.ContentType; 103 | } 104 | 105 | if (isMultipartFormData) 106 | { 107 | proxyMethodDescriptor.ContentType = ContentType.MultipartFormData; 108 | } 109 | 110 | var timeoutAttr = method.GetCustomAttribute(); 111 | if (timeoutAttr != null) 112 | proxyMethodDescriptor.Timeout = timeoutAttr.Timeout; 113 | 114 | var httpHeaders = method.GetCustomAttribute(); 115 | if (httpHeaders != null && httpHeaders.Headers != null) 116 | { 117 | foreach (var header in httpHeaders.Headers) 118 | { 119 | var token = header.Split(':'); 120 | if (token.Length > 1) 121 | { 122 | proxyMethodDescriptor.Headers[token[0].Trim()] = token[1].Trim(); 123 | } 124 | } 125 | } 126 | 127 | if (httpMethodAttribute != null) 128 | { 129 | if (httpMethodAttribute.Template.HasValue()) 130 | { 131 | var template = httpMethodAttribute.Template; 132 | proxyMethodDescriptor.MethodMarkerTemplate = template; 133 | var routeTemplate = TemplateParser.Parse(template); 134 | if (routeTemplate != null) 135 | { 136 | proxyMethodDescriptor.RouteTemplate = routeTemplate; 137 | proxyMethodDescriptor.TemplateParts = new List(routeTemplate.Parameters); 138 | 139 | proxyMethodDescriptor.TemplateKeys = routeTemplate.Segments 140 | .SelectMany(s => s.Parts.Where(p => p.IsLiteral) 141 | .Select(t => t.Text)).ToList(); 142 | 143 | proxyMethodDescriptor.TemplateParameterKeys = routeTemplate.Segments 144 | .SelectMany(s => s.Parts.Where(p => p.IsParameter) 145 | .Select(t => t.Name)).ToList(); 146 | } 147 | } 148 | 149 | if (httpMethodAttribute is HttpGetMarkerAttribute) 150 | { 151 | proxyMethodDescriptor.HttpMethod = HttpMethod.Get; 152 | } 153 | else if (httpMethodAttribute is HttpPostMarkerAttribute) 154 | { 155 | proxyMethodDescriptor.HttpMethod = HttpMethod.Post; 156 | } 157 | else if (httpMethodAttribute is HttpPutMarkerAttribute) 158 | { 159 | proxyMethodDescriptor.HttpMethod = HttpMethod.Put; 160 | if(proxyMethodDescriptor.HasAnyTemplateParameterKey) 161 | { 162 | for (int i = 0; i < proxyMethodDescriptor.TemplateParameterKeys.Count; i++) 163 | { 164 | var key = proxyMethodDescriptor.TemplateParameterKeys[i]; 165 | var templatePartParameterOrderedModel = proxyMethodDescriptor.Parameters[i]; 166 | if (templatePartParameterOrderedModel == null || templatePartParameterOrderedModel.PropertyName != key) 167 | { 168 | throw new ProxyException($"Key parameter: \"{key}\" does not match on the corresponding method parameter order."); 169 | } 170 | 171 | if (!templatePartParameterOrderedModel.IsSimpleType) 172 | { 173 | throw new ProxyException($"Key parameter: \"{key}\" type: \"{ templatePartParameterOrderedModel.ModelType.Name }\" " + 174 | $"must be ProxyModelMetadata.IsSimpleType to proper HTTP PUT Uri-Key (Url) model binding."); 175 | } 176 | } 177 | } 178 | } 179 | else if (httpMethodAttribute is HttpDeleteMarkerAttribute) 180 | { 181 | proxyMethodDescriptor.HttpMethod = HttpMethod.Delete; 182 | } 183 | } 184 | else 185 | { 186 | // Default GET 187 | proxyMethodDescriptor.HttpMethod = HttpMethod.Get; 188 | } 189 | 190 | descriptor.Methods.Add(method, proxyMethodDescriptor); 191 | } 192 | 193 | descriptors.Add(descriptor); 194 | } 195 | 196 | return descriptors; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/HashCodeCombiner.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.Collections; 5 | using System.Collections.Generic; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace NetCoreStack.Proxy.Internal 9 | { 10 | internal struct HashCodeCombiner 11 | { 12 | private long _combinedHash64; 13 | 14 | public int CombinedHash 15 | { 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 17 | get { return _combinedHash64.GetHashCode(); } 18 | } 19 | 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | private HashCodeCombiner(long seed) 22 | { 23 | _combinedHash64 = seed; 24 | } 25 | 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public void Add(IEnumerable e) 28 | { 29 | if (e == null) 30 | { 31 | Add(0); 32 | } 33 | else 34 | { 35 | var count = 0; 36 | foreach (object o in e) 37 | { 38 | Add(o); 39 | count++; 40 | } 41 | Add(count); 42 | } 43 | } 44 | 45 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 | public static implicit operator int(HashCodeCombiner self) 47 | { 48 | return self.CombinedHash; 49 | } 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public void Add(int i) 53 | { 54 | _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i; 55 | } 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public void Add(string s) 59 | { 60 | var hashCode = (s != null) ? s.GetHashCode() : 0; 61 | Add(hashCode); 62 | } 63 | 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public void Add(object o) 66 | { 67 | var hashCode = (o != null) ? o.GetHashCode() : 0; 68 | Add(hashCode); 69 | } 70 | 71 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 72 | public void Add(TValue value, IEqualityComparer comparer) 73 | { 74 | var hashCode = value != null ? comparer.GetHashCode(value) : 0; 75 | Add(hashCode); 76 | } 77 | 78 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 79 | public static HashCodeCombiner Start() 80 | { 81 | return new HashCodeCombiner(0x1505L); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/ProxyContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace NetCoreStack.Proxy.Internal 5 | { 6 | public class ProxyContext 7 | { 8 | public string ClientIp { get; set; } 9 | public string Query { get; set; } 10 | public string UserAgent { get; set; } 11 | public Type ProxyType { get; } 12 | public Func CultureFactory { get; set; } 13 | 14 | public ProxyContext(Type proxyType) 15 | { 16 | ProxyType = proxyType; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/ProxyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public class ProxyException : Exception 6 | { 7 | public ProxyException(string message) 8 | :base(message) 9 | { 10 | 11 | } 12 | 13 | public ProxyException(string message, Exception innerException) 14 | : base(message, innerException) 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/ProxyHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NetCoreStack.Contracts; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace NetCoreStack.Proxy.Internal 7 | { 8 | internal static class ProxyHelper 9 | { 10 | internal static TProxy CreateProxy(IServiceProvider container) where TProxy : IApiContract 11 | { 12 | object proxy = DispatchProxyAsync.Create(); 13 | var proxyContextFilter = container.GetRequiredService(); 14 | var proxyContext = new ProxyContext(typeof(TProxy)); 15 | 16 | var cultureFactory = container.GetService(); 17 | if (cultureFactory != null) 18 | { 19 | proxyContext.CultureFactory = cultureFactory.Invoke; 20 | } 21 | 22 | proxyContextFilter.Invoke(proxyContext); 23 | 24 | var initializer = proxy.GetType().GetMethod(nameof(HttpDispatchProxy.Initialize)); 25 | if (initializer == null) 26 | { 27 | throw new ArgumentNullException(nameof(initializer)); 28 | } 29 | 30 | initializer.Invoke(proxy, new[] { proxyContext, (object)container.GetService() }); 31 | return (TProxy)proxy; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/ProxyManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using NetCoreStack.Proxy.Extensions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | 9 | namespace NetCoreStack.Proxy 10 | { 11 | internal class ProxyManager : IProxyManager 12 | { 13 | private readonly IProxyTypeManager _typeManager; 14 | private readonly IHttpClientAccessor _httpClientAccessor; 15 | private readonly IOptions _options; 16 | private readonly IDefaultHeaderProvider _headerProvider; 17 | private readonly IProxyEndpointManager _endpointManager; 18 | private readonly IProxyContentStreamProvider _streamProvider; 19 | 20 | public ProxyManager(IProxyTypeManager typeManager, 21 | IDefaultHeaderProvider headerProvider, 22 | IHttpClientAccessor httpClientAccessor, 23 | IProxyEndpointManager endpointManager, 24 | IProxyContentStreamProvider streamProvider, 25 | IOptions options, 26 | IEnumerable requestFilters) 27 | { 28 | _typeManager = typeManager ?? throw new ArgumentNullException(nameof(typeManager)); 29 | _headerProvider = headerProvider ?? throw new ArgumentNullException(nameof(headerProvider)); 30 | _httpClientAccessor = httpClientAccessor ?? throw new ArgumentNullException(nameof(httpClientAccessor)); 31 | _endpointManager = endpointManager ?? throw new ArgumentNullException(nameof(endpointManager)); 32 | _streamProvider = streamProvider ?? throw new ArgumentNullException(nameof(endpointManager)); 33 | _options = options ?? throw new ArgumentNullException(nameof(options)); 34 | 35 | RequestFilters = new List(); 36 | if (requestFilters != null && requestFilters.Any()) 37 | { 38 | HasFilter = true; 39 | RequestFilters = requestFilters.ToList(); 40 | } 41 | } 42 | 43 | public HttpClient HttpClient 44 | { 45 | get 46 | { 47 | return _httpClientAccessor.HttpClient; 48 | } 49 | } 50 | 51 | public bool HasFilter { get; } 52 | public List RequestFilters { get; } 53 | 54 | private HttpRequestMessage CreateHttpRequest(ProxyMethodDescriptor methodDescriptor, RequestDescriptor requestDescriptor) 55 | { 56 | HttpRequestMessage requestMessage = new HttpRequestMessage(); 57 | 58 | var headers = new Dictionary(); 59 | 60 | if (!string.IsNullOrEmpty(requestDescriptor.ClientIp)) 61 | { 62 | headers.Add("X-Forwarded-For", requestDescriptor.ClientIp); 63 | } 64 | 65 | if (!string.IsNullOrEmpty(requestDescriptor.UserAgent)) 66 | { 67 | headers.Add("User-Agent", requestDescriptor.UserAgent); 68 | } 69 | 70 | if (requestDescriptor.Culture != null) 71 | { 72 | headers.Add("Accept-Language", requestDescriptor.Culture.Name); 73 | } 74 | 75 | headers.Merge(_headerProvider.Headers, true); 76 | 77 | headers.Merge(methodDescriptor.Headers, true); 78 | 79 | foreach (KeyValuePair entry in headers) 80 | { 81 | requestMessage.Headers.Add(entry.Key, entry.Value); 82 | } 83 | 84 | return requestMessage; 85 | } 86 | 87 | public async Task CreateRequestAsync(RequestDescriptor requestDescriptor) 88 | { 89 | var proxyDescriptor = _typeManager.ProxyDescriptors.FirstOrDefault(x => x.ProxyType == requestDescriptor.ProxyType); 90 | 91 | if (proxyDescriptor == null) 92 | throw new ArgumentOutOfRangeException("Proxy type could not be found!"); 93 | 94 | var regionKey = proxyDescriptor.RegionKey; 95 | 96 | ProxyMethodDescriptor methodDescriptor; 97 | if (!proxyDescriptor.Methods.TryGetValue(requestDescriptor.TargetMethod, out methodDescriptor)) 98 | throw new ArgumentOutOfRangeException("Method (Action) info could not be found!"); 99 | 100 | HttpRequestMessage request = CreateHttpRequest(methodDescriptor, requestDescriptor); 101 | request.Method = methodDescriptor.HttpMethod; 102 | var methodPath = requestDescriptor.TargetMethod.Name; 103 | if (methodDescriptor.MethodMarkerTemplate.HasValue()) 104 | methodPath = methodDescriptor.MethodMarkerTemplate; 105 | 106 | UriBuilder uriBuilder = _endpointManager.CreateUriBuilder(methodDescriptor, proxyDescriptor.Route, regionKey, methodPath); 107 | TimeSpan? timeout = methodDescriptor.Timeout; 108 | 109 | await _streamProvider.CreateRequestContentAsync(requestDescriptor, request, methodDescriptor, uriBuilder); 110 | 111 | return new RequestContext(request, 112 | methodDescriptor, 113 | proxyDescriptor.RegionKey, 114 | timeout); 115 | } 116 | 117 | public HttpClient GetHttpClient() 118 | { 119 | return _httpClientAccessor.HttpClient; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Internal/ProxyResultExecutor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace NetCoreStack.Proxy.Internal 7 | { 8 | public static class ProxyResultExecutor 9 | { 10 | public static async Task ExecuteAsync(HttpResponseMessage response, 11 | RequestContext requestContext, 12 | Type genericReturnType = null) 13 | { 14 | var context = new ResponseContext(response, requestContext); 15 | var methodDescriptor = requestContext.MethodDescriptor; 16 | 17 | if (response == null) 18 | return context; 19 | 20 | var statusCode = (int)response.StatusCode; 21 | 22 | if (statusCode >= 500) 23 | { 24 | throw new ProxyException("Proxy call result content is can not be null, " + 25 | "The exception may have occurred on the Server", null); 26 | } 27 | 28 | context.ResultContent = await response.Content.ReadAsStringAsync(); 29 | 30 | if (statusCode >= 400) 31 | { 32 | var message = $"Status Code: {statusCode}-{response.StatusCode.ToString()}, Message: {context.ResultContent}"; 33 | throw new ProxyException(message, null); 34 | } 35 | 36 | if (methodDescriptor.IsByteArrayReturn) 37 | { 38 | context.Value = await response.Content.ReadAsByteArrayAsync(); 39 | return context; 40 | } 41 | 42 | if (methodDescriptor.IsVoidReturn) 43 | return context; 44 | 45 | if (methodDescriptor.IsTaskReturn) 46 | { 47 | context.Value = Task.CompletedTask; 48 | return context; 49 | } 50 | 51 | if (genericReturnType != null) 52 | context.Value = JsonConvert.DeserializeObject(context.ResultContent, genericReturnType); 53 | else 54 | context.Value = JsonConvert.DeserializeObject(context.ResultContent, methodDescriptor.ReturnType); 55 | 56 | return context; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/NetCoreStack.Proxy.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NetCoreStack.Proxy 6 | NetCoreStack.Proxy 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Options/ProxyBuilderOptions.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | 6 | namespace NetCoreStack.Proxy 7 | { 8 | public class ProxyBuilderOptions 9 | { 10 | internal List ProxyList { get; } 11 | 12 | internal Type ProxyContextFilter { get; set; } 13 | 14 | public IDictionary DefaultHeaders { get; } 15 | 16 | public IList ModelResolvers { get; } 17 | 18 | public Func CultureFactory { get; set; } 19 | 20 | public ProxyBuilderOptions() 21 | { 22 | DefaultHeaders = new Dictionary(); 23 | ProxyList = new List(); 24 | ModelResolvers = new List(); 25 | } 26 | 27 | /// 28 | /// Register proxy interface 29 | /// 30 | /// 31 | public void Register() where TProxy : IApiContract 32 | { 33 | ProxyList.Add(typeof(TProxy)); 34 | } 35 | 36 | /// 37 | /// Use the TAccessor as instead default accessors values 38 | /// 39 | /// 40 | public void RegisterFilter() where TFilter : IProxyContextFilter 41 | { 42 | ProxyContextFilter = typeof(TFilter); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Options/ProxyOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public class ProxyOptions 6 | { 7 | public Dictionary RegionKeys { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/ProxyMetadataProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Reflection; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public class ProxyMetadataProvider 8 | { 9 | private readonly TypeCache _typeCache = new TypeCache(); 10 | private readonly Func _cacheEntryFactory; 11 | private readonly ProxyModelMetadataCacheEntry _metadataCacheEntryForObjectType; 12 | 13 | public ProxyMetadataProvider() 14 | { 15 | _cacheEntryFactory = CreateCacheEntry; 16 | _metadataCacheEntryForObjectType = GetMetadataCacheEntryForObjectType(); 17 | } 18 | 19 | private ProxyModelMetadataCacheEntry GetMetadataCacheEntryForObjectType() 20 | { 21 | var key = ProxyModelMetadataIdentity.ForType(typeof(object)); 22 | var entry = CreateCacheEntry(key); 23 | return entry; 24 | } 25 | 26 | private ProxyModelMetadataCacheEntry CreateCacheEntry(ProxyModelMetadataIdentity key) 27 | { 28 | var metadata = new ProxyModelMetadata(key); 29 | return new ProxyModelMetadataCacheEntry(metadata); 30 | } 31 | 32 | private ProxyModelMetadataCacheEntry GetCacheEntry(Type modelType) 33 | { 34 | ProxyModelMetadataCacheEntry cacheEntry; 35 | 36 | if (modelType == typeof(object)) 37 | { 38 | cacheEntry = _metadataCacheEntryForObjectType; 39 | } 40 | else 41 | { 42 | var key = ProxyModelMetadataIdentity.ForType(modelType); 43 | 44 | cacheEntry = _typeCache.GetOrAdd(key, _cacheEntryFactory); 45 | } 46 | 47 | return cacheEntry; 48 | } 49 | 50 | private ProxyModelMetadataCacheEntry GetCacheEntry(ParameterInfo parameter) 51 | { 52 | return _typeCache.GetOrAdd( 53 | ProxyModelMetadataIdentity.ForParameter(parameter), 54 | _cacheEntryFactory); 55 | } 56 | 57 | public ProxyModelMetadata GetMetadataForParameter(ParameterInfo parameter) 58 | { 59 | if (parameter == null) 60 | { 61 | throw new ArgumentNullException(nameof(parameter)); 62 | } 63 | 64 | var cacheEntry = GetCacheEntry(parameter); 65 | 66 | return cacheEntry.Metadata; 67 | } 68 | 69 | public ProxyModelMetadata GetMetadataForType(Type modelType) 70 | { 71 | if (modelType == null) 72 | { 73 | throw new ArgumentNullException(nameof(modelType)); 74 | } 75 | 76 | var cacheEntry = GetCacheEntry(modelType); 77 | 78 | return cacheEntry.Metadata; 79 | } 80 | 81 | private class TypeCache : ConcurrentDictionary 82 | { 83 | } 84 | 85 | private struct ProxyModelMetadataCacheEntry 86 | { 87 | public ProxyModelMetadataCacheEntry(ProxyModelMetadata metadata) 88 | { 89 | Metadata = metadata; 90 | } 91 | 92 | public ProxyModelMetadata Metadata { get; } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Resolvers/ComplexModelResolver.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public class ComplexModelResolver : ModelResolverBase 4 | { 5 | public override ModelResolverResult Resolve(ModelDictionaryContext context, ModelDictionaryResult result) 6 | { 7 | return ModelResolverResult.Failed(); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Resolvers/EnumerableModelResolver.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public class EnumerableModelResolver : ModelResolverBase 4 | { 5 | public override ModelResolverResult Resolve(ModelDictionaryContext context, ModelDictionaryResult result) 6 | { 7 | return ModelResolverResult.Failed(); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Resolvers/FormFileModelResolver.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public class FormFileModelResolver : ModelResolverBase 4 | { 5 | public override ModelResolverResult Resolve(ModelDictionaryContext context, ModelDictionaryResult result) 6 | { 7 | return ModelResolverResult.Failed(); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Resolvers/ModelResolverBase.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public abstract class ModelResolverBase : IModelResolver 4 | { 5 | public abstract ModelResolverResult Resolve(ModelDictionaryContext context, ModelDictionaryResult result); 6 | } 7 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Resolvers/ResolverFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetCoreStack.Proxy 4 | { 5 | public static class ResolverFactory 6 | { 7 | public static IModelResolver GetModelResolver(ProxyModelMetadata modelMetadata) 8 | { 9 | if (modelMetadata == null) 10 | { 11 | throw new ArgumentNullException(nameof(modelMetadata)); 12 | } 13 | 14 | if (modelMetadata.IsEnumerableType) 15 | { 16 | return new EnumerableModelResolver(); 17 | } 18 | 19 | if (modelMetadata.IsFormFile) 20 | { 21 | return new FormFileModelResolver(); 22 | } 23 | 24 | if (modelMetadata.IsComplexType && !modelMetadata.IsCollectionType) 25 | { 26 | return new ComplexModelResolver(); 27 | } 28 | 29 | return new SimpleModelResolver(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Resolvers/SimpleModelResolver.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public class SimpleModelResolver : ModelResolverBase 4 | { 5 | public override ModelResolverResult Resolve(ModelDictionaryContext context, ModelDictionaryResult result) 6 | { 7 | return ModelResolverResult.Failed(); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Resolvers/SystemObjectModelResolver.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public class SystemObjectModelResolver : ModelResolverBase 4 | { 5 | public override ModelResolverResult Resolve(ModelDictionaryContext context, ModelDictionaryResult result) 6 | { 7 | return ModelResolverResult.Failed(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/RoundRobinManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace NetCoreStack.Proxy 8 | { 9 | public class RoundRobinManager 10 | { 11 | private static readonly object _lockObj = new object(); 12 | 13 | public readonly ConcurrentDictionary> ProxyRegionDict = 14 | new ConcurrentDictionary>(); 15 | 16 | protected ProxyOptions Options { get; } 17 | 18 | public RoundRobinManager(IOptions options) 19 | { 20 | if (options == null) 21 | { 22 | throw new ArgumentNullException(nameof(options)); 23 | } 24 | 25 | if (options.Value == null || options.Value.RegionKeys == null) 26 | { 27 | throw new ArgumentNullException(nameof(options.Value.RegionKeys)); 28 | } 29 | 30 | Options = options.Value; 31 | 32 | Dictionary regionKeys = new Dictionary(); 33 | 34 | foreach (KeyValuePair entry in Options.RegionKeys) 35 | { 36 | if (string.IsNullOrEmpty(entry.Value)) 37 | continue; 38 | 39 | var urls = entry.Value.Split(',').Select(p => p.Trim()).ToList(); 40 | ProxyRegionDict.TryAdd(entry.Key, new Queue(urls)); 41 | } 42 | } 43 | 44 | public UriBuilder RoundRobinUri(string regionKey) 45 | { 46 | UriBuilder uri = null; 47 | Queue queue; 48 | if (!ProxyRegionDict.TryGetValue(regionKey, out queue)) 49 | { 50 | throw new ArgumentOutOfRangeException($"Region could not be found! {nameof(RoundRobinManager)}: \"{regionKey}\""); 51 | } 52 | 53 | lock (_lockObj) 54 | { 55 | var url = queue.Dequeue(); 56 | uri = new UriBuilder(url); 57 | queue.Enqueue(url); 58 | return uri; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ContentModelBindingContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing.Template; 2 | using NetCoreStack.Contracts; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net.Http; 7 | 8 | namespace NetCoreStack.Proxy 9 | { 10 | public class ContentModelBindingContext 11 | { 12 | public int ArgsLength 13 | { 14 | get 15 | { 16 | return Args != null ? Args.Length : 0; 17 | } 18 | } 19 | 20 | public bool HasAnyTemplateParameterKey 21 | { 22 | get 23 | { 24 | return TemplateParameterKeys.Any(); 25 | } 26 | } 27 | 28 | public Uri Uri => UriBuilder.Uri; 29 | public string MethodMarkerTemplate => MethodDescriptor.MethodMarkerTemplate; 30 | public List TemplateParts => MethodDescriptor.TemplateParts; 31 | public List TemplateParameterKeys => MethodDescriptor.TemplateParameterKeys; 32 | public List Parameters => MethodDescriptor.Parameters; 33 | public ContentType ContentType { get; set; } 34 | public ContentModelBindingResult ContentResult { get; set; } 35 | public object[] Args { get; set; } 36 | public IModelContentResolver ModelContentResolver { get; set; } 37 | public HttpMethod HttpMethod { get; } 38 | public UriBuilder UriBuilder { get; set; } 39 | public ProxyMethodDescriptor MethodDescriptor { get; } 40 | 41 | public ContentModelBindingContext(HttpMethod httpMethod, ProxyMethodDescriptor methodDescriptor, UriBuilder uriBuilder) 42 | { 43 | HttpMethod = httpMethod ?? throw new ArgumentNullException(nameof(httpMethod)); 44 | MethodDescriptor = methodDescriptor ?? throw new ArgumentNullException(nameof(methodDescriptor)); 45 | UriBuilder = uriBuilder ?? throw new ArgumentNullException(nameof(uriBuilder)); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ContentModelBindingResult.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Internal; 2 | using System; 3 | using System.Net.Http; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public struct ContentModelBindingResult : IEquatable 8 | { 9 | public static ContentModelBindingResult Failed() 10 | { 11 | return new ContentModelBindingResult(content: null, isContentSet: false); 12 | } 13 | 14 | public static ContentModelBindingResult Success(HttpContent content) 15 | { 16 | return new ContentModelBindingResult(content, isContentSet: true); 17 | } 18 | 19 | private ContentModelBindingResult(HttpContent content, bool isContentSet) 20 | { 21 | Content = content; 22 | IsContentSet = isContentSet; 23 | } 24 | 25 | public HttpContent Content { get; } 26 | 27 | public bool IsContentSet { get; } 28 | 29 | public override bool Equals(object obj) 30 | { 31 | var other = obj as ContentModelBindingResult?; 32 | if (other == null) 33 | { 34 | return false; 35 | } 36 | else 37 | { 38 | return Equals(other.Value); 39 | } 40 | } 41 | 42 | public override int GetHashCode() 43 | { 44 | var hashCodeCombiner = HashCodeCombiner.Start(); 45 | hashCodeCombiner.Add(IsContentSet); 46 | hashCodeCombiner.Add(Content); 47 | 48 | return hashCodeCombiner.CombinedHash; 49 | } 50 | 51 | public bool Equals(ContentModelBindingResult other) 52 | { 53 | return 54 | IsContentSet == other.IsContentSet && 55 | object.Equals(Content, other.Content); 56 | } 57 | 58 | public override string ToString() 59 | { 60 | if (IsContentSet) 61 | { 62 | return $"Success '{Content}'"; 63 | } 64 | else 65 | { 66 | return "Failed"; 67 | } 68 | } 69 | 70 | public static bool operator ==(ContentModelBindingResult x, ContentModelBindingResult y) 71 | { 72 | return x.Equals(y); 73 | } 74 | 75 | public static bool operator !=(ContentModelBindingResult x, ContentModelBindingResult y) 76 | { 77 | return !x.Equals(y); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/EnsureTemplateResult.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public struct EnsureTemplateResult 4 | { 5 | public static EnsureTemplateResult Completed(int parameterOffset, bool ignorePrefix) 6 | { 7 | return new EnsureTemplateResult(parameterOffset: parameterOffset, completed: true, ignorePrefix: ignorePrefix); 8 | } 9 | 10 | public static EnsureTemplateResult Continue(int parameterOffset, bool ignorePrefix) 11 | { 12 | return new EnsureTemplateResult(parameterOffset: parameterOffset, completed: false, ignorePrefix: ignorePrefix); 13 | } 14 | 15 | public bool BindingCompleted { get; } 16 | public bool IgnoreModelPrefix { get; } 17 | public int ParameterOffset { get; } 18 | 19 | private EnsureTemplateResult(int parameterOffset, bool completed, bool ignorePrefix) 20 | { 21 | ParameterOffset = parameterOffset; 22 | BindingCompleted = completed; 23 | IgnoreModelPrefix = ignorePrefix; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ModelDictionaryContext.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public class ModelDictionaryContext 4 | { 5 | public object Value { get; } 6 | public ProxyModelMetadata ModelMetadata { get; } 7 | 8 | public ModelDictionaryContext(ProxyModelMetadata modelMetadata, object value) 9 | { 10 | Value = value; 11 | ModelMetadata = modelMetadata; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ModelDictionaryResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Collections.Generic; 3 | 4 | namespace NetCoreStack.Proxy 5 | { 6 | public class ModelDictionaryResult 7 | { 8 | public Dictionary Dictionary { get; } 9 | 10 | public Dictionary Files { get; } 11 | 12 | public ModelDictionaryResult(Dictionary dictionary, Dictionary files = null) 13 | { 14 | Dictionary = dictionary; 15 | Files = files; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ModelResolverResult.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Internal; 2 | using System; 3 | 4 | namespace NetCoreStack.Proxy 5 | { 6 | public struct ModelResolverResult : IEquatable 7 | { 8 | public bool IsValueSet { get; } 9 | 10 | public static ModelResolverResult Failed() 11 | { 12 | return new ModelResolverResult(isValueSet: false); 13 | } 14 | 15 | public static ModelResolverResult Success() 16 | { 17 | return new ModelResolverResult(isValueSet: true); 18 | } 19 | 20 | private ModelResolverResult(bool isValueSet) 21 | { 22 | IsValueSet = isValueSet; 23 | } 24 | 25 | public override bool Equals(object obj) 26 | { 27 | var other = obj as ModelResolverResult?; 28 | if (other == null) 29 | { 30 | return false; 31 | } 32 | else 33 | { 34 | return Equals(other.Value); 35 | } 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | var hashCodeCombiner = HashCodeCombiner.Start(); 41 | hashCodeCombiner.Add(IsValueSet); 42 | return hashCodeCombiner.CombinedHash; 43 | } 44 | 45 | public bool Equals(ModelResolverResult other) 46 | { 47 | return IsValueSet == other.IsValueSet; 48 | } 49 | 50 | public override string ToString() 51 | { 52 | if (IsValueSet) 53 | { 54 | return $"Success"; 55 | } 56 | else 57 | { 58 | return "Failed"; 59 | } 60 | } 61 | 62 | public static bool operator ==(ModelResolverResult x, ModelResolverResult y) 63 | { 64 | return x.Equals(y); 65 | } 66 | 67 | public static bool operator !=(ModelResolverResult x, ModelResolverResult y) 68 | { 69 | return !x.Equals(y); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ProxyDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public class ProxyDescriptor 8 | { 9 | public Type ProxyType { get; } 10 | 11 | public string RegionKey { get; } 12 | 13 | public string Route { get; } 14 | 15 | public IDictionary Methods { get; set; } 16 | 17 | public ProxyDescriptor(Type proxyType, string regionKey, string route) 18 | { 19 | ProxyType = proxyType; 20 | RegionKey = regionKey; 21 | Route = route; 22 | Methods = new Dictionary(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ProxyMethodDescriptor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing.Template; 2 | using NetCoreStack.Contracts; 3 | using NetCoreStack.Proxy.Extensions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Reflection; 9 | using System.Threading.Tasks; 10 | 11 | namespace NetCoreStack.Proxy 12 | { 13 | public class ProxyMethodDescriptor 14 | { 15 | public string MethodMarkerTemplate { get; set; } 16 | 17 | public MethodInfo MethodInfo { get; } 18 | 19 | public Type ReturnType { get; } 20 | 21 | public Type UnderlyingReturnType { get; } 22 | 23 | public HttpMethod HttpMethod { get; set; } 24 | 25 | public TimeSpan? Timeout { get; set; } 26 | public RouteTemplate RouteTemplate { get; set; } 27 | 28 | public ContentType ContentType { get; set; } = ContentType.ModelAware; 29 | 30 | public List Parameters { get; } 31 | 32 | public bool IsVoidReturn { get; } 33 | 34 | public bool IsTaskReturn { get; } 35 | 36 | public bool IsGenericTaskReturn { get; } 37 | 38 | public List TemplateKeys { get; set; } 39 | 40 | public List TemplateParameterKeys { get; set; } 41 | 42 | public List TemplateParts { get; set; } 43 | 44 | public bool HasAnyTemplateParameterKey => TemplateParameterKeys.Any(); 45 | 46 | public Dictionary Headers { get; } 47 | public bool IsByteArrayReturn { get; } 48 | 49 | public ProxyMethodDescriptor(MethodInfo methodInfo) 50 | { 51 | MethodInfo = methodInfo; 52 | ReturnType = methodInfo.ReturnType; 53 | IsVoidReturn = ReturnType == typeof(void); 54 | IsTaskReturn = ReturnType.IsAssignableFrom(typeof(Task)) ? true : false; 55 | IsGenericTaskReturn = ReturnType.IsGenericTask() ? true : false; 56 | Headers = new Dictionary(StringComparer.Ordinal); 57 | Parameters = new List(); 58 | TemplateParts = new List(); 59 | TemplateKeys = new List(); 60 | TemplateParameterKeys = new List(); 61 | 62 | if (IsGenericTaskReturn) 63 | { 64 | UnderlyingReturnType = ReturnType.GetGenericArguments()[0]; 65 | if (typeof(byte[]).IsAssignableFrom(UnderlyingReturnType)) 66 | { 67 | IsByteArrayReturn = true; 68 | } 69 | } 70 | else 71 | { 72 | if (typeof(byte[]).IsAssignableFrom(ReturnType)) 73 | { 74 | IsByteArrayReturn = true; 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ProxyModelMetadata.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using NetCoreStack.Proxy.Internal; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Reflection; 8 | 9 | namespace NetCoreStack.Proxy 10 | { 11 | public class ProxyModelMetadata: IEquatable 12 | { 13 | private int? _hashCode; 14 | private Type _reflectedType; 15 | protected ProxyModelMetadataIdentity Identity { get; } 16 | 17 | public Type ModelType => Identity.ModelType; 18 | 19 | public Type ContainerType => Identity.ContainerType; 20 | 21 | public ProxyModelMetadata ElementType { get; private set; } 22 | 23 | public Type UnderlyingOrModelType { get; private set; } 24 | 25 | public PropertyInfo PropertyInfo { get; private set; } 26 | 27 | public ProxyModelMetadataKind MetadataKind => Identity.MetadataKind; 28 | 29 | public string PropertyName => Identity.Name; 30 | 31 | public bool IsFormFile { get; private set; } 32 | 33 | public bool IsSimpleType { get; private set; } 34 | 35 | public bool IsSystemObject { get; private set; } 36 | 37 | public bool IsComplexType { get; private set; } 38 | 39 | public bool IsNullableValueType { get; private set; } 40 | 41 | public bool IsCollectionType { get; private set; } 42 | 43 | public bool IsEnumerableType { get; private set; } 44 | 45 | public bool IsReferenceType { get; private set; } 46 | 47 | public bool IsReferenceOrNullableType { get; private set; } 48 | 49 | public IList Properties { get; private set; } 50 | 51 | public int PropertiesCount => Properties.Count; 52 | 53 | public ProxyModelMetadata(ProxyModelMetadataIdentity identity, Type reflectedType = null) 54 | { 55 | _reflectedType = reflectedType; 56 | Identity = identity; 57 | Properties = new List(); 58 | InitializeTypeInformation(); 59 | } 60 | 61 | internal ProxyModelMetadata(PropertyInfo propertyInfo, ProxyModelMetadataIdentity identity, Type reflectedType = null) 62 | :this(identity, reflectedType) 63 | { 64 | PropertyInfo = propertyInfo; 65 | } 66 | 67 | public bool IsSimple(Type type) 68 | { 69 | var typeInfo = type.GetTypeInfo(); 70 | 71 | return typeInfo.IsPrimitive || 72 | typeInfo.IsEnum || 73 | type.Equals(typeof(decimal)) || 74 | type.Equals(typeof(string)) || 75 | type.Equals(typeof(DateTime)) || 76 | type.Equals(typeof(Guid)) || 77 | type.Equals(typeof(DateTimeOffset)) || 78 | type.Equals(typeof(TimeSpan)) || 79 | type.Equals(typeof(Uri)); 80 | } 81 | 82 | private void InitializeTypeInformation() 83 | { 84 | var typeInfo = ModelType.GetTypeInfo(); 85 | 86 | IsComplexType = !TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string)); 87 | IsNullableValueType = Nullable.GetUnderlyingType(ModelType) != null; 88 | IsReferenceType = !typeInfo.IsValueType; 89 | IsReferenceOrNullableType = !typeInfo.IsValueType || IsNullableValueType; 90 | UnderlyingOrModelType = Nullable.GetUnderlyingType(ModelType) ?? ModelType; 91 | IsFormFile = typeof(IFormFile).IsAssignableFrom(ModelType); 92 | IsSimpleType = IsSimple(ModelType); 93 | IsSystemObject = ModelType == typeof(object); 94 | 95 | var collectionType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(ICollection<>)); 96 | IsCollectionType = collectionType != null; 97 | 98 | if (ModelType == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(ModelType)) 99 | { 100 | // Do nothing, not Enumerable. 101 | } 102 | else if (ModelType.IsArray) 103 | { 104 | IsEnumerableType = true; 105 | var elementType = ModelType.GetElementType(); 106 | 107 | PropertyInfo propertyInfo = ContainerType?.GetProperty(PropertyName); 108 | if (propertyInfo != null) 109 | { 110 | ElementType = new ProxyModelMetadata(propertyInfo, 111 | ProxyModelMetadataIdentity.ForProperty(elementType, propertyInfo.Name, ModelType), 112 | reflectedType: _reflectedType); 113 | } 114 | 115 | IsFormFile = typeof(IFormFile).IsAssignableFrom(elementType); 116 | } 117 | else 118 | { 119 | IsEnumerableType = true; 120 | 121 | var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(IEnumerable<>)); 122 | var elementType = enumerableType?.GenericTypeArguments[0]; 123 | 124 | if (elementType != null) 125 | { 126 | IsFormFile = typeof(IFormFile).IsAssignableFrom(elementType); 127 | } 128 | 129 | if (elementType == null) 130 | { 131 | // ModelType implements IEnumerable but not IEnumerable. 132 | elementType = typeof(object); 133 | } 134 | 135 | PropertyInfo propertyInfo = ContainerType?.GetProperty(PropertyName); 136 | if (propertyInfo != null) 137 | { 138 | ElementType = new ProxyModelMetadata(propertyInfo, 139 | ProxyModelMetadataIdentity.ForProperty(elementType, propertyInfo.Name, ModelType), 140 | reflectedType: _reflectedType); 141 | } 142 | } 143 | 144 | if (IsNullableValueType) 145 | { 146 | var elementType = Nullable.GetUnderlyingType(ModelType); 147 | PropertyInfo pInfo = ContainerType?.GetProperty(PropertyName); 148 | if (pInfo != null) 149 | { 150 | ElementType = new ProxyModelMetadata(pInfo, 151 | ProxyModelMetadataIdentity.ForProperty(elementType, pInfo.Name, ModelType), 152 | reflectedType: _reflectedType); 153 | } 154 | } 155 | 156 | if (IsComplexType && IsReferenceOrNullableType && 157 | (!IsEnumerableType && !IsCollectionType) && 158 | ModelType.Name != typeof(object).Name) 159 | { 160 | PropertyInfo[] properties = ModelType.GetProperties(); 161 | List metadataList = new List(); 162 | foreach (var prop in properties) 163 | { 164 | if (_reflectedType == null) 165 | { 166 | _reflectedType = prop.ReflectedType; 167 | } 168 | 169 | if (_reflectedType == prop.PropertyType) 170 | { 171 | // self reference loop 172 | continue; 173 | } 174 | 175 | if (ModelType == prop.PropertyType) 176 | { 177 | // Parent child ref - self reference loop 178 | continue; 179 | } 180 | 181 | metadataList.Add(new ProxyModelMetadata(prop, 182 | ProxyModelMetadataIdentity.ForProperty(prop.PropertyType, prop.Name, ModelType), 183 | reflectedType: _reflectedType)); 184 | } 185 | 186 | Properties = metadataList; 187 | } 188 | } 189 | 190 | public bool Equals(ProxyModelMetadata other) 191 | { 192 | if (object.ReferenceEquals(this, other)) 193 | { 194 | return true; 195 | } 196 | 197 | if (other == null) 198 | { 199 | return false; 200 | } 201 | else 202 | { 203 | return Identity.Equals(other.Identity); 204 | } 205 | } 206 | 207 | public override bool Equals(object obj) 208 | { 209 | return Equals(obj as ProxyModelMetadata); 210 | } 211 | 212 | public override int GetHashCode() 213 | { 214 | if (_hashCode == null) 215 | { 216 | _hashCode = Identity.GetHashCode(); 217 | } 218 | 219 | return _hashCode.Value; 220 | } 221 | 222 | public override string ToString() 223 | { 224 | var name = PropertyName; 225 | if (ContainerType != null) 226 | { 227 | name = ContainerType.Name + " - " + name; 228 | } 229 | 230 | return name; 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ProxyModelMetadataIdentity.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Internal; 2 | using System; 3 | using System.Reflection; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public struct ProxyModelMetadataIdentity : IEquatable 8 | { 9 | public static ProxyModelMetadataIdentity ForType(Type modelType) 10 | { 11 | if (modelType == null) 12 | { 13 | throw new ArgumentNullException(nameof(modelType)); 14 | } 15 | 16 | return new ProxyModelMetadataIdentity() 17 | { 18 | ModelType = modelType, 19 | }; 20 | } 21 | 22 | public static ProxyModelMetadataIdentity ForProperty( 23 | Type modelType, 24 | string name, 25 | Type containerType) 26 | { 27 | if (modelType == null) 28 | { 29 | throw new ArgumentNullException(nameof(modelType)); 30 | } 31 | 32 | if (containerType == null) 33 | { 34 | throw new ArgumentNullException(nameof(containerType)); 35 | } 36 | 37 | if (string.IsNullOrEmpty(name)) 38 | { 39 | throw new ArgumentException(nameof(name)); 40 | } 41 | 42 | return new ProxyModelMetadataIdentity() 43 | { 44 | ModelType = modelType, 45 | Name = name, 46 | ContainerType = containerType, 47 | }; 48 | } 49 | 50 | public static ProxyModelMetadataIdentity ForParameter(ParameterInfo parameter) 51 | { 52 | if (parameter == null) 53 | { 54 | throw new ArgumentNullException(nameof(parameter)); 55 | } 56 | 57 | return new ProxyModelMetadataIdentity() 58 | { 59 | Name = parameter.Name, 60 | ModelType = parameter.ParameterType, 61 | ParameterInfo = parameter, 62 | }; 63 | } 64 | 65 | public Type ContainerType { get; private set; } 66 | 67 | public Type ModelType { get; private set; } 68 | 69 | public ProxyModelMetadataKind MetadataKind 70 | { 71 | get 72 | { 73 | if (ParameterInfo != null) 74 | { 75 | return ProxyModelMetadataKind.Parameter; 76 | } 77 | else if (ContainerType != null && Name != null) 78 | { 79 | return ProxyModelMetadataKind.Property; 80 | } 81 | else 82 | { 83 | return ProxyModelMetadataKind.Type; 84 | } 85 | } 86 | } 87 | 88 | public string Name { get; private set; } 89 | 90 | public ParameterInfo ParameterInfo { get; private set; } 91 | 92 | public bool Equals(ProxyModelMetadataIdentity other) 93 | { 94 | return 95 | ContainerType == other.ContainerType && 96 | ModelType == other.ModelType && 97 | Name == other.Name && 98 | ParameterInfo == other.ParameterInfo; 99 | } 100 | 101 | public override bool Equals(object obj) 102 | { 103 | var other = obj as ProxyModelMetadataIdentity?; 104 | return other.HasValue && Equals(other.Value); 105 | } 106 | 107 | public override int GetHashCode() 108 | { 109 | var hash = new HashCodeCombiner(); 110 | hash.Add(ContainerType); 111 | hash.Add(ModelType); 112 | hash.Add(Name, StringComparer.Ordinal); 113 | hash.Add(ParameterInfo); 114 | return hash; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ProxyModelMetadataKind.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreStack.Proxy 2 | { 3 | public enum ProxyModelMetadataKind 4 | { 5 | Type, 6 | Property, 7 | Parameter, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/RequestContext.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Proxy.Extensions; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Net.Http; 5 | 6 | namespace NetCoreStack.Proxy 7 | { 8 | public class RequestContext : IDisposable 9 | { 10 | public ProxyMethodDescriptor MethodDescriptor { get; } 11 | public HttpRequestMessage Request { get; } 12 | public string RegionKey { get; } 13 | public TimeSpan? Timeout { get; } 14 | 15 | public RequestContext(HttpRequestMessage request, 16 | ProxyMethodDescriptor methodDescriptor, 17 | string regionKey, 18 | TimeSpan? timeout = null) 19 | { 20 | if (request == null) 21 | { 22 | throw new ArgumentNullException(nameof(request)); 23 | } 24 | 25 | if (methodDescriptor == null) 26 | { 27 | throw new ArgumentNullException(nameof(methodDescriptor)); 28 | } 29 | 30 | if (!regionKey.HasValue()) 31 | { 32 | throw new ArgumentNullException(nameof(regionKey)); 33 | } 34 | 35 | Request = request; 36 | MethodDescriptor = methodDescriptor; 37 | RegionKey = regionKey; 38 | Timeout = timeout; 39 | } 40 | 41 | public void Dispose() 42 | { 43 | Debug.WriteLine("===Request Context disposing"); 44 | Request.Dispose(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/RequestDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public class RequestDescriptor 8 | { 9 | public MethodInfo TargetMethod { get; set; } 10 | public Type ProxyType { get; } 11 | public object[] Args { get; } 12 | public string ClientIp { get; } 13 | public string UserAgent { get; } 14 | public string Query { get; } 15 | public CultureInfo Culture { get; set; } 16 | 17 | public RequestDescriptor(MethodInfo targetMethod, 18 | Type proxyType, 19 | string clientIp, 20 | string userAgent, 21 | string query, 22 | CultureInfo culture, 23 | object[] args) 24 | { 25 | TargetMethod = targetMethod; 26 | ProxyType = proxyType; 27 | ClientIp = clientIp; 28 | UserAgent = userAgent; 29 | Query = query; 30 | Culture = culture; 31 | Args = args; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NetCoreStack.Proxy/Types/ResponseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net.Http; 4 | 5 | namespace NetCoreStack.Proxy 6 | { 7 | public class ResponseContext: IDisposable 8 | { 9 | public RequestContext RequestContext { get; } 10 | public string ResultContent { get; set; } 11 | public HttpResponseMessage Response { get; set; } 12 | public object Value { get; set; } 13 | 14 | public ResponseContext(HttpResponseMessage response, 15 | RequestContext requestContext, 16 | Type genericReturnType = null) 17 | { 18 | Response = response ?? throw new ArgumentNullException(nameof(response)); 19 | RequestContext = requestContext ?? throw new ArgumentNullException(nameof(requestContext)); 20 | } 21 | 22 | public void Dispose() 23 | { 24 | Debug.WriteLine("===Response Context disposing"); 25 | RequestContext.Dispose(); 26 | Response.Dispose(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/Controllers/GuidelineController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using NetCoreStack.Contracts; 5 | using NetCoreStack.Proxy.Test.Contracts; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Threading.Tasks; 11 | 12 | namespace NetCoreStack.Proxy.Mvc.Hosting.Controllers 13 | { 14 | [Route("api/[controller]")] 15 | public class GuidelineController : Controller, IGuidelineApi 16 | { 17 | private readonly IHostingEnvironment _hostingEnvironment; 18 | private readonly ILoggerFactory _loggerFactory; 19 | private readonly ILogger _logger; 20 | 21 | public GuidelineController(IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory) 22 | { 23 | _hostingEnvironment = hostingEnvironment; 24 | _loggerFactory = loggerFactory; 25 | _logger = _loggerFactory.CreateLogger(); 26 | } 27 | 28 | [HttpGet(nameof(GetComplexType))] 29 | public async Task GetComplexType(ComplexTypeModel model) 30 | { 31 | await Task.CompletedTask; 32 | var query = HttpContext.Request.QueryString.HasValue ? HttpContext.Request.QueryString.Value : string.Empty; 33 | _logger.LogWarning(JsonConvert.SerializeObject(model)); 34 | } 35 | 36 | [HttpGet(nameof(TaskOperation))] 37 | public async Task TaskOperation() 38 | { 39 | await Task.CompletedTask; 40 | } 41 | 42 | [HttpGet(nameof(PrimitiveReturn))] 43 | public async Task PrimitiveReturn(int i, string s, long l, DateTime dt) 44 | { 45 | await Task.CompletedTask; 46 | var query = HttpContext.Request.QueryString.HasValue ? HttpContext.Request.QueryString.Value : string.Empty; 47 | _logger.LogWarning(JsonConvert.SerializeObject(new { i, s, l, dt, query })); 48 | return 17; 49 | } 50 | 51 | [HttpGet(nameof(GetWithComplexReferenceType))] 52 | public async Task GetWithComplexReferenceType(CollectionRequest request) 53 | { 54 | await Task.CompletedTask; 55 | var query = HttpContext.Request.QueryString.HasValue ? HttpContext.Request.QueryString.Value : string.Empty; 56 | _logger.LogWarning(JsonConvert.SerializeObject(new { request, query })); 57 | } 58 | 59 | [HttpGet(nameof(GetEnumerableModels))] 60 | public Task> GetEnumerableModels() 61 | { 62 | // HttpLive 63 | return null; 64 | } 65 | 66 | [HttpGet(nameof(GetCollectionStreamTask))] 67 | public Task> GetCollectionStreamTask() 68 | { 69 | // HttpLive 70 | return null; 71 | } 72 | 73 | [HttpPost(nameof(TaskComplexTypeModel))] 74 | public async Task TaskComplexTypeModel([FromBody]ComplexTypeModel model) 75 | { 76 | await Task.CompletedTask; 77 | _logger.LogWarning(JsonConvert.SerializeObject(model)); 78 | } 79 | 80 | [HttpPost(nameof(TaskSingleFileModel))] 81 | public async Task TaskSingleFileModel(SingleFileModel model) 82 | { 83 | await Task.CompletedTask; 84 | 85 | var name = model.File.Name; 86 | var fileName = model.File.FileName; 87 | var length = model.File.Length; 88 | 89 | using (var ms = new MemoryStream()) 90 | { 91 | model.File.CopyTo(ms); 92 | System.IO.File.WriteAllBytes(Path.Combine(_hostingEnvironment.ContentRootPath, fileName), ms.ToArray()); 93 | } 94 | 95 | _logger.LogWarning(JsonConvert.SerializeObject(new { name, fileName, length })); 96 | } 97 | 98 | [HttpPost(nameof(TaskActionBarMultipartFormData))] 99 | public Task TaskActionBarMultipartFormData(Bar model) 100 | { 101 | throw new NotImplementedException(); 102 | } 103 | 104 | [HttpPut("kv")] 105 | public async Task CreateOrUpdateKey(string key, Bar body) 106 | { 107 | await Task.CompletedTask; 108 | _logger.LogWarning(JsonConvert.SerializeObject(new { key, body })); 109 | return true; 110 | } 111 | 112 | [HttpPut("kv/nobody")] 113 | public Task CreateOrUpdateKey(string key) 114 | { 115 | throw new NotImplementedException(); 116 | } 117 | 118 | [HttpPut(nameof(TaskKeyAndSingleFileModel))] 119 | public Task TaskKeyAndSingleFileModel(string key, SingleFileModel model) 120 | { 121 | throw new NotImplementedException(); 122 | } 123 | 124 | [HttpPut(nameof(TaskKeySingleFileAndBarModel))] 125 | public Task TaskKeySingleFileAndBarModel(string key, SingleFileModel model, Bar bar) 126 | { 127 | throw new NotImplementedException(); 128 | } 129 | 130 | [HttpDelete(nameof(TaskActionDelete))] 131 | public async Task TaskActionDelete(long id) 132 | { 133 | await Task.CompletedTask; 134 | _logger.LogWarning(JsonConvert.SerializeObject(new { id })); 135 | } 136 | 137 | public Task TaskKeyAndSingleFileAndPropsModel(string key, SingleFileAndPropsModel model) 138 | { 139 | throw new NotImplementedException(); 140 | } 141 | 142 | public Task TaskEnumerableFileModel(EnumerableFileModel model) 143 | { 144 | throw new NotImplementedException(); 145 | } 146 | 147 | public Task TaskKeyAndEnumerableFileModel(string key, EnumerableFileModel model) 148 | { 149 | throw new NotImplementedException(); 150 | } 151 | 152 | public Task TaskActionBarSimpleXml(BarSimple model) 153 | { 154 | throw new NotImplementedException(); 155 | } 156 | 157 | public Task UploadAsync(FileProxyUploadContext context) 158 | { 159 | throw new NotImplementedException(); 160 | } 161 | 162 | public Task GetFileAsync(string id, string fileName) 163 | { 164 | throw new NotImplementedException(); 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/Controllers/SelfController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using NetCoreStack.Contracts; 5 | using NetCoreStack.Proxy.Test.Contracts; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Threading.Tasks; 11 | 12 | namespace NetCoreStack.Proxy.Mvc.Hosting.Controllers 13 | { 14 | [Route("api/[controller]")] 15 | public class SelfController : Controller, ISelfApi 16 | { 17 | private readonly IHostingEnvironment _hostingEnvironment; 18 | private readonly ILoggerFactory _loggerFactory; 19 | private readonly ILogger _logger; 20 | 21 | public SelfController(IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory) 22 | { 23 | _hostingEnvironment = hostingEnvironment; 24 | _loggerFactory = loggerFactory; 25 | _logger = _loggerFactory.CreateLogger(); 26 | } 27 | 28 | [HttpPost(nameof(Operation))] 29 | public Task Operation([FromBody]Baz model) 30 | { 31 | _logger.LogWarning(JsonConvert.SerializeObject(model)); 32 | return Task.CompletedTask; 33 | } 34 | 35 | [HttpPost(nameof(OperationCollection))] 36 | public Task OperationCollection([FromBody]Baz2 model) 37 | { 38 | _logger.LogWarning(JsonConvert.SerializeObject(model)); 39 | return Task.CompletedTask; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/NetCoreStack.Proxy.Mvc.Hosting.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Logging; 4 | using System.IO; 5 | 6 | namespace NetCoreStack.Proxy.Mvc.Hosting 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | var webHost = new WebHostBuilder() 13 | .UseKestrel() 14 | .UseContentRoot(Directory.GetCurrentDirectory()) 15 | .ConfigureAppConfiguration((hostingContext, config) => 16 | { 17 | var env = hostingContext.HostingEnvironment; 18 | config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 19 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 20 | config.AddEnvironmentVariables(); 21 | }) 22 | .ConfigureLogging((hostingContext, logging) => 23 | { 24 | logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 25 | logging.AddConsole(); 26 | logging.AddDebug(); 27 | }) 28 | .UseStartup() 29 | .Build(); 30 | 31 | webHost.Run(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5003/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/values", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "NetCoreStack.Proxy.Mvc.Hosting": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:5003/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/SampleĞüişçWord.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetCoreStack/Proxy/b3aadbe50a4ad99acc9133eca894853607761d60/test/NetCoreStack.Proxy.Mvc.Hosting/SampleĞüişçWord.docx -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Swashbuckle.AspNetCore.Swagger; 6 | 7 | namespace NetCoreStack.Proxy.Mvc.Hosting 8 | { 9 | public class Startup 10 | { 11 | public Startup(IConfiguration configuration) 12 | { 13 | Configuration = configuration; 14 | } 15 | 16 | public IConfiguration Configuration { get; } 17 | 18 | public void ConfigureServices(IServiceCollection services) 19 | { 20 | services.AddMvc(); 21 | 22 | services.AddSwaggerGen(c => 23 | { 24 | c.SwaggerDoc("v1", new Info { Title = "API", Version = "v1" }); 25 | }); 26 | } 27 | 28 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 29 | { 30 | if (env.IsDevelopment()) 31 | { 32 | app.UseDeveloperExceptionPage(); 33 | } 34 | 35 | app.UseSwagger(); 36 | 37 | app.UseSwaggerUI(c => 38 | { 39 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1"); 40 | }); 41 | 42 | app.UseMvc(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Mvc.Hosting/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/Agent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetCoreStack.Proxy.Test.Contracts 4 | { 5 | /// 6 | /// AgentCheck represents a check known to the agent 7 | /// 8 | public class AgentCheck 9 | { 10 | public string Node { get; set; } 11 | public string CheckID { get; set; } 12 | public string Name { get; set; } 13 | public string Status { get; set; } 14 | public string Notes { get; set; } 15 | public string Output { get; set; } 16 | public string ServiceID { get; set; } 17 | public string ServiceName { get; set; } 18 | } 19 | 20 | /// 21 | /// AgentMember represents a cluster member known to the agent 22 | /// 23 | public class AgentMember 24 | { 25 | public string Name { get; set; } 26 | public string Addr { get; set; } 27 | public ushort Port { get; set; } 28 | public Dictionary Tags { get; set; } 29 | public int Status { get; set; } 30 | public byte ProtocolMin { get; set; } 31 | public byte ProtocolMax { get; set; } 32 | public byte ProtocolCur { get; set; } 33 | public byte DelegateMin { get; set; } 34 | public byte DelegateMax { get; set; } 35 | public byte DelegateCur { get; set; } 36 | 37 | public AgentMember() 38 | { 39 | Tags = new Dictionary(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/Attachment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public class Attachment : EntityAuditInsert 7 | { 8 | public string Name { get; set; } 9 | public string Content { get; set; } 10 | } 11 | 12 | public class SubtitleAttachmentDto 13 | { 14 | 15 | public List SubtitleAttachmentList { get; set; } 16 | } 17 | 18 | public class SubtitleAttachment : EntityAuditUpdate 19 | { 20 | public long CategoryId { get; set; } 21 | 22 | public Category Category { get; set; } 23 | 24 | public long SeasonId { get; set; } 25 | 26 | public Attachment Attachment { get; set; } 27 | } 28 | 29 | public partial class Category : EntityAuditUpdate 30 | { 31 | 32 | public string CatName { get; set; } 33 | 34 | public long? DomainID { get; set; } 35 | 36 | public long? ParentCategoryID { get; set; } 37 | public virtual Category ParentCategory { get; set; } 38 | 39 | public DateTime? ReleaseDate { get; set; } 40 | 41 | public DateTime? FinalDate { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/ContextBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Features; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public static class ContextBaseExtensions 7 | { 8 | public readonly static string ClientUserAgentHeader = "User-Agent"; 9 | 10 | public static string GetIp(this HttpContext context) 11 | { 12 | return context?.Features?.Get()?.RemoteIpAddress?.ToString(); 13 | } 14 | 15 | public static string GetUserAgent(this HttpRequest request) 16 | { 17 | var userAgent = request.Headers[ClientUserAgentHeader].ToString(); 18 | if (!string.IsNullOrEmpty(userAgent)) 19 | { 20 | return userAgent; 21 | } 22 | 23 | return string.Empty; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/EntityAuditInsert.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Data.Contracts; 2 | using System; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public class EntityAuditInsert : EntityInsertActive 7 | { 8 | public long? CreatedByUserUserGroupDomainId { get; set; } 9 | } 10 | 11 | public class EntityInsertActive : EntityInsertBase 12 | { 13 | public bool IsActive { get; set; } 14 | 15 | public EntityInsertActive() 16 | { 17 | IsActive = true; 18 | } 19 | } 20 | 21 | public class EntityAuditUpdate : EntityAuditInsert 22 | { 23 | public long? UpdatedByUserUserGroupDomainId { get; set; } 24 | public DateTime? UpdatedDate { get; set; } 25 | } 26 | 27 | public class EntityInsertBase : EntityIdentitySql 28 | { 29 | public DateTime CreatedDate { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/FileProxyUploadContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Collections.Generic; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public class FileProxyUploadContext 7 | { 8 | public string Directory { get; set; } 9 | public IEnumerable Files { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/IAttachmentApi.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using NetCoreStack.Contracts; 3 | using NetCoreStack.Mvc; 4 | using System.Threading.Tasks; 5 | 6 | namespace NetCoreStack.Proxy.Test.Contracts 7 | { 8 | [NetCoreStack.Contracts.ApiRoute("api/[controller]", regionKey: "Main")] 9 | public interface IAttachmentApi : IApiContract 10 | { 11 | [HttpGetMarker] 12 | Task> GetFileMetaData([FromHeader]long attachmentId); 13 | 14 | [HttpPutMarker] 15 | Task> PutSubtitleAttachmentList([FromBody] SubtitleAttachmentDto attachments); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/IConsulApi.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace NetCoreStack.Proxy.Test.Contracts 6 | { 7 | /// 8 | /// Consul API Interface [https://www.consul.io/] 9 | /// 10 | [ApiRoute("v1", regionKey: "Consul")] 11 | public interface IConsulApi : IApiContract 12 | { 13 | /// 14 | /// Sample remote API interface method definiton. 15 | /// 16 | /// 17 | [HttpGetMarker(Template = "agent/members")] 18 | Task> GetMembersAsync(); 19 | 20 | [HttpGetMarker(Template = "health/node/{id}")] 21 | Task> CheckNode(string node); 22 | 23 | [HttpPutMarker(Template = "kv/{key}")] 24 | Task CreateOrUpdateKey(string key, string body); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/IFileProxyApi.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using System.Threading.Tasks; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | [ApiRoute("/", regionKey: "FS")] 7 | public interface IFileProxyApi : IApiContract 8 | { 9 | [HttpGetMarker(Template = "proxy-fs/{id}/{filename}")] 10 | Task GetFileAsync(string id, string filename); 11 | 12 | [HttpPostMarker(Template = "upload")] 13 | Task UploadAsync(FileProxyUploadContext context); 14 | } 15 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/IGuidelineApi.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace NetCoreStack.Proxy.Test.Contracts 7 | { 8 | [ApiRoute("api/[controller]", regionKey: "Main")] 9 | public interface IGuidelineApi : IApiContract 10 | { 11 | [HttpHeaders("X-Method-Header: Some Value")] 12 | Task TaskOperation(); 13 | 14 | Task GetComplexType(ComplexTypeModel model); 15 | 16 | Task PrimitiveReturn(int i, string s, long l, DateTime dt); 17 | 18 | Task GetWithComplexReferenceType(CollectionRequest request); 19 | 20 | Task> GetEnumerableModels(); 21 | 22 | Task> GetCollectionStreamTask(); 23 | 24 | /// 25 | /// Default Content-Type is ModelAware if it is not contain FormFile type property that will be JSON body 26 | /// 27 | /// 28 | /// 29 | [HttpPostMarker] 30 | Task TaskComplexTypeModel(ComplexTypeModel model); 31 | 32 | [HttpPostMarker(ContentType = ContentType.MultipartFormData)] 33 | Task TaskActionBarMultipartFormData(Bar model); 34 | 35 | [HttpPostMarker(ContentType = ContentType.Xml)] 36 | Task TaskActionBarSimpleXml(BarSimple model); 37 | 38 | [HttpPostMarker] 39 | Task TaskSingleFileModel(SingleFileModel model); 40 | 41 | [HttpPostMarker] 42 | Task TaskEnumerableFileModel(EnumerableFileModel model); 43 | 44 | [HttpPutMarker(Template = "TaskKeyAndSingleFileModel/{key}")] 45 | Task TaskKeyAndSingleFileModel(string key, SingleFileModel model); 46 | 47 | [HttpPutMarker(Template = "TaskKeyAndMultipleFileModel/{key}")] 48 | Task TaskKeyAndEnumerableFileModel(string key, EnumerableFileModel model); 49 | 50 | [HttpPutMarker(Template = "TaskKeyAndSingleFileAndPropsModel/{key}")] 51 | Task TaskKeyAndSingleFileAndPropsModel(string key, SingleFileAndPropsModel model); 52 | 53 | [HttpPutMarker(Template = "TaskKeySingleFileAndBarModel/{key}")] 54 | Task TaskKeySingleFileAndBarModel(string key, SingleFileModel model, Bar bar); 55 | 56 | /// 57 | /// Template and parameter usage, key parameter will be part of the request Url and extracting it as api/guideline/kv/ 58 | /// 59 | /// 60 | /// 61 | /// 62 | [HttpPutMarker(Template = "kv/{key}")] 63 | Task CreateOrUpdateKey(string key, Bar body); 64 | 65 | [HttpPutMarker(Template = "kv/nobody/{key}")] 66 | Task CreateOrUpdateKey(string key); 67 | 68 | [HttpDeleteMarker] 69 | Task TaskActionDelete(long id); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/ISelfApi.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using System.Threading.Tasks; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | [ApiRoute("api/[controller]", regionKey: "Main")] 7 | public interface ISelfApi : IApiContract 8 | { 9 | [HttpPostMarker] 10 | Task Operation(Baz model); 11 | 12 | [HttpPostMarker] 13 | Task OperationCollection(Baz2 model); 14 | } 15 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/ISmsApi.cs: -------------------------------------------------------------------------------- 1 | using NetCoreStack.Contracts; 2 | using NetCoreStack.Mvc; 3 | using System.Threading.Tasks; 4 | 5 | namespace NetCoreStack.Proxy.Test.Contracts 6 | { 7 | [ApiRoute("api/[controller]", "Integrations")] 8 | public interface ISmsApi : IApiContract 9 | { 10 | Task> Send(string telephone, string content, bool isOtp = false, int? minutesTimeout = null); 11 | } 12 | 13 | public class SmsResult 14 | { 15 | public bool Successfull { get; set; } 16 | public string MessageId { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/NetCoreStack.Proxy.Test.Contracts.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/SampleModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public class SampleModel 7 | { 8 | public string String { get; set; } 9 | public int Number { get; set; } 10 | public bool Boolean { get; set; } 11 | public int[] Array { get; set; } 12 | public IEnumerable Enumerable { get; set; } 13 | public DateTime Date { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/SolutionPathUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace NetCoreStack.Proxy.Test.Contracts 6 | { 7 | public static class SolutionPathUtility 8 | { 9 | private const string SolutionName = "NetCoreStack.Proxy.sln"; 10 | 11 | /// 12 | /// Gets the full path to the project. 13 | /// 14 | /// 15 | /// The parent directory of the project. 16 | /// e.g. samples, test, or test/Websites 17 | /// 18 | /// The project's assembly. 19 | /// The full path to the project. 20 | public static string GetProjectPath(string solutionRelativePath, Assembly assembly) 21 | { 22 | var projectName = assembly.GetName().Name; 23 | var applicationBasePath = AppDomain.CurrentDomain.BaseDirectory; 24 | 25 | var directoryInfo = new DirectoryInfo(applicationBasePath); 26 | do 27 | { 28 | var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, SolutionName)); 29 | if (solutionFileInfo.Exists) 30 | { 31 | return Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath, projectName)); 32 | } 33 | 34 | directoryInfo = directoryInfo.Parent; 35 | } 36 | while (directoryInfo.Parent != null); 37 | 38 | throw new Exception($"Solution root could not be located using application root {applicationBasePath}."); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/SomeFormComplexViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Collections.Generic; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public class SomeFormComplexViewModel 7 | { 8 | public string Name { get; set; } 9 | 10 | public IEnumerable Tags { get; set; } 11 | 12 | public ICollection SomeCollection { get; set; } 13 | 14 | public IFormFile Files { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/SomeFormFileCollectionViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Collections.Generic; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public class SomeFormFileCollectionViewModel 7 | { 8 | public string Name { get; set; } 9 | public IEnumerable Files { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/SomeFormFileViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace NetCoreStack.Proxy.Test.Contracts 4 | { 5 | public class SomeFormFileViewModel 6 | { 7 | public string Name { get; set; } 8 | public IFormFile Files { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/TypesModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using NetCoreStack.Contracts; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace NetCoreStack.Proxy.Test.Contracts 8 | { 9 | public class TypesModel 10 | { 11 | public bool Bool { get; set; } 12 | public byte Byte { get; set; } 13 | public sbyte SByte { get; set; } 14 | public char Char { get; set; } 15 | public decimal Decimal { get; set; } 16 | public double Double { get; set; } 17 | public float Float { get; set; } 18 | public int Int { get; set; } 19 | public uint UInt { get; set; } 20 | public long Long { get; set; } 21 | public ulong ULong { get; set; } 22 | public object Object { get; set; } 23 | public short Short { get; set; } 24 | public ushort UShort { get; set; } 25 | public string String { get; set; } 26 | } 27 | 28 | public class ComplexTypeModel : TypesModel 29 | { 30 | public int[] IntArray { get; set; } 31 | public decimal? DecimalNullable { get; set; } 32 | public IEnumerable IEnumerable { get; set; } 33 | public ICollection ICollection { get; set; } 34 | public Guid Guid { get; set; } 35 | public Uri Uri { get; set; } 36 | public TimeSpan TimeSpan { get; set; } 37 | public DateTime DateTime { get; set; } 38 | public DateTimeOffset DateTimeOffset { get; set; } 39 | public Foo Foo { get; set; } 40 | public Bar Bar { get; set; } 41 | } 42 | 43 | public class ComplexTypeModel2 44 | { 45 | public Bar Bar { get; set; } 46 | } 47 | 48 | public class ComplexTypeModel3 49 | { 50 | public string String { get; set; } 51 | public Foo Foo { get; set; } 52 | public Bar Bar { get; set; } 53 | } 54 | 55 | public enum SomeEnum 56 | { 57 | Value0 = 0, 58 | Value1 = 1, 59 | Value2 = 2, 60 | Value4 = 4 61 | } 62 | 63 | public class Foo 64 | { 65 | public string String { get; set; } 66 | public IEnumerable IEnumerableInt { get; set; } 67 | } 68 | 69 | public class FooColumns 70 | { 71 | public string String { get; set; } 72 | public List IEnumerableColumns { get; set; } 73 | } 74 | 75 | public class Bar 76 | { 77 | public string String { get; set; } 78 | public int someint { get; set; } 79 | public SomeEnum SomeEnum { get; set; } 80 | public Foo Foo { get; set; } 81 | } 82 | 83 | public class BarSimple 84 | { 85 | public string String { get; set; } 86 | public int someint { get; set; } 87 | public SomeEnum SomeEnum { get; set; } 88 | } 89 | 90 | public class ObjectModel 91 | { 92 | public object Object{ get; set; } 93 | } 94 | 95 | public class NullableObjectModel 96 | { 97 | public int? IntNullable { get; set; } 98 | public decimal? DecimalNullable { get; set; } 99 | public DateTime? DateTimeNullable { get; set; } 100 | } 101 | 102 | public class PureEnumerable 103 | { 104 | public IEnumerable IEnumerable { get; set; } 105 | } 106 | 107 | public class PureCollection 108 | { 109 | public ICollection ICollection { get; set; } 110 | } 111 | 112 | public class StringEnumerable 113 | { 114 | public IEnumerable IEnumerableString { get; set; } 115 | } 116 | 117 | public class UriEnumerable 118 | { 119 | public IEnumerable IEnumerableUri { get; set; } 120 | } 121 | 122 | public class SingleFileModel 123 | { 124 | public IFormFile File { get; set; } 125 | } 126 | 127 | public class SingleFileAndPropsModel 128 | { 129 | public string String { get; set; } 130 | public int Int { get; set; } 131 | public IFormFile File { get; set; } 132 | } 133 | 134 | public class EnumerableFileModel 135 | { 136 | public IEnumerable Files { get; set; } 137 | } 138 | 139 | public class InnerFileModel 140 | { 141 | public IEnumerable Files { get; set; } 142 | } 143 | 144 | public class FileModel 145 | { 146 | public InnerFileModel InnerFileModel { get; set; } 147 | } 148 | 149 | public class Baz 150 | { 151 | public string String { get; set; } 152 | public Self Self { get; set; } 153 | } 154 | 155 | public class Self 156 | { 157 | public int Int { get; set; } 158 | public Baz Baz { get; set; } 159 | } 160 | 161 | public class Baz2 162 | { 163 | public string String { get; set; } 164 | public IEnumerable SelfCollection { get; set; } 165 | } 166 | 167 | public class SelfCollection 168 | { 169 | public string String { get; set; } 170 | public Baz2 Baz2 { get; set; } 171 | } 172 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Test.Contracts/TypesModelHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace NetCoreStack.Proxy.Test.Contracts 5 | { 6 | public static class TypesModelHelper 7 | { 8 | public static ComplexTypeModel GetComplexTypeModel() 9 | { 10 | var dateTime = DateTime.Parse("1/1/2018 9:00:00 AM", CultureInfo.InvariantCulture); 11 | var dateTimeOffset = new DateTimeOffset(dateTime); 12 | var guid = Guid.Parse("5cb98c19-7c0c-4661-bd9a-a2042b3bcb0d"); 13 | ComplexTypeModel model = new ComplexTypeModel 14 | { 15 | Bar = new Bar 16 | { 17 | String = "Bar string value!", 18 | SomeEnum = SomeEnum.Value0, 19 | someint = 6, 20 | Foo = new Foo { IEnumerableInt = new[] { 2, 3 }, String = "Foo inner str value!" } 21 | }, 22 | Bool = true, 23 | Foo = new Foo { IEnumerableInt = new[] { 4, 5 }, String = "Foo string value!" }, 24 | Byte = (byte)'A', // 65 25 | Char = 'c', 26 | DateTime = dateTime, 27 | DateTimeOffset = dateTimeOffset, 28 | Decimal = 300.5m, // 300.5 29 | DecimalNullable = 100.5m, 30 | Double = 1.7E+3, // 1700 31 | Float = 4.5f, // 4.5 32 | Guid = guid, 33 | IEnumerable = new[] { "value1", "value2", "value3" }, 34 | ICollection = new[] { 2, 4, 6, 8 }, 35 | Int = int.MaxValue, 36 | IntArray = new int[] { int.MaxValue, int.MinValue }, 37 | Long = 0x100000000, // 4294967296 38 | Object = new { a = 1, b = true, c = "str" }, 39 | SByte = -102, 40 | Short = 0x040A, // 1034 41 | String = "string value!", 42 | TimeSpan = new TimeSpan(4, 15, 30), 43 | UInt = 0xB2D05E00, // 3000000000 44 | ULong = 0x0001D8e864DD, // 7934076125 45 | Uri = new Uri("http://localhost:5003"), 46 | UShort = 0xFE0A // 65034 47 | }; 48 | 49 | return model; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/CustomProxyContextFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using NetCoreStack.Proxy.Internal; 3 | using NetCoreStack.Proxy.Test.Contracts; 4 | 5 | namespace NetCoreStack.Proxy.Tests 6 | { 7 | public class CustomProxyContextFilter : IProxyContextFilter 8 | { 9 | private readonly IHttpContextAccessor contextAccessor; 10 | 11 | public CustomProxyContextFilter(IHttpContextAccessor contextAccessor) 12 | { 13 | this.contextAccessor = contextAccessor; 14 | } 15 | 16 | public void Invoke(ProxyContext proxyContext) 17 | { 18 | if (contextAccessor?.HttpContext == null) 19 | { 20 | return; 21 | } 22 | 23 | if (contextAccessor?.HttpContext.Request != null) 24 | { 25 | proxyContext.ClientIp = contextAccessor.HttpContext.GetIp(); 26 | proxyContext.UserAgent = contextAccessor.HttpContext.Request.GetUserAgent(); 27 | if (contextAccessor.HttpContext.Request.QueryString.HasValue) 28 | { 29 | proxyContext.Query = contextAccessor.HttpContext.Request.QueryString.Value; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/NetCoreStack.Proxy.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Always 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | Always 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/ObjFile.txt: -------------------------------------------------------------------------------- 1 | {"Metadata":"NetCoreStack.Domain.Contracts.AlbumKnockoutValidatorViewModel","Draw":1,"Length":10,"Start":0,"Text":null,"Search":null,"Filters":null,"Order":null,"Columns":[{"Data":"id","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"genreId","Meta":"Int64?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"artist","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"title","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"title","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"title","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"title","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"title","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"title","Meta":"String?","Composer":"","Searchable":true,"Orderable":true,"Search":null},{"Data":"price","Meta":"Decimal?","Composer":"","Searchable":true,"Orderable":true,"Search":null},null]} -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/ProxyCreationTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using NetCoreStack.Contracts; 4 | using NetCoreStack.Proxy.Test.Contracts; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace NetCoreStack.Proxy.Tests 13 | { 14 | public class ProxyCreationTests 15 | { 16 | private readonly string _someKey = "248fd6db0ae44ec48169fa2391b067da"; 17 | 18 | protected IServiceProvider Resolver { get; } 19 | protected IConfiguration Configuration { get; } 20 | 21 | public ProxyCreationTests() 22 | { 23 | Resolver = TestHelper.HttpContext.RequestServices; 24 | Configuration = TestHelper.Configuration; 25 | } 26 | 27 | [Fact] 28 | public async Task TaskOperationTest() 29 | { 30 | var guidelineApi = Resolver.GetService(); 31 | var task = guidelineApi.TaskOperation(); 32 | await task; 33 | } 34 | 35 | [Fact] 36 | public async Task GetComplexTypeTest() 37 | { 38 | var guidelineApi = Resolver.GetService(); 39 | var task = guidelineApi.GetComplexType(TypesModelHelper.GetComplexTypeModel()); 40 | await task; 41 | } 42 | 43 | [Fact] 44 | public async Task PrimitiveReturnTest() 45 | { 46 | var guidelineApi = Resolver.GetService(); 47 | var task = guidelineApi.PrimitiveReturn(int.MaxValue, "some string", long.MaxValue, DateTime.Now); 48 | await task; 49 | } 50 | 51 | [Fact] 52 | public async Task GetWithComplexReferenceTypeTest() 53 | { 54 | var guidelineApi = Resolver.GetService(); 55 | var task = guidelineApi.GetWithComplexReferenceType(new CollectionRequest 56 | { 57 | Draw = 1, 58 | Filters = "", 59 | Length = 10, 60 | Metadata = typeof(ComplexTypeModel).FullName, 61 | Start = 0, 62 | Search = null, 63 | Text = "", 64 | Columns = new List 65 | { 66 | new Column 67 | { 68 | Composer = "", 69 | Data = nameof(ComplexTypeModel.Int), 70 | Meta = typeof(int).Name, 71 | Search = null, 72 | Searchable = true, 73 | Orderable = true 74 | }, 75 | new Column 76 | { 77 | Composer = "", 78 | Data = nameof(ComplexTypeModel.String), 79 | Meta = typeof(String).Name, 80 | Search = null, 81 | Searchable = false, 82 | Orderable = false 83 | } 84 | }, 85 | Order = new List 86 | { 87 | new OrderDescriptor 88 | { 89 | ColumnIndex = 0, 90 | Direction = ListSortDirection.Ascending 91 | } 92 | } 93 | }); 94 | 95 | await task; 96 | } 97 | 98 | [Fact] 99 | [Trait("Category", "Group")] 100 | public async Task TaskCallHttpPostWithReferenceTypeParameterTest() 101 | { 102 | Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("tr-TR"); 103 | var guidelineApi = Resolver.GetService(); 104 | await guidelineApi.TaskComplexTypeModel(TypesModelHelper.GetComplexTypeModel()); 105 | } 106 | 107 | [Fact] 108 | [Trait("Category", "Group")] 109 | public async Task TaskActionBarMultipartFormDataTest() 110 | { 111 | Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("fr-FR"); 112 | var guidelineApi = Resolver.GetService(); 113 | await guidelineApi.TaskActionBarMultipartFormData(new Bar 114 | { 115 | String = "Bar string value!", 116 | someint = 6, 117 | SomeEnum = SomeEnum.Value2, 118 | Foo = new Foo { IEnumerableInt = new[] { 1, 3, 5, 7, 9 }, String = "Foo string value!" } 119 | }); 120 | } 121 | 122 | [Fact] 123 | [Trait("Category", "Group")] 124 | public async Task TaskActionBarSimpleXml() 125 | { 126 | Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("es-ES"); 127 | var guidelineApi = Resolver.GetService(); 128 | await guidelineApi.TaskActionBarSimpleXml(new BarSimple 129 | { 130 | String = "Bar string value!", 131 | someint = 6, 132 | SomeEnum = SomeEnum.Value2, 133 | }); 134 | } 135 | 136 | [Fact] 137 | public async Task GenericTaskResultCallTest() 138 | { 139 | var guidelineApi = Resolver.GetService(); 140 | var items = await guidelineApi.GetEnumerableModels(); 141 | Assert.True(items != null); 142 | Assert.True(items.Count() == 4); 143 | } 144 | 145 | [Fact] 146 | public async Task GetCollectionStreamTest() 147 | { 148 | var guidelineApi = Resolver.GetService(); 149 | var collection = await guidelineApi.GetCollectionStreamTask(); 150 | Assert.True(collection != null); 151 | Assert.True(collection.Data.Count() == 5); 152 | } 153 | 154 | [Fact] 155 | public async Task GetSingleFileWithKeyTemplate() 156 | { 157 | var guidelineApi = Resolver.GetService(); 158 | byte[] bytes = await guidelineApi.GetFileAsync("5a6ee0791653ff2348f1cd32", "pdf-sample.pdf"); 159 | Assert.True(true); 160 | } 161 | 162 | [Fact] 163 | public async Task GetFileWithSpaceTest() 164 | { 165 | var guidelineApi = Resolver.GetService(); 166 | byte[] bytes = await guidelineApi.GetFileAsync("5a6ee0791653ff2348f1cd32", Uri.EscapeUriString("Open Source.png")); 167 | Assert.True(true); 168 | } 169 | 170 | [Fact] 171 | public async Task GetSingleFileWithKeyTemplateAndUtf8() 172 | { 173 | var proxy = Resolver.GetService(); 174 | 175 | await proxy.UploadAsync(new FileProxyUploadContext 176 | { 177 | Directory = "5a6ee0791653ff2348f1cd32", 178 | Files = new[] 179 | { 180 | TestHelper.GetFormFile("files", "SampleĞüişçWord.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=utf-8") 181 | } 182 | }); 183 | 184 | Assert.True(true); 185 | } 186 | 187 | [Fact] 188 | public async Task TaskSingleFileModelWithUtf8() 189 | { 190 | var guidelineApi = Resolver.GetService(); 191 | 192 | var model = new SingleFileModel 193 | { 194 | File = TestHelper.GetFormFile("file", "SampleĞüişçWord.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=utf-8") 195 | }; 196 | 197 | await guidelineApi.TaskSingleFileModel(model); 198 | Assert.True(true); 199 | } 200 | 201 | [Fact] 202 | public async Task TaskSingleFileModel() 203 | { 204 | var guidelineApi = Resolver.GetService(); 205 | 206 | var model = new SingleFileModel 207 | { 208 | File = TestHelper.GetFormFile("file", "SampleĞüişçWord.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 209 | }; 210 | 211 | await guidelineApi.TaskSingleFileModel(model); 212 | Assert.True(true); 213 | } 214 | 215 | [Fact] 216 | public async Task UploadAsyncTemplateTest() 217 | { 218 | var fileProxyApi = Resolver.GetService(); 219 | 220 | var model = new FileProxyUploadContext 221 | { 222 | Directory = "proxy-fs/123456", 223 | Files = new[] { TestHelper.GetFormFile("files", "file1.txt"), TestHelper.GetFormFile("files", "file2.txt") } 224 | }; 225 | 226 | await fileProxyApi.UploadAsync(model); 227 | Assert.True(true); 228 | } 229 | 230 | [Fact] 231 | public async Task TaskKeyAndSingleFileModel() 232 | { 233 | var guidelineApi = Resolver.GetService(); 234 | 235 | var model = new SingleFileModel 236 | { 237 | File = TestHelper.GetFormFile("file") 238 | }; 239 | 240 | await guidelineApi.TaskKeyAndSingleFileModel(_someKey, model); 241 | Assert.True(true); 242 | } 243 | 244 | [Fact] 245 | public async Task TaskKeyAndSingleFileAndPropsModel() 246 | { 247 | var guidelineApi = Resolver.GetService(); 248 | 249 | var model = new SingleFileAndPropsModel 250 | { 251 | Int = 6, 252 | String = "Some string value!", 253 | File = TestHelper.GetFormFile("file") 254 | }; 255 | 256 | await guidelineApi.TaskKeyAndSingleFileAndPropsModel(_someKey, model); 257 | Assert.True(true); 258 | } 259 | 260 | [Fact] 261 | public async Task TaskEnumerableFileModel() 262 | { 263 | var guidelineApi = Resolver.GetService(); 264 | 265 | var model = new EnumerableFileModel 266 | { 267 | Files = new[] { TestHelper.GetFormFile("files", "file1.txt"), TestHelper.GetFormFile("files", "file2.txt") } 268 | }; 269 | 270 | await guidelineApi.TaskEnumerableFileModel(model); 271 | Assert.True(true); 272 | } 273 | 274 | [Fact] 275 | public async Task TaskKeyAndEnumerableFileModel() 276 | { 277 | var guidelineApi = Resolver.GetService(); 278 | 279 | var model = new EnumerableFileModel 280 | { 281 | Files = new[] { TestHelper.GetFormFile("files", "file1.txt"), TestHelper.GetFormFile("files", "file2.txt") } 282 | }; 283 | 284 | await guidelineApi.TaskKeyAndEnumerableFileModel(_someKey, model); 285 | Assert.True(true); 286 | } 287 | 288 | [Fact] 289 | public async Task CreateOrUpdateNoBodyKeyTest() 290 | { 291 | var guidelineApi = Resolver.GetService(); 292 | var result = await guidelineApi.CreateOrUpdateKey(_someKey); 293 | Assert.True(result); 294 | } 295 | 296 | [Fact] 297 | public async Task CreateOrUpdateKeyTest() 298 | { 299 | var guidelineApi = Resolver.GetService(); 300 | var result = await guidelineApi.CreateOrUpdateKey(_someKey, new Bar 301 | { 302 | String = "Bar string value!", 303 | someint = 6, 304 | SomeEnum = SomeEnum.Value2, 305 | Foo = new Foo { IEnumerableInt = new[] { 1, 3, 5, 7, 9 }, String = "Foo string value!" } 306 | }); 307 | Assert.True(result); 308 | } 309 | 310 | [Fact] 311 | public async Task TaskActionDeleteTest() 312 | { 313 | var guidelineApi = Resolver.GetService(); 314 | await guidelineApi.TaskActionDelete(1); 315 | Assert.True(true); 316 | } 317 | 318 | [Fact] 319 | public async Task SelfApiOperation() 320 | { 321 | var selfApi = Resolver.GetService(); 322 | await selfApi.Operation(new Baz { String = "Some string", Self = new Self { Int = 7 } }); 323 | Assert.True(true); 324 | } 325 | 326 | [Fact] 327 | public async Task SelfApiOperationCollection() 328 | { 329 | var selfApi = Resolver.GetService(); 330 | await selfApi.OperationCollection(new Baz2 331 | { 332 | String = "Some string", 333 | SelfCollection = new List { new SelfCollection { String = "Collection string" } } 334 | }); 335 | 336 | Assert.True(true); 337 | } 338 | 339 | [Fact] 340 | public async Task SelfApiWithThisOperation() 341 | { 342 | var selfApi = Resolver.GetService(); 343 | var baz = new Baz 344 | { 345 | String = "Some string" 346 | }; 347 | 348 | var self = new Self 349 | { 350 | Baz = baz, 351 | Int = 7 352 | }; 353 | 354 | baz.Self = self; 355 | 356 | await selfApi.Operation(baz); 357 | Assert.True(true); 358 | } 359 | 360 | [Fact] 361 | public async Task AttachmentApiTests() 362 | { 363 | var api = Resolver.GetService(); 364 | await api.PutSubtitleAttachmentList(new SubtitleAttachmentDto { 365 | SubtitleAttachmentList = new List 366 | { 367 | new SubtitleAttachment 368 | { 369 | Attachment = new Attachment 370 | { 371 | Name = "Some attachment", 372 | Content = "Lorem ipsum dolar sit amet" 373 | }, 374 | CategoryId = 2, 375 | SeasonId = 1 376 | } 377 | } 378 | }); 379 | Assert.True(true); 380 | } 381 | 382 | [Fact] 383 | public async Task TaskSmsSendTests() 384 | { 385 | var api = Resolver.GetService(); 386 | var result = await api.Send("000 111 22 33", "Proxy test content", true, 120); 387 | Assert.True(true); 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/SampleĞüişçWord.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetCoreStack/Proxy/b3aadbe50a4ad99acc9133eca894853607761d60/test/NetCoreStack.Proxy.Tests/SampleĞüişçWord.docx -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting.Internal; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Http.Internal; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Logging.Abstractions; 8 | using Microsoft.Extensions.Primitives; 9 | using Microsoft.Net.Http.Headers; 10 | using Moq; 11 | using NetCoreStack.Mvc.Helpers; 12 | using NetCoreStack.Proxy.Test.Contracts; 13 | using System; 14 | using System.Collections.Generic; 15 | using System.Globalization; 16 | using System.IO; 17 | using System.Text; 18 | 19 | namespace NetCoreStack.Proxy.Tests 20 | { 21 | public static class TestHelper 22 | { 23 | public static HttpContext HttpContext { get; set; } 24 | 25 | public static IConfiguration Configuration { get; set; } 26 | 27 | private static void CreateHttpContext(IServiceProvider serviceProvider) 28 | { 29 | var contentType = "application/json; charset=utf-8"; 30 | var request = new Mock(); 31 | var response = new Mock(); 32 | var items = new Dictionary(); 33 | var cookies = new Dictionary(); 34 | var responseHeaders = new HeaderDictionary(); 35 | 36 | var headers = new HeaderDictionary(); 37 | headers.Add(ContextBaseExtensions.ClientUserAgentHeader, new StringValues("Chrome/63.0.3239.84 Safari/537.36")); 38 | 39 | request.SetupGet(x => x.Cookies).Returns(new RequestCookieCollection(cookies)); 40 | request.SetupGet(r => r.Headers).Returns(headers); 41 | request.SetupGet(f => f.ContentType).Returns(contentType); 42 | response.SetupGet(r => r.Headers).Returns(responseHeaders); 43 | 44 | var httpContext = new Mock(); 45 | httpContext.Setup(c => c.RequestServices).Returns(serviceProvider); 46 | httpContext.SetupGet(c => c.Request).Returns(request.Object); 47 | httpContext.SetupGet(c => c.Items).Returns(items); 48 | httpContext.Setup(c => c.Response).Returns(response.Object); 49 | 50 | HttpContext = httpContext.Object; 51 | } 52 | 53 | static TestHelper() 54 | { 55 | var services = new ServiceCollection(); 56 | services.AddSingleton(NullLoggerFactory.Instance); 57 | services.AddSingleton(resolver => new HttpContextAccessor 58 | { 59 | HttpContext = HttpContext 60 | }); 61 | 62 | var env = new HostingEnvironment 63 | { 64 | ContentRootPath = Directory.GetCurrentDirectory() 65 | }; 66 | 67 | services.AddSingleton(env); 68 | 69 | var builder = new ConfigurationBuilder() 70 | .SetBasePath(env.ContentRootPath) 71 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 72 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 73 | .AddEnvironmentVariables(); 74 | 75 | Configuration = builder.Build(); 76 | 77 | services.AddNetCoreProxy(Configuration, options => 78 | { 79 | options.DefaultHeaders.Add("X-NetCoreStack-Header", "ProxyHeaderValue"); 80 | options.RegisterFilter(); 81 | options.Register(); 82 | options.Register(); 83 | options.Register(); 84 | options.Register(); 85 | options.Register(); 86 | options.Register(); 87 | 88 | options.CultureFactory = () => 89 | { 90 | var thread = System.Threading.Thread.CurrentThread; 91 | return thread.CurrentCulture; 92 | }; 93 | }); 94 | 95 | CreateHttpContext(services.BuildServiceProvider()); 96 | } 97 | 98 | public static FormFile GetFormFile(string name, string fileName = "") 99 | { 100 | var content = "some text content"; 101 | var bytes = Encoding.UTF8.GetBytes(content); 102 | var length = bytes.Length; 103 | var ms = new MemoryStream(bytes); 104 | 105 | fileName = string.IsNullOrEmpty(fileName) ? "some_text_file.txt" : fileName; 106 | var formFile = new FormFile(ms, 0, length, name, fileName) 107 | { 108 | Headers = new HeaderDictionary 109 | { 110 | [HeaderNames.ContentType] = "text/plain", 111 | [HeaderNames.ContentDisposition] = $"form-data; name=\"{name}\"; filename=\"{fileName}\"" 112 | } 113 | }; 114 | 115 | return formFile; 116 | } 117 | 118 | public static FormFile GetFormFile(string name, string fileName, string contentType = null) 119 | { 120 | var bytes = File.ReadAllBytes(fileName); 121 | var length = bytes.Length; 122 | var ms = new MemoryStream(bytes); 123 | 124 | contentType = contentType ?? MimeTypeHelper.Resolve(Path.GetExtension(fileName)); 125 | 126 | var formFile = new FormFile(ms, 0, length, name, fileName) 127 | { 128 | Headers = new HeaderDictionary 129 | { 130 | [HeaderNames.ContentType] = contentType, 131 | [HeaderNames.ContentDisposition] = $"form-data; name=\"{name}\"; filename=\"{fileName}\"" 132 | } 133 | }; 134 | 135 | return formFile; 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ProxySettings": { 3 | "RegionKeys": { 4 | "Main": "http://localhost:5003/,http://localhost:5004/", 5 | "Consul": "http://localhost:8500/", 6 | "Integrations": "http://localhost:5003/", 7 | "FS": "http://localhost:8585/" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /test/NetCoreStack.Proxy.Tests/httplive.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetCoreStack/Proxy/b3aadbe50a4ad99acc9133eca894853607761d60/test/NetCoreStack.Proxy.Tests/httplive.db --------------------------------------------------------------------------------