├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── doc ├── api │ └── index.md ├── apidoc │ └── readme.txt ├── articles │ ├── intro.md │ ├── posting_content.md │ └── toc.yml ├── docfx.json ├── filterConfig.yml ├── images │ ├── d.svg │ ├── favicon.ico │ └── logo.svg ├── index.md ├── template │ └── partials │ │ └── navbar.tmpl.partial └── toc.yml └── src ├── Dapplo.HttpExtensions.JsonNet ├── Dapplo.HttpExtensions.JsonNet.csproj ├── JsonNetJsonSerializer.cs └── ReadOnlyConsideringContractResolver.cs ├── Dapplo.HttpExtensions.JsonSimple ├── Dapplo.HttpExtensions.JsonSimple.csproj ├── DataContractJsonSerializerStrategy.cs ├── DefaultJsonHttpContentConverter.cs ├── IJsonSerializerStrategy.cs ├── JsonArray.cs ├── JsonExtensionDataAttribute.cs ├── JsonObject.cs ├── PocoJsonSerializerStrategy.cs ├── ReflectionUtils.cs ├── SimpleJson.cs └── SimpleJsonSerializer.cs ├── Dapplo.HttpExtensions.OAuth ├── AuthorizeModes.cs ├── BaseOAuthSettings.cs ├── CodeReceivers │ ├── EmbeddedBrowserCodeReceiver.cs │ ├── LocalServerCodeReceiver.cs │ ├── OutOfBoundCodeReceiver.cs │ └── PassThroughCodeReceiver.cs ├── Dapplo.HttpExtensions.OAuth.csproj ├── Desktop │ ├── OAuthLoginForm.Designer.cs │ └── OAuthLoginForm.cs ├── GlobalUsings.cs ├── GrantTypes.cs ├── ICodeReceiverSettings.cs ├── IOAuth1Token.cs ├── IOAuth2Token.cs ├── IOAuthCodeReceiver.cs ├── OAuth1HttpBehaviour.cs ├── OAuth1HttpBehaviourFactory.cs ├── OAuth1HttpMessageHandler.cs ├── OAuth1Parameters.cs ├── OAuth1RequestConfiguration.cs ├── OAuth1Settings.cs ├── OAuth1SignatureTransports.cs ├── OAuth1SignatureTypes.cs ├── OAuth1Token.cs ├── OAuth2Fields.cs ├── OAuth2HttpBehaviourFactory.cs ├── OAuth2HttpMessageHandler.cs ├── OAuth2Settings.cs ├── OAuth2TokenInformation.cs ├── OAuth2TokenResponse.cs └── OAuthExtensions.cs ├── Dapplo.HttpExtensions.SystemTextJson ├── Dapplo.HttpExtensions.SystemTextJson.csproj └── SystemTextJsonSerializer.cs ├── Dapplo.HttpExtensions.Tests ├── Dapplo.HttpExtensions.Tests.csproj ├── GithubTests.cs ├── HttpBehaviourExtensionsTests.cs ├── HttpPartsPostTest.cs ├── HttpRequestConfigurationTests.cs ├── HttpRequestMessageExtensionsTests.cs ├── OAuth │ ├── ImgurOAuth2Tests.cs │ ├── OAuth2Tests.cs │ └── OAuthTests.cs ├── SimpleJsonTests.cs ├── Support │ └── UriHttpListenerExtensionsTests.cs ├── TestEntities │ ├── GitHubError.cs │ ├── GitHubRelease.cs │ ├── MyMultiPartRequest.cs │ ├── ReleaseTypes.cs │ ├── SerializeTestEntity.cs │ └── WithExtensionData.cs ├── UriActionExtensionsMultiPartTests.cs ├── UriActionExtensionsTests.cs ├── UriModifyExtensionsTests.cs ├── UriParseExtensionsTests.cs ├── d.png └── xunit.runner.json ├── Dapplo.HttpExtensions.WinForms ├── ContentConverter │ ├── BitmapConfiguration.cs │ └── BitmapHttpContentConverter.cs └── Dapplo.HttpExtensions.WinForms.csproj ├── Dapplo.HttpExtensions.Wpf ├── ContentConverter │ ├── BitmapSourceConfiguration.cs │ └── BitmapSourceHttpContentConverter.cs └── Dapplo.HttpExtensions.Wpf.csproj ├── Dapplo.HttpExtensions.sln ├── Dapplo.HttpExtensions.snk ├── Dapplo.HttpExtensions ├── AuthorizationExtensions.cs ├── ContentConverter │ ├── ByteArrayHttpContentConverter.cs │ ├── DefaultJsonHttpContentConverter.cs │ ├── DefaultJsonHttpContentConverterConfiguration.cs │ ├── FormUriEncodedContentConverter.cs │ ├── StreamHttpContentConverter.cs │ ├── StringConfiguration.cs │ ├── StringHttpContentConverter.cs │ ├── SyndicationFeedHttpContentConverter.cs │ └── XDocumentHttpContentConverter.cs ├── Dapplo.HttpExtensions.csproj ├── Extensions │ ├── EnumExtensions.cs │ ├── HttpBehaviourExtensions.cs │ ├── StringExtensions.cs │ └── TypeExtensions.cs ├── Factory │ ├── HttpClientFactory.cs │ ├── HttpContentFactory.cs │ ├── HttpMessageHandlerFactory.cs │ ├── HttpRequestMessageFactory.cs │ └── WebProxyFactory.cs ├── GlobalUsings.cs ├── HttpBehaviour.cs ├── HttpBehaviourExtensions.cs ├── HttpClientExtensions.cs ├── HttpContentExtensions.cs ├── HttpExtensionsGlobals.cs ├── HttpRequestMessageConfiguration.cs ├── HttpRequestMessageExtensions.cs ├── HttpResponse.cs ├── HttpResponseMessageExtensions.cs ├── IChangeableHttpBehaviour.cs ├── IHttpBehaviour.cs ├── IHttpContentConverter.cs ├── IHttpRequestConfiguration.cs ├── IHttpSettings.cs ├── IJsonSerializer.cs ├── Listener │ ├── HttpListenerContextExtensions.cs │ ├── ListenerPortExtensions.cs │ └── UriHttpListenerExtensions.cs ├── MiscExtensions.cs ├── Support │ ├── ContentItem.cs │ ├── HttpPartAttribute.cs │ ├── HttpParts.cs │ ├── HttpRequestAttribute.cs │ ├── HttpResponseAttribute.cs │ ├── HttpSettings.cs │ ├── MediaTypes.cs │ ├── ProgressStream.cs │ └── ProgressStreamReport.cs ├── UriActionExtensions.cs ├── UriModifyExtensions.cs └── UriParseExtensions.cs ├── Directory.Build.props ├── NuGet.Config ├── global.json ├── icon.png └── version.json /.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/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | github: lakritzator 5 | patreon: # Replace with a single Patreon username 6 | open_collective: # Replace with a single open collective project 7 | ko_fi: # Replace with a single Ko-fi username 8 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | custom: # Replace with a custom url 10 | -------------------------------------------------------------------------------- /.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 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | 214 | # Cake tools 215 | tools/**/ 216 | tools/packages.config.md5sum 217 | 218 | # DocFX 219 | _site/ 220 | /**/DROP/ 221 | /**/TEMP/ 222 | doc/api/**/*.yml 223 | doc/api/.manifest 224 | 225 | # Coverage 226 | CoverageReport 227 | coverage.xml 228 | xunit.xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Dapplo and Contributors 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dapplo.httpextensions 2 | ===================== 3 | 4 | - Documentation [here](http://www.dapplo.net/Dapplo.HttpExtensions) 5 | - Current build status: [![Build Status](https://dev.azure.com/Dapplo/Dapplo%20framework/_apis/build/status/dapplo.Dapplo.HttpExtensions?branchName=master)](https://dev.azure.com/Dapplo/Dapplo%20framework/_build/latest?definitionId=9&branchName=master) 6 | - Coverage Status: [![Coverage Status](https://coveralls.io/repos/github/dapplo/Dapplo.HttpExtensions/badge.svg?branch=master)](https://coveralls.io/github/dapplo/Dapplo.HttpExtensions?branch=master) 7 | - NuGet package: [![NuGet package](https://img.shields.io/nuget/v/Dapplo.HttpExtensions.svg)](https://www.nuget.org/packages/Dapplo.HttpExtensions) 8 | 9 | Sometimes you just want to access a service in the internet or on a local server. 10 | This project helps you to deal with a lot of difficult stuff, like having a default proxy or even making a specify proxy possible. 11 | Also it can handle Json communication, which a lot of REST based APIs use. 12 | 13 | A short example: 14 | ``` 15 | using Dapplo.HttpExtensions; 16 | 17 | var uri = new Uri("http://myserver/"); 18 | var response = await uri.GetAsAsync(); 19 | ``` 20 | 21 | A more "complex" example of how you can use this, is available in the test. 22 | 23 | There is a test with calling a JSON Service (GitHub): [UriActionExtensionsTests.TestGetAsJsonAsync_GitHubApiReleases](https://github.com/dapplo/Dapplo.HttpExtensions/blob/master/Dapplo.HttpExtensions.Test/UriActionExtensionsTests.cs). 24 | 25 | There is also an OAuth test: [OAuth/OAuthTests.TestOAuthHttpMessageHandler](https://github.com/dapplo/Dapplo.HttpExtensions/blob/master/Dapplo.HttpExtensions.Test/OAuth/OAuthTests.cs). 26 | This is not running during the build, as it needs "human" interaction with a browser. 27 | 28 | Some of the features: 29 | 30 | 1. Fluent API 31 | 2. Progress support, for uploading and downloading 32 | 3. Typed access to Http content (e.g. GetAsAsync ) 33 | 4. Typed upload 34 | 5. Json support, in two variants: 35 | 1. [SimpleJson](https://github.com/facebook-csharp-sdk/simple-json) via nuget package Dapplo.HttpExtensions.JsonSimple and call SimpleJsonSerializer.RegisterGlobally() or set IHttpBehavior.JsonSerializer to new SimpleJsonSerializer(); 36 | 2. install nuget package Dapplo.HttpExtensions.JsonNet and call JsonNetJsonSerializer.RegisterGlobally(); or set IHttpBehavior.JsonSerializer to new JsonNetJsonSerializer(); 37 | 6. OAuth 1 & 2 via nuget package Dapplo.HttpExtensions.OAuth, this is currently work in process but with some servers it should already be usable. 38 | 39 | Notes: 40 | This project uses async code, and tries to conform to the Task-bases Asynchronous Pattern (TAP). Just so you know why sometimes the method name look odd... Meaning all async methods have names which end with Async and (where possible) accept a CancellationToken. This is the final parameter, as adviced here: https://blogs.msdn.microsoft.com/andrewarnottms/2014/03/19/recommended-patterns-for-cancellationtoken/ 41 | 42 | API Changes, with every 0.x change the signatures have changed: 43 | In 0.2.x a HttpBehaviour object was added to prevent future signature changes as much as possible. 44 | In 0.3.x a lot of previous method were combined. (GetAsMemoryStream -> GetAsAsync, GetAsStringAsync -> GetAsAsync) 45 | In 0.4.x the IHttpBehaviour was removed again, it's now passed via the CallContext. If you need to a specific behaviour for a call, than you will need to call MakeCurrent() on the behaviour before the call. 46 | In 0.5.x The configuration logic for the converters was changed 47 | In 0.6.x the Json and OAuth support were removed from the main package 48 | In 0.7.x doesn't have changes (switched to 0.8 by mistake) 49 | In 0.8.x the project / build was changed to use the new csproj, build with VS2017 15.3 (due to csproj limitations in 15.2) -------------------------------------------------------------------------------- /doc/api/index.md: -------------------------------------------------------------------------------- 1 | # Dapplo.HttpExtensions API Description 2 | 3 | Here is the API described.. 4 | -------------------------------------------------------------------------------- /doc/apidoc/readme.txt: -------------------------------------------------------------------------------- 1 | Here are over-write files located 2 | -------------------------------------------------------------------------------- /doc/articles/intro.md: -------------------------------------------------------------------------------- 1 | # Dapplo.HttpExtensions 2 | 3 | Quick start coming here... -------------------------------------------------------------------------------- /doc/articles/posting_content.md: -------------------------------------------------------------------------------- 1 | # Posting content 2 | 3 | The library tries to take a lot of work away from the developer, and tries to use sane defaults for it's behaviour. 4 | 5 | You can just post a System.Drawing.Bitmap, and the library will generate a binary upload with the mimetype being "imgage/png" 6 | The configuration for this can be modified by using the BitmapConfiguration and settings this on the used HttpBehaviour. 7 | For instance, if you like the default to be gif, use the following line: 8 | ```HttpBehaviour.Current.SetConfig(new BitmapConfiguration {Format = ImageFormat.Gif});``` 9 | There are converters for BitmapSource, XDocument, SyndicationFeed, Stream, byte[], or a IEnumerable> (e.g. IDictionary) 10 | 11 | Also posting JSON is quite easy, but you will need to have a serializer. 12 | By adding Dapplo.HttpExtensions.JsonNet you can configure this with one line: 13 | ```HttpExtensionsGlobals.JsonSerializer = new JsonNetJsonSerializer();``` 14 | Or 15 | ```JsonNetJsonSerializer.RegisterGlobally();``` 16 | 17 | Now if you would do something like this: 18 | 19 | ``` 20 | var exampleUri = new Uri("https://myserver/api/create"); 21 | var someObject = .... 22 | 23 | exampleUri.PostAsync(someObject); 24 | ``` 25 | 26 | In this case, the library tries to serialize someObject by every means possible, and will when you did not add other "converters" use JsonNetJsonSerializer. 27 | In this case it uses Json.NET to serialize the object, and will create a "string" content with json and use the mimetype "application/json". 28 | 29 | To have more control over the content which is posted, you can supply a self created HttpContent object. 30 | There is a factory to do the heavy lifting, allowing more controll, here an example which specifies a different mimetype: 31 | var content = HttpContentFactory.Create(someObject); 32 | content.SetContentType("application/json-patch+json"); 33 | 34 | Sending is than easy: 35 | ```exampleUri.PostAsync(content);``` 36 | 37 | You can also "post" an implementation of a custom class, where you can use your own properties. 38 | Here is an example to generate a multipart request with a json and a png: 39 | 40 | ``` 41 | [HttpRequest] 42 | public class MyMultiPartRequest 43 | { 44 | [HttpPart(HttpParts.RequestMultipartName, Order = 1)] 45 | public string BitmapContentName { get; set; } = "File"; 46 | 47 | [HttpPart(HttpParts.RequestContentType, Order = 1)] 48 | public string BitmapContentType { get; set; } = "image/png"; 49 | 50 | [HttpPart(HttpParts.RequestMultipartFilename, Order = 1)] 51 | public string BitmapFileName { get; set; } = "empty.png"; 52 | 53 | [HttpPart(HttpParts.RequestContentType, Order = 0)] 54 | public string ContentType { get; set; } = "application/json"; 55 | 56 | [HttpPart(HttpParts.RequestHeaders)] 57 | public IDictionary Headers { get; } = new Dictionary(); 58 | 59 | [HttpPart(HttpParts.RequestContent, Order = 0)] 60 | public object JsonInformation { get; set; } 61 | 62 | [HttpPart(HttpParts.RequestContent, Order = 1)] 63 | public TBitmap OurBitmap { get; set; } 64 | } 65 | ``` -------------------------------------------------------------------------------- /doc/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Introduction 2 | href: intro.md 3 | - name: Posting content 4 | href: posting_content.md -------------------------------------------------------------------------------- /doc/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "src": "../src", 7 | "files": [ 8 | "Dapplo.HttpExtensions*/*.csproj" 9 | ], 10 | "exclude": [ 11 | "**/bin/**", 12 | "**/obj/**", 13 | "**Tests/*.csproj", 14 | ] 15 | } 16 | ], 17 | "dest": "api", 18 | "filter": "filterConfig.yml" 19 | } 20 | ], 21 | "build": { 22 | "content": [ 23 | { 24 | "files": [ 25 | "api/**.md", 26 | "api/**.yml", 27 | "articles/**.md", 28 | "articles/**/toc.yml", 29 | "toc.yml", 30 | "*.md" 31 | ], 32 | "exclude": [ 33 | "_site/**", 34 | "README.md" 35 | ] 36 | } 37 | ], 38 | "resource": [ 39 | { 40 | "files": [ 41 | "images/**" 42 | ], 43 | } 44 | ], 45 | "overwrite": [ 46 | { 47 | "files": [ 48 | "apidoc/**.md" 49 | ], 50 | } 51 | ], 52 | "dest": "_site", 53 | "globalMetadata": { 54 | "_appTitle": "Dapplo.HttpExtensions", 55 | "_appLogoPath": "images/d.svg", 56 | "_appFaviconPath": "images/favicon.ico", 57 | "_appFooter": "Copyright © Dapplo" 58 | }, 59 | "globalMetadataFiles": [], 60 | "template": [ 61 | "statictoc", 62 | "template" 63 | ], 64 | "fileMetadataFiles": [], 65 | "postProcessors": [], 66 | "noLangKeyword": false 67 | } 68 | } -------------------------------------------------------------------------------- /doc/filterConfig.yml: -------------------------------------------------------------------------------- 1 | apiRules: 2 | - exclude: 3 | uidRegex: ^.*PowerShell.*$ 4 | type: Namespace 5 | -------------------------------------------------------------------------------- /doc/images/d.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 67 | 73 | 79 | 85 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /doc/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapplo/Dapplo.HttpExtensions/437df89f66bae1557bd9f206281405232662f2db/doc/images/favicon.ico -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Dapplo.HttpExtensions 2 | 3 | - Current build status: [![Build status](https://ci.appveyor.com/api/projects/status/y4n7u63336vhuy46?svg=true)](https://ci.appveyor.com/project/dapplo/dapplo-httpextensions) 4 | - Coverage Status: [![Coverage Status](https://coveralls.io/repos/github/dapplo/Dapplo.HttpExtensions/badge.svg?branch=master)](https://coveralls.io/github/dapplo/Dapplo.HttpExtensions?branch=master) 5 | - NuGet package: [![NuGet package](https://img.shields.io/nuget/v/Dapplo.HttpExtensions.svg)](https://www.nuget.org/packages/Dapplo.HttpExtensions) 6 | 7 | Sometimes you just want to access a service in the internet or on a local server. 8 | This project helps you to deal with a lot of difficult stuff, like having a default proxy or even making a specify proxy possible. 9 | Also it can handle Json communication, which a lot of REST based APIs use. 10 | 11 | A short example: 12 | ``` 13 | using Dapplo.HttpExtensions; 14 | 15 | var uri = new Uri("http://myserver/"); 16 | var response = await uri.GetAsAsync(); 17 | ``` 18 | 19 | A more "complex" example of how you can use this, is available in the test. 20 | 21 | There is a test with calling a JSON Service (GitHub): [UriActionExtensionsTests.TestGetAsJsonAsync_GitHubApiReleases](https://github.com/dapplo/Dapplo.HttpExtensions/blob/master/Dapplo.HttpExtensions.Test/UriActionExtensionsTests.cs). 22 | 23 | There is also an OAuth test: [OAuth/OAuthTests.TestOAuthHttpMessageHandler](https://github.com/dapplo/Dapplo.HttpExtensions/blob/master/Dapplo.HttpExtensions.Test/OAuth/OAuthTests.cs). 24 | This is not running during the build, as it needs "human" interaction with a browser. 25 | 26 | Some of the features: 27 | 28 | 1. Fluent API 29 | 2. Progress support, for uploading and downloading 30 | 3. Typed access to Http content (e.g. GetAsAsync ) 31 | 4. Typed upload 32 | 5. Json support, in two variants: 33 | 1. [SimpleJson](https://github.com/facebook-csharp-sdk/simple-json) via nuget package Dapplo.HttpExtensions.JsonSimple and call SimpleJsonSerializer.RegisterGlobally() or set IHttpBehavior.JsonSerializer to new SimpleJsonSerializer(); 34 | 2. install nuget package Dapplo.HttpExtensions.JsonNet and call JsonNetJsonSerializer.RegisterGlobally(); or set IHttpBehavior.JsonSerializer to new JsonNetJsonSerializer(); 35 | 6. OAuth 1 & 2 via nuget package Dapplo.HttpExtensions.OAuth, this is currently work in process but with some servers it should already be usable. 36 | 37 | Notes: 38 | This project uses async code, and tries to conform to the Task-bases Asynchronous Pattern (TAP). Just so you know why sometimes the method name look odd... Meaning all async methods have names which end with Async and (where possible) accept a CancellationToken. This is the final parameter, as adviced here: https://blogs.msdn.microsoft.com/andrewarnottms/2014/03/19/recommended-patterns-for-cancellationtoken/ 39 | 40 | API Changes, with every 0.x change the signatures have changed: 41 | In 0.2.x a HttpBehaviour object was added to prevent future signature changes as much as possible. 42 | In 0.3.x a lot of previous method were combined. (GetAsMemoryStream -> GetAsAsync, GetAsStringAsync -> GetAsAsync) 43 | In 0.4.x the IHttpBehaviour was removed again, it's now passed via the CallContext. If you need to a specific behaviour for a call, than you will need to call MakeCurrent() on the behaviour before the call. 44 | In 0.5.x The configuration logic for the converters was changed 45 | In 0.6.x the Json and OAuth support were removed from the main package 46 | -------------------------------------------------------------------------------- /doc/template/partials/navbar.tmpl.partial: -------------------------------------------------------------------------------- 1 | {{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} 2 | 3 | -------------------------------------------------------------------------------- /doc/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Articles 2 | href: articles/ 3 | - name: Api Documentation 4 | href: api/ 5 | homepage: api/index.md 6 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonNet/Dapplo.HttpExtensions.JsonNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Json.NET functionality for Dapplo.HttpExtensions 4 | net471;netstandard1.3;netstandard2.0;net6.0 5 | http;rest;dapplo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonNet/JsonNetJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | using Newtonsoft.Json; 8 | #if NETFRAMEWORK 9 | using System.Drawing; 10 | using System.Windows.Media.Imaging; 11 | #endif 12 | 13 | namespace Dapplo.HttpExtensions.JsonNet 14 | { 15 | /// 16 | /// Made to have Dapplo.HttpExtension use Json.NET 17 | /// 18 | public class JsonNetJsonSerializer : IJsonSerializer 19 | { 20 | private static readonly Type[] NotSerializableTypes = 21 | { 22 | #if NETFRAMEWORK 23 | typeof(Bitmap), 24 | typeof(BitmapSource), 25 | #endif 26 | typeof(Stream), 27 | typeof(MemoryStream) 28 | }; 29 | 30 | /// 31 | /// Register this IJsonSerializer 32 | /// 33 | /// bool to specify if this also needs to be set when another serializer is already specified 34 | public static void RegisterGlobally(bool force = true) 35 | { 36 | if (force || HttpExtensionsGlobals.JsonSerializer != null) 37 | { 38 | HttpExtensionsGlobals.JsonSerializer = new JsonNetJsonSerializer(); 39 | } 40 | } 41 | 42 | /// 43 | /// The JsonSerializerSettings used in the JsonNetJsonSerializer 44 | /// 45 | public JsonSerializerSettings Settings { get; set; } = new JsonSerializerSettings 46 | { 47 | DateParseHandling = DateParseHandling.None, 48 | NullValueHandling = NullValueHandling.Ignore, 49 | DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, 50 | ContractResolver = new ReadOnlyConsideringContractResolver() 51 | }; 52 | 53 | 54 | /// 55 | /// Test if the specified type can be serialized to JSON 56 | /// 57 | /// Type to check 58 | /// bool 59 | public bool CanSerializeTo(Type sourceType) 60 | { 61 | return NotSerializableTypes.All(type => type != sourceType); 62 | } 63 | 64 | /// 65 | /// Test if the specified type can be deserialized 66 | /// 67 | /// Type to check 68 | /// bool 69 | public bool CanDeserializeFrom(Type targetType) 70 | { 71 | return NotSerializableTypes.All(type => type != targetType); 72 | } 73 | 74 | /// 75 | /// Deserialize the specified json string into the target type 76 | /// 77 | /// 78 | /// 79 | /// 80 | public object Deserialize(Type targetType, string jsonString) 81 | { 82 | return JsonConvert.DeserializeObject(jsonString, targetType, Settings); 83 | } 84 | 85 | /// 86 | /// Serialize the passed object into a json string 87 | /// 88 | /// 89 | /// 90 | /// 91 | public string Serialize(T jsonObject) 92 | { 93 | return JsonConvert.SerializeObject(jsonObject, Settings); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonNet/ReadOnlyConsideringContractResolver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.ComponentModel; 5 | using System.Reflection; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Serialization; 8 | 9 | namespace Dapplo.HttpExtensions.JsonNet; 10 | 11 | /// 12 | /// This takes care that the members are only serialized when there is no ReadOnlyAttribute 13 | /// 14 | public class ReadOnlyConsideringContractResolver : DefaultContractResolver 15 | { 16 | /// 17 | /// This takes care that the members are only serialized when there is no ReadOnlyAttribute 18 | /// 19 | /// MemberInfo 20 | /// MemberSerialization 21 | /// JsonProperty 22 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 23 | { 24 | var property = base.CreateProperty(member, memberSerialization); 25 | property.ShouldSerialize = o => member.GetCustomAttribute() is null; 26 | return property; 27 | } 28 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonSimple/Dapplo.HttpExtensions.JsonSimple.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Simple functionality for Dapplo.HttpExtensions 4 | Dapplo.HttpExtensions.JsonSimple 5 | netstandard1.3;netstandard2.0;net471 6 | false 7 | Dapplo.HttpExtensions.JsonSimple 8 | Dapplo.HttpExtensions.JsonSimple 9 | http;rest;dapplo 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonSimple/IJsonSerializerStrategy.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.CodeDom.Compiler; 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | namespace Dapplo.HttpExtensions.JsonSimple; 9 | 10 | [GeneratedCode("simple-json", "1.0.0")] 11 | #if SIMPLE_JSON_INTERNAL 12 | internal 13 | #else 14 | public 15 | #endif 16 | interface IJsonSerializerStrategy 17 | { 18 | [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")] 19 | bool TrySerializeNonPrimitiveObject(object input, out object output); 20 | 21 | object DeserializeObject(object value, Type type); 22 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonSimple/JsonArray.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.CodeDom.Compiler; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Diagnostics.CodeAnalysis; 8 | 9 | namespace Dapplo.HttpExtensions.JsonSimple; 10 | 11 | /// 12 | /// Represents the json array. 13 | /// 14 | [GeneratedCode("simple-json", "1.0.0")] 15 | [EditorBrowsable(EditorBrowsableState.Never)] 16 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] 17 | #if SIMPLE_JSON_OBJARRAYINTERNAL 18 | internal 19 | #else 20 | public 21 | #endif 22 | class JsonArray : List 23 | { 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | public JsonArray() 28 | { 29 | } 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// 34 | /// The capacity of the json array. 35 | public JsonArray(int capacity) : base(capacity) 36 | { 37 | } 38 | 39 | /// 40 | /// The json representation of the array. 41 | /// 42 | /// The json representation of the array. 43 | public override string ToString() 44 | { 45 | return SimpleJson.SerializeObject(this) ?? string.Empty; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonSimple/JsonExtensionDataAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | 6 | namespace Dapplo.HttpExtensions.JsonSimple; 7 | 8 | /// 9 | /// Use this attribute on a IDictionary to be able to receive all data which cannot be matched in the target type 10 | /// 11 | [AttributeUsage(AttributeTargets.Property)] 12 | public class JsonExtensionDataAttribute : Attribute 13 | { 14 | /// 15 | /// Specify a regex to match the property names of the Json content 16 | /// 17 | public string Pattern { get; set; } 18 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.JsonSimple/SimpleJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | #if NETFRAMEWORK 9 | using System.Drawing; 10 | using System.Windows.Media.Imaging; 11 | #endif 12 | 13 | namespace Dapplo.HttpExtensions.JsonSimple 14 | { 15 | /// 16 | /// This defines the default way how Json is de-/serialized. 17 | /// 18 | public class SimpleJsonSerializer : IJsonSerializer 19 | { 20 | private static readonly Type[] NotSerializableTypes = 21 | { 22 | #if NETFRAMEWORK 23 | typeof(Bitmap), 24 | typeof(BitmapSource), 25 | #endif 26 | typeof(Stream), 27 | typeof(MemoryStream) 28 | }; 29 | 30 | /// 31 | /// Register this IJsonSerializer globally 32 | /// 33 | /// bool to specify if this also needs to be set when another serializer is already specified 34 | public static void RegisterGlobally(bool force = true) 35 | { 36 | if (force || HttpExtensionsGlobals.JsonSerializer != null) 37 | { 38 | HttpExtensionsGlobals.JsonSerializer = new SimpleJsonSerializer(); 39 | } 40 | } 41 | 42 | /// 43 | /// 44 | /// Type to deserialize from a json string 45 | /// json 46 | /// Deserialized json as targetType 47 | public object Deserialize(Type targetType, string jsonString) 48 | { 49 | return SimpleJson.DeserializeObject(jsonString, targetType); 50 | } 51 | 52 | /// 53 | /// Test if the specified type can be serialized to JSON 54 | /// 55 | /// Type to check 56 | /// bool 57 | public bool CanSerializeTo(Type sourceType) 58 | { 59 | return NotSerializableTypes.All(type => type != sourceType); 60 | } 61 | 62 | /// 63 | /// Test if the specified type can be deserialized 64 | /// 65 | /// Type to check 66 | /// bool 67 | public bool CanDeserializeFrom(Type targetType) 68 | { 69 | return NotSerializableTypes.All(type => type != targetType); 70 | } 71 | 72 | /// 73 | /// 74 | /// Type to serialize to json string 75 | /// The actual object 76 | /// string with json 77 | public string Serialize(TContent jsonObject) 78 | { 79 | return SimpleJson.SerializeObject(jsonObject); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/AuthorizeModes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Specify the autorize mode that is used to get the token from the cloud service. 8 | /// Some details are described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp 9 | /// You can register your implementations with the OAuthHttpMessageHandler 10 | /// Currently only a LocalServer is in this project 11 | /// 12 | public enum AuthorizeModes 13 | { 14 | /// 15 | /// Default value, this will give an exception, caller needs to specify another value 16 | /// 17 | Unknown, 18 | 19 | /// 20 | /// Used with tests 21 | /// 22 | TestPassThrough, 23 | 24 | /// 25 | /// Used with a redirect URL to http://localhost:port, this is supported out of the box 26 | /// 27 | LocalhostServer, 28 | 29 | /// 30 | /// This mode should show a popup where the user can paste the code, this is used with a redirect_uri of: 31 | /// urn:ietf:wg:oauth:2.0:oob 32 | /// 33 | OutOfBound, 34 | 35 | /// 36 | /// This mode should monitor for title changes, used with a redirect_uri of: urn:ietf:wg:oauth:2.0:oob:auto 37 | /// Dapplo.Windows has possibilities to monitor titles, this could be used for an implementation 38 | /// 39 | OutOfBoundAuto, 40 | 41 | /// 42 | /// Should ask the user to enter the PIN which is shown in the browser 43 | /// 44 | Pin, 45 | 46 | /// 47 | /// Should open an embedded _browser and catch the redirect 48 | /// 49 | EmbeddedBrowser, 50 | 51 | /// 52 | /// Custom mode 1 53 | /// 54 | Custom1, 55 | 56 | /// 57 | /// Custom mode 2 58 | /// 59 | Custom2 60 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/BaseOAuthSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Common properties for the OauthXSettings 8 | /// 9 | public abstract class BaseOAuthSettings : ICodeReceiverSettings 10 | { 11 | /// 12 | /// Put anything in here which is needed for the OAuth implementation of this specific service but isn't generic, e.g. 13 | /// for Google there is a "scope" 14 | /// 15 | public IDictionary AdditionalAttributes { get; set; } = new Dictionary(); 16 | 17 | /// 18 | /// The AuthorizeMode for this OAuth settings 19 | /// 20 | public AuthorizeModes AuthorizeMode { get; set; } = AuthorizeModes.Unknown; 21 | 22 | /// 23 | /// This makes sure than the OAuth request that needs to authenticate blocks all others until it's ready. 24 | /// 25 | public SemaphoreSlim Lock { get; } = new SemaphoreSlim(1, 1); 26 | 27 | /// 28 | /// The URL to get a (request) token 29 | /// 30 | public Uri TokenUrl { get; set; } 31 | 32 | /// 33 | public Uri AuthorizationUri { get; set; } 34 | 35 | /// 36 | public string RedirectUrl { get; set; } 37 | 38 | /// 39 | public string CloudServiceName { get; set; } = "the remote server"; 40 | 41 | /// 42 | public string ClientId { get; set; } 43 | 44 | /// 45 | public string ClientSecret { get; set; } 46 | 47 | /// 48 | public string State { get; set; } = Guid.NewGuid().ToString(); 49 | 50 | /// 51 | public int EmbeddedBrowserWidth { get; set; } = 600; 52 | 53 | /// 54 | public int EmbeddedBrowserHeight { get; set; } = 400; 55 | 56 | /// 57 | public TaskScheduler UiTaskScheduler { get; set; } 58 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/CodeReceivers/EmbeddedBrowserCodeReceiver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Drawing; 5 | using System.Windows.Forms; 6 | using Dapplo.HttpExtensions.OAuth.Desktop; 7 | 8 | namespace Dapplo.HttpExtensions.OAuth.CodeReceivers; 9 | 10 | /// 11 | /// This will start an embedded browser to wait for the code 12 | /// 13 | public class EmbeddedBrowserCodeReceiver : IOAuthCodeReceiver 14 | { 15 | #pragma warning disable IDE0090 // Use 'new(...)' 16 | private static readonly LogSource Log = new LogSource(); 17 | #pragma warning restore IDE0090 // Use 'new(...)' 18 | 19 | /// 20 | /// Receive the code from an OAuth server 21 | /// 22 | /// AuthorizeModes 23 | /// ICodeReceiverSettings 24 | /// CancellationToken 25 | /// IDictionary with information 26 | public Task> ReceiveCodeAsync(AuthorizeModes authorizeMode, ICodeReceiverSettings codeReceiverSettings, 27 | CancellationToken cancellationToken = default) 28 | { 29 | if (codeReceiverSettings.RedirectUrl is null) 30 | { 31 | throw new ArgumentNullException(nameof(codeReceiverSettings.RedirectUrl), "The EmbeddedBrowserCodeReceiver needs a redirect url."); 32 | } 33 | // while the listener is beging starter in the "background", here we prepare opening the browser 34 | var uriBuilder = new UriBuilder(codeReceiverSettings.AuthorizationUri) 35 | { 36 | Query = codeReceiverSettings.AuthorizationUri.QueryToKeyValuePairs() 37 | .Select(x => new KeyValuePair(x.Key, x.Value.FormatWith(codeReceiverSettings))) 38 | .ToQueryString() 39 | }; 40 | Log.Verbose().WriteLine("Opening Uri {0}", uriBuilder.Uri.AbsoluteUri); 41 | 42 | // Needs to run on th UI thread. 43 | return Task.Factory.StartNew(() => 44 | { 45 | var oAuthLoginForm = new OAuthLoginForm(codeReceiverSettings.CloudServiceName, 46 | new Size(codeReceiverSettings.EmbeddedBrowserWidth, codeReceiverSettings.EmbeddedBrowserHeight), 47 | uriBuilder.Uri, codeReceiverSettings.RedirectUrl); 48 | 49 | if (oAuthLoginForm.ShowDialog() == DialogResult.OK) 50 | { 51 | return oAuthLoginForm.CallbackParameters; 52 | } 53 | 54 | return null; 55 | }, cancellationToken, TaskCreationOptions.None, codeReceiverSettings.UiTaskScheduler ?? TaskScheduler.FromCurrentSynchronizationContext()); 56 | } 57 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/CodeReceivers/PassThroughCodeReceiver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth.CodeReceivers; 5 | 6 | /// 7 | /// Simply pass the Request token as the authentication 8 | /// 9 | internal class PassThroughCodeReceiver : IOAuthCodeReceiver 10 | { 11 | /// 12 | /// The OAuth code receiver implementation 13 | /// 14 | /// which of the AuthorizeModes was used to call the method 15 | /// 16 | /// CancellationToken 17 | /// Dictionary with values 18 | public Task> ReceiveCodeAsync(AuthorizeModes authorizeMode, ICodeReceiverSettings codeReceiverSettings, 19 | CancellationToken cancellationToken = default) 20 | { 21 | var result = new Dictionary(); 22 | 23 | if (codeReceiverSettings is OAuth1Settings oauth1Settings) 24 | { 25 | result.Add(OAuth1Parameters.Token.EnumValueOf(), oauth1Settings.RequestToken); 26 | } 27 | return Task.FromResult>(result); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/Dapplo.HttpExtensions.OAuth.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | An OAuth extension for Dapplo.HttpExtensions 4 | net472;netcoreapp3.1;net6.0-windows 5 | http;rest;dapplo 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Form 40 | 41 | 42 | Form 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/Desktop/OAuthLoginForm.Designer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Dapplo.Windows.EmbeddedBrowser; 5 | 6 | namespace Dapplo.HttpExtensions.OAuth.Desktop 7 | { 8 | sealed partial class OAuthLoginForm 9 | { 10 | /// 11 | /// Required designer variable. 12 | /// 13 | private System.ComponentModel.IContainer components = null; 14 | 15 | /// 16 | /// Clean up any resources being used. 17 | /// 18 | /// true if managed resources should be disposed; otherwise, false. 19 | protected override void Dispose(bool disposing) 20 | { 21 | if (disposing && (components != null)) 22 | { 23 | components.Dispose(); 24 | } 25 | base.Dispose(disposing); 26 | } 27 | 28 | #region Windows Form Designer generated code 29 | 30 | /// 31 | /// Required method for Designer support - do not modify 32 | /// the contents of this method with the code editor. 33 | /// 34 | private void InitializeComponent() 35 | { 36 | this._addressTextBox = new System.Windows.Forms.TextBox(); 37 | this._browser = new ExtendedWebBrowser(); 38 | this.SuspendLayout(); 39 | // 40 | // _addressTextBox 41 | // 42 | this._addressTextBox.Cursor = System.Windows.Forms.Cursors.Arrow; 43 | this._addressTextBox.Dock = System.Windows.Forms.DockStyle.Top; 44 | this._addressTextBox.Enabled = false; 45 | this._addressTextBox.Location = new System.Drawing.Point(0, 0); 46 | this._addressTextBox.Name = "addressTextBox"; 47 | this._addressTextBox.Size = new System.Drawing.Size(595, 20); 48 | this._addressTextBox.TabIndex = 3; 49 | this._addressTextBox.TabStop = false; 50 | // 51 | // _browser 52 | // 53 | _browser.Dock = System.Windows.Forms.DockStyle.Fill; 54 | this._browser.Location = new System.Drawing.Point(0, 20); 55 | this._browser.MinimumSize = new System.Drawing.Size(100, 100); 56 | this._browser.Name = "browser"; 57 | this._browser.Size = new System.Drawing.Size(595, 295); 58 | this._browser.TabIndex = 4; 59 | // 60 | // OAuthLoginForm 61 | // 62 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 63 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 64 | this.ClientSize = new System.Drawing.Size(595, 315); 65 | this.Controls.Add(this._browser); 66 | this.Controls.Add(this._addressTextBox); 67 | this.MaximizeBox = false; 68 | this.MinimizeBox = false; 69 | this.Name = "OAuthLoginForm"; 70 | this.ResumeLayout(false); 71 | this.PerformLayout(); 72 | 73 | } 74 | 75 | #endregion 76 | 77 | private System.Windows.Forms.TextBox _addressTextBox; 78 | private ExtendedWebBrowser _browser; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/Desktop/OAuthLoginForm.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Drawing; 5 | using System.Windows.Forms; 6 | using Dapplo.Windows.Dpi.Forms; 7 | using Dapplo.Windows.EmbeddedBrowser; 8 | using Dapplo.Windows.User32; 9 | 10 | namespace Dapplo.HttpExtensions.OAuth.Desktop; 11 | 12 | /// 13 | /// The OAuthLoginForm is used to allow the user to authorize Greenshot with an "Oauth" application 14 | /// 15 | public sealed partial class OAuthLoginForm : DpiAwareForm 16 | { 17 | #pragma warning disable IDE0090 // Use 'new(...)' 18 | private static readonly LogSource Log = new LogSource(); 19 | #pragma warning restore IDE0090 // Use 'new(...)' 20 | private readonly string _callbackUrl; 21 | 22 | /// 23 | /// Constructor for an OAuth login form 24 | /// 25 | /// title of the form 26 | /// size of the form 27 | /// Uri for the link 28 | /// Uri for the callback 29 | public OAuthLoginForm(string browserTitle, Size size, Uri authorizationLink, string callbackUrl) 30 | { 31 | // Make sure we use the most recent version of IE 32 | InternetExplorerVersion.ChangeEmbeddedVersion(); 33 | 34 | _callbackUrl = callbackUrl; 35 | InitializeComponent(); 36 | ClientSize = size; 37 | Text = browserTitle; 38 | _addressTextBox.Text = authorizationLink.ToString(); 39 | 40 | // The script errors are suppressed by using the ExtendedWebBrowser 41 | _browser.ScriptErrorsSuppressed = false; 42 | _browser.DocumentCompleted += Browser_DocumentCompleted; 43 | _browser.Navigated += Browser_Navigated; 44 | _browser.Navigating += Browser_Navigating; 45 | _browser.Navigate(authorizationLink); 46 | Load += OAuthLoginForm_Load; 47 | } 48 | 49 | /// 50 | /// the parameters which were supplied in the uri-callback from the server are stored here 51 | /// 52 | public IDictionary CallbackParameters { get; set; } 53 | 54 | /// 55 | /// Check if the dialog result was an ok 56 | /// 57 | public bool IsOk => DialogResult == DialogResult.OK; 58 | 59 | private void Browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 60 | { 61 | Log.Verbose().WriteLine("document completed with url: {0}", e.Url); 62 | CheckUrl(e.Url); 63 | } 64 | 65 | private void Browser_Navigated(object sender, WebBrowserNavigatedEventArgs e) 66 | { 67 | Log.Verbose().WriteLine("Navigated to url: {0}", e.Url); 68 | CheckUrl(e.Url); 69 | } 70 | 71 | private void Browser_Navigating(object sender, WebBrowserNavigatingEventArgs e) 72 | { 73 | Log.Verbose().WriteLine("Navigating to url: {0}", e.Url); 74 | _addressTextBox.Text = e.Url.ToString(); 75 | CheckUrl(e.Url); 76 | } 77 | 78 | private void CheckUrl(Uri uri) 79 | { 80 | if (uri is null) 81 | { 82 | return; 83 | } 84 | if (uri.AbsoluteUri.Contains("error")) 85 | { 86 | DialogResult = DialogResult.Abort; 87 | } 88 | else if (uri.AbsoluteUri.StartsWith(_callbackUrl)) 89 | { 90 | CallbackParameters = uri.QueryToDictionary(); 91 | DialogResult = DialogResult.OK; 92 | } 93 | } 94 | 95 | /// 96 | /// Make sure the form is visible 97 | /// 98 | /// sender object 99 | /// EventArgs 100 | private void OAuthLoginForm_Load(object sender, EventArgs e) 101 | { 102 | Visible = true; 103 | User32Api.SetForegroundWindow(Handle); 104 | } 105 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | global using System; 5 | global using System.Collections.Generic; 6 | global using System.Net; 7 | global using System.Net.Http; 8 | global using System.Linq; 9 | global using System.Reactive.Linq; 10 | global using System.Reactive.Threading.Tasks; 11 | global using System.Text.RegularExpressions; 12 | global using System.Text; 13 | global using System.Threading; 14 | global using System.Threading.Tasks; 15 | global using System.Runtime.Serialization; 16 | global using Dapplo.Log; 17 | global using Dapplo.HttpExtensions.Extensions; 18 | global using Dapplo.HttpExtensions.Factory; 19 | global using Dapplo.HttpExtensions.Listener; 20 | global using Dapplo.HttpExtensions.Support; 21 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/GrantTypes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Enum values for the OAuth grant types 8 | /// 9 | public enum GrantTypes 10 | { 11 | /// 12 | /// Requesting a Password 13 | /// 14 | [EnumMember(Value = "password")] Password, 15 | 16 | /// 17 | /// Requesting a refresh token 18 | /// 19 | [EnumMember(Value = "refresh_token")] RefreshToken, 20 | 21 | /// 22 | /// Requesting a authorization code 23 | /// 24 | [EnumMember(Value = "authorization_code")] AuthorizationCode 25 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/ICodeReceiverSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Settings interface the OAuth (2) protocol 8 | /// 9 | public interface ICodeReceiverSettings 10 | { 11 | /// 12 | /// The autorization Uri where the values of this class will be "injected" 13 | /// Example how this can be created: 14 | /// 15 | /// new Uri("http://server").AppendSegments("auth").Query("client_id", "{ClientId}"); 16 | /// 17 | /// 18 | Uri AuthorizationUri { get; set; } 19 | 20 | /// 21 | /// The OAuth (2) client id / consumer key 22 | /// 23 | string ClientId { get; set; } 24 | 25 | /// 26 | /// The OAuth (2) client secret / consumer secret 27 | /// The OAuth client/consumer secret 28 | /// For OAuth1SignatureTypes.RsaSha1 use RsaSha1Provider instead! 29 | /// 30 | string ClientSecret { get; set; } 31 | 32 | /// 33 | /// Specify the name of the cloud service, so it can be used in window titles, logs etc 34 | /// 35 | string CloudServiceName { get; set; } 36 | 37 | /// 38 | /// This can be used to specify the height of the embedded browser window, default is 400 39 | /// 40 | int EmbeddedBrowserHeight { get; set; } 41 | 42 | /// 43 | /// This can be used to specify the width of the embedded browser window, default is 600 44 | /// 45 | int EmbeddedBrowserWidth { get; set; } 46 | 47 | /// 48 | /// This is the redirect URL, in some implementations this is automatically set (LocalServerCodeReceiver) 49 | /// In some implementations this could be e.g. urn:ietf:wg:oauth:2.0:oob or urn:ietf:wg:oauth:2.0:oob:auto 50 | /// 51 | string RedirectUrl { get; set; } 52 | 53 | /// 54 | /// The OAuth (2) state, this is something that is passed to the server, is not processed but returned back to the 55 | /// client. 56 | /// e.g. a correlation ID 57 | /// Default this is filled with a new Guid 58 | /// 59 | string State { get; set; } 60 | 61 | /// 62 | /// A TaskScheduler which is used to schedule tasks on the UI 63 | /// 64 | TaskScheduler UiTaskScheduler { get; set; } 65 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/IOAuth1Token.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace Dapplo.HttpExtensions.OAuth; 7 | 8 | /// 9 | /// The credentials which should be stored. 10 | /// This can be used to extend your Dapplo.Config.IIniSection extending interface. 11 | /// 12 | public interface IOAuth1Token 13 | { 14 | /// 15 | /// Token for accessing OAuth services 16 | /// 17 | [Display(Description = "Contains the OAuth token (encrypted)")] 18 | string OAuthToken { get; set; } 19 | 20 | /// 21 | /// OAuth token secret 22 | /// 23 | [Display(Description = "OAuth token secret (encrypted)")] 24 | string OAuthTokenSecret { get; set; } 25 | 26 | /// 27 | /// OAuth token verifier 28 | /// 29 | [Display(Description = "OAuth token verifier (encrypted)")] 30 | string OAuthTokenVerifier { get; set; } 31 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/IOAuth2Token.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace Dapplo.HttpExtensions.OAuth; 7 | 8 | /// 9 | /// The credentials which should be stored. 10 | /// This can be used to extend your Dapplo.Config.IIniSection extending interface. 11 | /// 12 | public interface IOAuth2Token 13 | { 14 | /// 15 | /// Bearer token for accessing OAuth 2 services 16 | /// 17 | [Display(Description = "Contains the OAuth 2 access token (encrypted)")] 18 | string OAuth2AccessToken { get; set; } 19 | 20 | /// 21 | /// Expire time for the AccessToken, this time (-HttpExtensionsGlobals.OAuth2ExpireOffset) is check to know if a new 22 | /// AccessToken needs to be generated with the RefreshToken 23 | /// 24 | [Display(Description = "When does the OAuth 2 AccessToken expire")] 25 | DateTimeOffset OAuth2AccessTokenExpires { get; set; } 26 | 27 | /// 28 | /// Token used to get a new Access Token 29 | /// 30 | [Display(Description = "Contains the OAuth 2 refresh token (encrypted)")] 31 | string OAuth2RefreshToken { get; set; } 32 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/IOAuthCodeReceiver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// This is the interface for the OAuth code receiver 8 | /// 9 | public interface IOAuthCodeReceiver 10 | { 11 | /// 12 | /// The actual code receiving code 13 | /// 14 | /// AuthorizeModes will tell you for what mode you were called 15 | /// ICodeReceiverSettings with the settings for the code receiver 16 | /// CancellationToken 17 | /// Dictionary with the returned key-values 18 | Task> ReceiveCodeAsync(AuthorizeModes authorizeMode, ICodeReceiverSettings codeReceiverSettings, 19 | CancellationToken cancellationToken = default); 20 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth1HttpBehaviourFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// This factory can be used to create a IHttpBehaviour which handles OAuth requests 8 | /// 9 | public static class OAuth1HttpBehaviourFactory 10 | { 11 | /// 12 | /// Create a specify OAuth IHttpBehaviour 13 | /// 14 | /// OAuthSettings 15 | /// IHttpBehaviour or null 16 | /// IHttpBehaviour 17 | public static OAuth1HttpBehaviour Create(OAuth1Settings oAuthSettings, IHttpBehaviour fromHttpBehaviour = null) 18 | { 19 | // Get a clone of a IHttpBehaviour (passed or current) 20 | var oauthHttpBehaviour = new OAuth1HttpBehaviour(fromHttpBehaviour); 21 | // Add a wrapper (delegate handler) which wraps all new HttpMessageHandlers 22 | oauthHttpBehaviour.ChainOnHttpMessageHandlerCreated(httpMessageHandler => new OAuth1HttpMessageHandler(oAuthSettings, oauthHttpBehaviour, httpMessageHandler)); 23 | return oauthHttpBehaviour; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth1Parameters.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Enum with all the parameters for OAuth 1 8 | /// 9 | public enum OAuth1Parameters 10 | { 11 | /// 12 | /// Consumer key is the key which a service provides for it's consumers 13 | /// 14 | [EnumMember(Value = "oauth_consumer_key")] ConsumerKey, 15 | 16 | /// 17 | /// Callback Uri 18 | /// 19 | [EnumMember(Value = "oauth_callback")] Callback, 20 | 21 | /// 22 | /// Used version 23 | /// 24 | [EnumMember(Value = "oauth_version")] Version, 25 | 26 | /// 27 | /// Signing method 28 | /// 29 | [EnumMember(Value = "oauth_signature_method")] SignatureMethod, 30 | 31 | /// 32 | /// Timestamp of the request 33 | /// 34 | [EnumMember(Value = "oauth_timestamp")] Timestamp, 35 | 36 | /// 37 | /// A unique code 38 | /// 39 | [EnumMember(Value = "oauth_nonce")] Nonce, 40 | 41 | /// 42 | /// Token 43 | /// 44 | [EnumMember(Value = "oauth_token")] Token, 45 | 46 | /// 47 | /// Token verifier 48 | /// 49 | [EnumMember(Value = "oauth_verifier")] Verifier, 50 | 51 | /// 52 | /// Token secret 53 | /// 54 | [EnumMember(Value = "oauth_token_secret")] TokenSecret, 55 | 56 | /// 57 | /// Signature for the request 58 | /// 59 | [EnumMember(Value = "oauth_signature")] Signature 60 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth1RequestConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Specify OAuth 1 request configuration 8 | /// 9 | public class OAuth1RequestConfiguration : IHttpRequestConfiguration 10 | { 11 | /// 12 | /// The OAuth parameters for this request 13 | /// 14 | public IDictionary Parameters { get; set; } = new Dictionary(); 15 | 16 | /// 17 | /// Name of the configuration, this should be unique and usually is the type of the object 18 | /// 19 | public string Name { get; } = nameof(OAuth1RequestConfiguration); 20 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth1Settings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Security.Cryptography; 5 | 6 | namespace Dapplo.HttpExtensions.OAuth; 7 | 8 | /// 9 | /// Settings for the OAuth protocol, if possible this should not be used and OAuth 2.0 is a better choice 10 | /// 11 | public class OAuth1Settings : BaseOAuthSettings 12 | { 13 | /// 14 | /// The HttpMethod which is used for getting the access token 15 | /// 16 | public HttpMethod AccessTokenMethod { get; set; } = HttpMethod.Get; 17 | 18 | /// 19 | /// The URL to get an access token 20 | /// 21 | public Uri AccessTokenUrl { get; set; } 22 | 23 | /// 24 | /// OAuth authorize token 25 | /// 26 | public string AuthorizeToken { get; internal set; } 27 | 28 | /// 29 | /// If this is set, the value of the verifier will be validated (not null) 30 | /// 31 | public bool CheckVerifier { get; set; } = true; 32 | 33 | 34 | /// 35 | /// OAuth request token 36 | /// 37 | public string RequestToken { get; internal set; } 38 | 39 | /// 40 | /// OAuth request token secret 41 | /// 42 | public string RequestTokenSecret { get; internal set; } 43 | 44 | /// 45 | /// The type of signature that is used, mostly this is HMacSha1 46 | /// 47 | public OAuth1SignatureTypes SignatureType { get; set; } = OAuth1SignatureTypes.HMacSha1; 48 | 49 | /// 50 | /// For OAuth1SignatureTypes.RsaSha1 set this 51 | /// 52 | public RSACryptoServiceProvider RsaSha1Provider { get; set; } 53 | 54 | /// 55 | /// The actualy token information, placed in an interface for usage with the Dapplo.Config project 56 | /// the OAuthToken, a default implementation is assigned when the settings are created. 57 | /// When using a Dapplo.Config IIniSection for your settings, this property can/should be overwritten with an instance 58 | /// of your interface by makeing it extend IOAuthToken 59 | /// 60 | public IOAuth1Token Token { get; set; } = new OAuth1Token(); 61 | 62 | /// 63 | /// The HttpMethod which is used for getting the token 64 | /// 65 | public HttpMethod TokenMethod { get; set; } = HttpMethod.Post; 66 | 67 | /// 68 | /// This defines the transport "way" which the OAuth signature takes. 69 | /// Default is OAuth1SignatureTransports.Headers, which is normal, but SOME systems want the information in the query parameters. 70 | /// (An example is Atlassians Confluence) 71 | /// 72 | public OAuth1SignatureTransports SignatureTransport { get; set; } = OAuth1SignatureTransports.Headers; 73 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth1SignatureTransports.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Used to define the transport which the signature takes, in the headers or as query parameters 8 | /// 9 | public enum OAuth1SignatureTransports 10 | { 11 | /// 12 | /// Place the signature information in the headers of the request, this is the default 13 | /// 14 | Headers, 15 | 16 | /// 17 | /// Place the signature information in the query parameters of the request 18 | /// 19 | QueryParameters 20 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth1SignatureTypes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Provides a predefined set of algorithms that are supported officially by the OAuth 1.x protocol 8 | /// 9 | public enum OAuth1SignatureTypes 10 | { 11 | /// 12 | /// Hash-based Message Authentication Code (HMAC) using the SHA1 hash function. 13 | /// 14 | [EnumMember(Value = "HMAC-SHA1")] HMacSha1, 15 | 16 | /// 17 | /// The PLAINTEXT method does not provide any security protection and SHOULD only be used over a secure channel such as 18 | /// HTTPS. It does not use the Signature Base String. 19 | /// 20 | [EnumMember(Value = "PLAINTEXT")] PlainText, 21 | 22 | /// 23 | /// RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in [RFC3447] section 8.2 (more 24 | /// simply known as PKCS#1) 25 | /// 26 | [EnumMember(Value = "RSA-SHA1")] RsaSha1 27 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth1Token.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// A default implementation for the IOAuthToken, nothing fancy 8 | /// For more information, see the IOAuthToken interface 9 | /// 10 | public class OAuth1Token : IOAuth1Token 11 | { 12 | /// 13 | public string OAuthTokenSecret { get; set; } 14 | 15 | /// 16 | public string OAuthToken { get; set; } 17 | 18 | /// 19 | public string OAuthTokenVerifier { get; set; } 20 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth2Fields.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// This enum is used internally for the mapping of the field names 8 | /// 9 | internal enum OAuth2Fields 10 | { 11 | [EnumMember(Value = "refresh_token")] RefreshToken, 12 | [EnumMember(Value = "code")] Code, 13 | [EnumMember(Value = "client_id")] ClientId, 14 | [EnumMember(Value = "client_secret")] ClientSecret, 15 | [EnumMember(Value = "grant_type")] GrantType, 16 | [EnumMember(Value = "redirect_uri")] RedirectUri, 17 | [EnumMember(Value = "error")] Error, 18 | [EnumMember(Value = "error_description")] ErrorDescription 19 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth2HttpBehaviourFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// This factory can be used to create a IHttpBehaviour which handles OAuth 2 requests 8 | /// 9 | public static class OAuth2HttpBehaviourFactory 10 | { 11 | /// 12 | /// Create a specify OAuth2 IHttpBehaviour 13 | /// 14 | /// OAuth2Settings 15 | /// IHttpBehaviour to clone, null if a new needs to be generated 16 | /// IChangeableHttpBehaviour 17 | public static IChangeableHttpBehaviour Create(OAuth2Settings oAuth2Settings, IHttpBehaviour fromHttpBehaviour = null) 18 | { 19 | // Get a clone of a IHttpBehaviour (passed or current) 20 | var oauthHttpBehaviour = (fromHttpBehaviour ?? HttpBehaviour.Current).ShallowClone(); 21 | // Add a wrapper (delegate handler) which wraps all new HttpMessageHandlers 22 | oauthHttpBehaviour.ChainOnHttpMessageHandlerCreated(httpMessageHandler => new OAuth2HttpMessageHandler(oAuth2Settings, oauthHttpBehaviour, httpMessageHandler)); 23 | return oauthHttpBehaviour; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth2Settings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Settings for the OAuth 2 protocol 8 | /// 9 | public class OAuth2Settings : BaseOAuthSettings 10 | { 11 | /// 12 | /// This contains the code returned from the authorization, but only shortly after it was received. 13 | /// It will be cleared as soon as it was used. 14 | /// 15 | internal string Code { get; set; } 16 | 17 | /// 18 | /// Return true if the access token is expired. 19 | /// Important "side-effect": if true is returned the AccessToken will be set to null! 20 | /// 21 | public bool IsAccessTokenExpired 22 | { 23 | get 24 | { 25 | var expired = Token.IsAccessTokenExpired(); 26 | 27 | // Make sure the token is not usable 28 | if (expired) 29 | { 30 | Token.OAuth2AccessToken = null; 31 | } 32 | return expired; 33 | } 34 | } 35 | 36 | /// 37 | /// The actualy token information, placed in an interface for usage with the Dapplo.Config project 38 | /// the OAuth2Token, a default implementation is assigned when the settings are created. 39 | /// When using a Dapplo.Config IIniSection for your settings, this can/should be overwritten with your interface, and 40 | /// make it extend IOAuth2Token 41 | /// 42 | public IOAuth2Token Token { get; set; } = new OAuth2TokenInformation(); 43 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth2TokenInformation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// A default implementation for the IOAuth2Token, nothing fancy 8 | /// For more information, see the IOAuth2Token interface 9 | /// 10 | internal class OAuth2TokenInformation : IOAuth2Token 11 | { 12 | public string OAuth2AccessToken { get; set; } 13 | 14 | public DateTimeOffset OAuth2AccessTokenExpires { get; set; } 15 | 16 | public string OAuth2RefreshToken { get; set; } 17 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuth2TokenResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Container for the OAuth token / refresh response 8 | /// 9 | [DataContract] 10 | public class OAuth2TokenResponse 11 | { 12 | private readonly DateTimeOffset _responseTime = DateTimeOffset.Now; 13 | 14 | /// 15 | /// The access token 16 | /// 17 | [DataMember(Name = "access_token")] 18 | public string AccessToken { get; set; } 19 | 20 | /// 21 | /// The error 22 | /// 23 | [DataMember(Name = "error")] 24 | public string Error { get; set; } 25 | 26 | /// 27 | /// Details to the error 28 | /// 29 | [DataMember(Name = "error_description")] 30 | public string ErrorDescription { get; set; } 31 | 32 | /// 33 | /// Returns the time that the token expires 34 | /// 35 | public DateTimeOffset Expires => _responseTime.AddSeconds(ExpiresInSeconds); 36 | 37 | /// 38 | /// DateTimeOffset.Now.AddSeconds(expiresIn) 39 | /// 40 | [DataMember(Name = "expires_in")] 41 | public long ExpiresInSeconds { get; set; } 42 | 43 | /// 44 | /// Test if the response has an error 45 | /// 46 | public bool HasError => !string.IsNullOrEmpty(Error); 47 | 48 | /// 49 | /// Test if the error is an invalid grant 50 | /// 51 | public bool IsInvalidGrant => HasError && Error == "invalid_grant"; 52 | 53 | /// 54 | /// Refresh token, used to get a new access token 55 | /// 56 | [DataMember(Name = "refresh_token")] 57 | public string RefreshToken { get; set; } 58 | 59 | /// 60 | /// Type for the token 61 | /// 62 | [DataMember(Name = "token_type")] 63 | public string TokenType { get; set; } 64 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.OAuth/OAuthExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.OAuth; 5 | 6 | /// 7 | /// Helper method for OAuth 8 | /// 9 | public static class OAuthExtensions 10 | { 11 | /// 12 | /// Test if the IOAuth1Token has a token set 13 | /// 14 | /// IOAuth1Token 15 | /// true if the specified IOAuth1Token has token information 16 | public static bool HasToken(this IOAuth1Token oAuth1Token) 17 | { 18 | return !string.IsNullOrEmpty(oAuth1Token.OAuthToken) || 19 | !string.IsNullOrEmpty(oAuth1Token.OAuthTokenSecret); 20 | } 21 | 22 | /// 23 | /// Reset the oauth token information 24 | /// 25 | /// IOAuth1Token 26 | public static void ResetToken(this IOAuth1Token oAuth1Token) 27 | { 28 | oAuth1Token.OAuthToken = null; 29 | oAuth1Token.OAuthTokenSecret = null; 30 | oAuth1Token.OAuthTokenVerifier = null; 31 | } 32 | 33 | /// 34 | /// Test if the IOAuth2Token has a token set 35 | /// 36 | /// IOAuth2Token 37 | /// true if the specified IOAuth2Token has token information 38 | public static bool HasToken(this IOAuth2Token oAuth2Token) 39 | { 40 | return !string.IsNullOrEmpty(oAuth2Token.OAuth2AccessToken) || 41 | !string.IsNullOrEmpty(oAuth2Token.OAuth2RefreshToken); 42 | } 43 | 44 | /// 45 | /// Reset the oauth token information 46 | /// 47 | /// 48 | public static void ResetToken(this IOAuth2Token oAuth2Token) 49 | { 50 | oAuth2Token.OAuth2AccessToken = null; 51 | oAuth2Token.OAuth2RefreshToken = null; 52 | oAuth2Token.OAuth2AccessTokenExpires = default; 53 | } 54 | 55 | /// 56 | /// Test if the access token is expired 57 | /// 58 | /// IOAuth2Token 59 | /// True if the token is expired 60 | public static bool IsAccessTokenExpired(this IOAuth2Token oAuth2Token) 61 | { 62 | if (string.IsNullOrEmpty(oAuth2Token.OAuth2AccessToken) || oAuth2Token.OAuth2AccessTokenExpires == default) 63 | { 64 | return false; 65 | } 66 | 67 | return DateTimeOffset.Now.AddSeconds(HttpExtensionsGlobals.OAuth2ExpireOffset) > oAuth2Token.OAuth2AccessTokenExpires; 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.SystemTextJson/Dapplo.HttpExtensions.SystemTextJson.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | System.Text.Json functionality for Dapplo.HttpExtensions 4 | net471;netstandard2.0;net6.0 5 | http;rest;dapplo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.SystemTextJson/SystemTextJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | #if NETFRAMEWORK 10 | using System.Drawing; 11 | using System.Windows.Media.Imaging; 12 | #endif 13 | 14 | namespace Dapplo.HttpExtensions.SystemTextJson 15 | { 16 | /// 17 | /// Made to have Dapplo.HttpExtension use System.Text.Json 18 | /// 19 | public class SystemTextJsonSerializer : IJsonSerializer 20 | { 21 | private static readonly Type[] NotSerializableTypes = 22 | { 23 | #if NETFRAMEWORK 24 | typeof(Bitmap), 25 | typeof(BitmapSource), 26 | #endif 27 | typeof(Stream), 28 | typeof(MemoryStream) 29 | }; 30 | 31 | /// 32 | /// Register this IJsonSerializer 33 | /// 34 | /// bool to specify if this also needs to be set when another serializer is already specified 35 | public static void RegisterGlobally(bool force = true) 36 | { 37 | if (force || HttpExtensionsGlobals.JsonSerializer != null) 38 | { 39 | HttpExtensionsGlobals.JsonSerializer = new SystemTextJsonSerializer(); 40 | } 41 | } 42 | 43 | /// 44 | /// The JsonSerializerOptions used in the JsonSerializer 45 | /// 46 | public JsonSerializerOptions Options { get; set; } = new JsonSerializerOptions 47 | { 48 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull 49 | }; 50 | 51 | 52 | /// 53 | /// Test if the specified type can be serialized to JSON 54 | /// 55 | /// Type to check 56 | /// bool 57 | public bool CanSerializeTo(Type sourceType) 58 | { 59 | return NotSerializableTypes.All(type => type != sourceType); 60 | } 61 | 62 | /// 63 | /// Test if the specified type can be deserialized 64 | /// 65 | /// Type to check 66 | /// bool 67 | public bool CanDeserializeFrom(Type targetType) 68 | { 69 | return NotSerializableTypes.All(type => type != targetType); 70 | } 71 | 72 | /// 73 | /// Deserialize the specified json string into the target type 74 | /// 75 | /// Type 76 | /// string 77 | /// object 78 | public object Deserialize(Type targetType, string jsonString) 79 | { 80 | return JsonSerializer.Deserialize(jsonString, targetType, Options); 81 | } 82 | 83 | /// 84 | /// Serialize the passed object into a json string 85 | /// 86 | /// object of type T 87 | /// type for the object to serialize 88 | /// string 89 | public string Serialize(T jsonObject) 90 | { 91 | return JsonSerializer.Serialize(jsonObject, Options); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/Dapplo.HttpExtensions.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Tests for Dapplo.HttpExtensions 4 | netcoreapp3.1;net6.0-windows 5 | true 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/HttpBehaviourExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Drawing; 6 | #if NETFRAMEWORK 7 | using System.Net.Cache; 8 | #endif 9 | using System.Threading.Tasks; 10 | using Dapplo.HttpExtensions.WinForms.ContentConverter; 11 | using Dapplo.Log; 12 | using Dapplo.Log.XUnit; 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | 16 | namespace Dapplo.HttpExtensions.Tests; 17 | 18 | /// 19 | /// Some tests which use http://httpbin.org/ 20 | /// 21 | public class HttpBehaviourExtensionsTests 22 | { 23 | private readonly Uri _bitmapUri = new("http://httpbin.org/image/png"); 24 | 25 | public HttpBehaviourExtensionsTests(ITestOutputHelper testOutputHelper) 26 | { 27 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 28 | BitmapHttpContentConverter.RegisterGlobally(); 29 | #if NETFRAMEWORK 30 | HttpExtensionsGlobals.HttpSettings.RequestCacheLevel = RequestCacheLevel.NoCacheNoStore; 31 | #endif 32 | } 33 | 34 | /// 35 | /// Test POST 36 | /// 37 | [Fact] 38 | public async Task TestHttpBehaviourChaining() 39 | { 40 | bool testChainOnHttpContentCreated1 = false; 41 | bool testChainOnHttpContentCreated2 = false; 42 | 43 | bool testChainOnHttpRequestMessageCreated1 = false; 44 | bool testChainOnHttpRequestMessageCreated2 = false; 45 | 46 | bool testChainOnHttpMessageHandlerCreated1 = false; 47 | bool testChainOnHttpMessageHandlerCreated2 = false; 48 | 49 | var testBehaviour = HttpBehaviour.Current.ShallowClone(); 50 | 51 | testBehaviour.ChainOnHttpContentCreated(x => 52 | { 53 | testChainOnHttpContentCreated1 = true; 54 | return x; 55 | }); 56 | 57 | // Test if the chaining chains 58 | testBehaviour.ChainOnHttpContentCreated(x => 59 | { 60 | // The previous Func should be called before this 61 | Assert.True(testChainOnHttpContentCreated1); 62 | testChainOnHttpContentCreated2 = true; 63 | return x; 64 | }); 65 | 66 | testBehaviour.ChainOnHttpRequestMessageCreated(x => 67 | { 68 | testChainOnHttpRequestMessageCreated1 = true; 69 | return x; 70 | }); 71 | 72 | // Test if the chaining chains 73 | testBehaviour.ChainOnHttpRequestMessageCreated(x => 74 | { 75 | // The previous Func should be called before this 76 | Assert.True(testChainOnHttpRequestMessageCreated1); 77 | testChainOnHttpRequestMessageCreated2 = true; 78 | return x; 79 | }); 80 | 81 | testBehaviour.ChainOnHttpMessageHandlerCreated(x => 82 | { 83 | testChainOnHttpMessageHandlerCreated1 = true; 84 | return x; 85 | }); 86 | 87 | // Test if the chaining chains 88 | testBehaviour.ChainOnHttpMessageHandlerCreated(x => 89 | { 90 | // The previous Func should be called before this 91 | Assert.True(testChainOnHttpMessageHandlerCreated1); 92 | testChainOnHttpMessageHandlerCreated2 = true; 93 | return x; 94 | }); 95 | 96 | testBehaviour.MakeCurrent(); 97 | 98 | using (var bitmap = await _bitmapUri.GetAsAsync()) 99 | { 100 | await new Uri("https://httpbin.org/post").PostAsync(bitmap); 101 | } 102 | Assert.True(testChainOnHttpContentCreated1); 103 | Assert.True(testChainOnHttpContentCreated2); 104 | Assert.True(testChainOnHttpRequestMessageCreated1); 105 | Assert.True(testChainOnHttpRequestMessageCreated2); 106 | Assert.True(testChainOnHttpMessageHandlerCreated1); 107 | Assert.True(testChainOnHttpMessageHandlerCreated2); 108 | } 109 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/HttpPartsPostTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Drawing; 6 | using System.Threading.Tasks; 7 | using System.Windows.Media; 8 | using System.Windows.Media.Imaging; 9 | using Dapplo.HttpExtensions.JsonSimple; 10 | using Dapplo.HttpExtensions.Tests.TestEntities; 11 | using Dapplo.HttpExtensions.WinForms.ContentConverter; 12 | using Dapplo.HttpExtensions.Wpf.ContentConverter; 13 | using Dapplo.Log; 14 | using Dapplo.Log.XUnit; 15 | using Xunit; 16 | using Xunit.Abstractions; 17 | 18 | namespace Dapplo.HttpExtensions.Tests; 19 | 20 | /// 21 | /// Test posting parts 22 | /// 23 | public class HttpPartsPostTest 24 | { 25 | private static readonly LogSource Log = new(); 26 | private static readonly Uri RequestBinUri = new("http://httpbin.org"); 27 | 28 | public HttpPartsPostTest(ITestOutputHelper testOutputHelper) 29 | { 30 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 31 | SimpleJsonSerializer.RegisterGlobally(); 32 | BitmapHttpContentConverter.RegisterGlobally(); 33 | BitmapSourceHttpContentConverter.RegisterGlobally(); 34 | } 35 | 36 | /// 37 | /// Test posting, using Bitmap 38 | /// 39 | [Fact] 40 | public async Task TestPost_Bitmap() 41 | { 42 | var testUri = RequestBinUri.AppendSegments("post"); 43 | var uploadBehaviour = HttpBehaviour.Current.ShallowClone(); 44 | 45 | bool hasProgress = false; 46 | 47 | uploadBehaviour.UseProgressStream = true; 48 | uploadBehaviour.UploadProgress += progress => 49 | { 50 | Log.Info().WriteLine("Progress {0}", (int) (progress * 100)); 51 | hasProgress = true; 52 | }; 53 | uploadBehaviour.MakeCurrent(); 54 | var testObject = new MyMultiPartRequest 55 | { 56 | BitmapContentName = "MyBitmapContent", 57 | BitmapFileName = "MyBitmapFilename", 58 | OurBitmap = new Bitmap(10, 10), 59 | JsonInformation = new GitHubError {DocumentationUrl = "http://test.de", Message = "Hello"} 60 | }; 61 | testObject.Headers.Add("Name", "Dapplo"); 62 | var result = await testUri.PostAsync(testObject); 63 | Assert.NotNull(result); 64 | Assert.True(hasProgress); 65 | } 66 | 67 | /// 68 | /// Test posting, this time use a BitmapSource 69 | /// 70 | [Fact] 71 | public async Task TestPost_BitmapSource() 72 | { 73 | var testUri = RequestBinUri.AppendSegments("post"); 74 | var uploadBehaviour = HttpBehaviour.Current.ShallowClone(); 75 | 76 | bool hasProgress = false; 77 | 78 | uploadBehaviour.UseProgressStream = true; 79 | uploadBehaviour.UploadProgress += progress => 80 | { 81 | Log.Info().WriteLine("Progress {0}", (int) (progress * 100)); 82 | hasProgress = true; 83 | }; 84 | uploadBehaviour.MakeCurrent(); 85 | var testObject = new MyMultiPartRequest 86 | { 87 | BitmapContentName = "MyBitmapContent", 88 | BitmapFileName = "MyBitmapFilename", 89 | OurBitmap = BitmapSource.Create(1, 1, 96, 96, PixelFormats.Bgr24, null, new byte[] {0, 0, 0}, 3), 90 | JsonInformation = new GitHubError {DocumentationUrl = "http://test.de", Message = "Hello"} 91 | }; 92 | testObject.Headers.Add("Name", "Dapplo"); 93 | var result = await testUri.PostAsync(testObject); 94 | Assert.NotNull(result); 95 | Assert.True(hasProgress); 96 | } 97 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/HttpRequestConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Drawing.Imaging; 5 | #if NETFRAMEWORK 6 | using System.Net.Cache; 7 | #endif 8 | using Dapplo.HttpExtensions.Extensions; 9 | using Dapplo.HttpExtensions.WinForms.ContentConverter; 10 | using Dapplo.Log; 11 | using Dapplo.Log.XUnit; 12 | using Xunit; 13 | using Xunit.Abstractions; 14 | 15 | namespace Dapplo.HttpExtensions.Tests; 16 | 17 | public class HttpRequestConfigurationTests 18 | { 19 | public HttpRequestConfigurationTests(ITestOutputHelper testOutputHelper) 20 | { 21 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 22 | #if NETFRAMEWORK 23 | HttpExtensionsGlobals.HttpSettings.RequestCacheLevel = RequestCacheLevel.NoCacheNoStore; 24 | #endif 25 | } 26 | 27 | /// 28 | /// Test posting, using Bitmap 29 | /// 30 | [Fact] 31 | public void Test_GetSet() 32 | { 33 | var httpBehaviour = HttpBehaviour.Current; 34 | var testConfig = new BitmapConfiguration {Format = ImageFormat.Gif}; 35 | Assert.Equal(ImageFormat.Gif, testConfig.Format); 36 | httpBehaviour.SetConfig(testConfig); 37 | Assert.Equal(ImageFormat.Gif, testConfig.Format); 38 | var retrievedConfig = httpBehaviour.GetConfig(); 39 | Assert.Equal(ImageFormat.Gif, retrievedConfig.Format); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/HttpRequestMessageExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | #if NETFRAMEWORK 6 | using System.Net.Cache; 7 | #endif 8 | using System.Threading.Tasks; 9 | using Dapplo.HttpExtensions.Factory; 10 | using Dapplo.Log; 11 | using Dapplo.Log.XUnit; 12 | using Xunit; 13 | using Xunit.Abstractions; 14 | 15 | namespace Dapplo.HttpExtensions.Tests; 16 | 17 | /// 18 | /// Testing HttpRequestMessageExtensions 19 | /// 20 | public class HttpRequestMessageExtensionsTests 21 | { 22 | public HttpRequestMessageExtensionsTests(ITestOutputHelper testOutputHelper) 23 | { 24 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 25 | #if NETFRAMEWORK 26 | HttpExtensionsGlobals.HttpSettings.RequestCacheLevel = RequestCacheLevel.NoCacheNoStore; 27 | #endif 28 | } 29 | 30 | /// 31 | /// Test getting the uri as Bitmap 32 | /// 33 | [Fact] 34 | public async Task TestSendAsync() 35 | { 36 | var testUri = new Uri("http://httpbin.org/xml"); 37 | var httpRequestMessage = HttpRequestMessageFactory.CreateGet(testUri); 38 | var result = await httpRequestMessage.SendAsync(); 39 | Assert.NotNull(result); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/OAuth/OAuth2Tests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | using Dapplo.HttpExtensions.OAuth; 9 | using Dapplo.Log; 10 | using Dapplo.Log.XUnit; 11 | using Xunit; 12 | using Xunit.Abstractions; 13 | 14 | namespace Dapplo.HttpExtensions.Tests.OAuth; 15 | 16 | /// 17 | /// This test is more an integration test, SHOULD NOT RUN on a headless server, as it opens a browser where a user 18 | /// should do something 19 | /// 20 | public class OAuth2Tests 21 | { 22 | private static readonly Uri GoogleApiUri = new("https://www.googleapis.com"); 23 | private readonly IHttpBehaviour _oAuthHttpBehaviour; 24 | 25 | public OAuth2Tests(ITestOutputHelper testOutputHelper) 26 | { 27 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 28 | var oAuth2Settings = new OAuth2Settings 29 | { 30 | ClientId = "", 31 | ClientSecret = "", 32 | CloudServiceName = "Google", 33 | AuthorizeMode = AuthorizeModes.LocalhostServer, 34 | TokenUrl = GoogleApiUri.AppendSegments("oauth2", "v4", "token"), 35 | AuthorizationUri = new Uri("https://accounts.google.com").AppendSegments("o", "oauth2", "v2", "auth") 36 | .ExtendQuery(new Dictionary 37 | { 38 | {"response_type", "code"}, 39 | {"client_id", "{ClientId}"}, 40 | {"redirect_uri", "{RedirectUrl}"}, 41 | {"state", "{State}"}, 42 | {"scope", GoogleApiUri.AppendSegments("auth", "calendar").AbsoluteUri} 43 | }) 44 | }; 45 | _oAuthHttpBehaviour = OAuth2HttpBehaviourFactory.Create(oAuth2Settings); 46 | } 47 | 48 | /// 49 | /// This will test Oauth with a LocalServer "code" receiver against a demo oauth server provided by brentertainment.com 50 | /// 51 | /// Task 52 | //[Fact] 53 | #pragma warning disable xUnit1013 // Public method should be marked as test 54 | public async Task TestOAuth2HttpMessageHandler() 55 | #pragma warning restore xUnit1013 // Public method should be marked as test 56 | { 57 | var calendarApiUri = GoogleApiUri.AppendSegments("calendar", "v3"); 58 | // Make sure you use your special IHttpBehaviour before the requests which need OAuth 59 | _oAuthHttpBehaviour.MakeCurrent(); 60 | var response = await calendarApiUri.AppendSegments("users", "me", "calendarList").GetAsAsync(); 61 | Assert.True(response.ContainsKey("items")); 62 | Assert.True(response["items"].Count > 0); 63 | } 64 | 65 | [Fact] 66 | public void TestOutOfBoundCodeReceiverParseTitle() 67 | { 68 | var queryPartOfTitleRegEx = new Regex(@".*\|\|(?.*)\|\|.*", RegexOptions.IgnoreCase); 69 | var state = Guid.NewGuid().ToString(); 70 | const string code = "2497hf29ruh234zruif390ugo34t23jfg23"; 71 | var query = $"state={state}&code={code}"; 72 | var testString = $"Greenshot authenticated with Imgur||{query}|| - Google Chrome"; 73 | Assert.False(string.IsNullOrEmpty(testString)); 74 | var match = queryPartOfTitleRegEx.Match(testString); 75 | Assert.True(match.Success); 76 | var queryParameters = match.Groups["query"]?.Value; 77 | Assert.NotNull(queryParameters); 78 | Assert.NotEmpty(queryParameters); 79 | Assert.Equal(query, queryParameters); 80 | var dict = UriParseExtensions.QueryStringToDictionary(queryParameters); 81 | Assert.Equal(code, dict["code"]); 82 | Assert.Equal(state, dict["state"]); 83 | 84 | } 85 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/OAuth/OAuthTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Net.Http; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Dapplo.HttpExtensions.OAuth; 10 | using Dapplo.Log; 11 | using Dapplo.Log.XUnit; 12 | using Xunit; 13 | using Xunit.Abstractions; 14 | 15 | namespace Dapplo.HttpExtensions.Tests.OAuth; 16 | 17 | /// 18 | /// This tests some of the basic oauth 1 logic, together with a server at: http://term.ie/oauth/example/ 19 | /// 20 | public class OAuthTests 21 | { 22 | private static readonly Uri OAuthTestServerUri = new("http://term.ie/oauth/example/"); 23 | private readonly IHttpBehaviour _oAuthHttpBehaviour; 24 | 25 | public OAuthTests(ITestOutputHelper testOutputHelper) 26 | { 27 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 28 | var oAuthSettings = new OAuth1Settings 29 | { 30 | ClientId = "key", 31 | ClientSecret = "secret", 32 | AuthorizeMode = AuthorizeModes.TestPassThrough, 33 | TokenUrl = OAuthTestServerUri.AppendSegments("request_token.php"), 34 | TokenMethod = HttpMethod.Post, 35 | AccessTokenUrl = OAuthTestServerUri.AppendSegments("access_token.php"), 36 | AccessTokenMethod = HttpMethod.Post, 37 | CheckVerifier = false 38 | }; 39 | _oAuthHttpBehaviour = OAuth1HttpBehaviourFactory.Create(oAuthSettings); 40 | } 41 | 42 | [Fact] 43 | public void TestHmacSha1Hash() 44 | { 45 | // See: http://oauth.net/core/1.0a/#RFC2104 46 | var hmacsha1 = new HMACSHA1 {Key = Encoding.UTF8.GetBytes("kd94hf93k423kf44&pfkkdhi9sl3r4s00")}; 47 | var digest = OAuth1HttpMessageHandler.ComputeHash(hmacsha1, 48 | "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal"); 49 | 50 | Assert.Equal("tR3+Ty81lMeYAr/Fid0kMTYa/WM=", digest); 51 | } 52 | 53 | /// 54 | /// This will test Oauth with http://term.ie/oauth/example/ 55 | /// 56 | /// Task 57 | //[Fact] // Disabled as term.ie is no longer available 58 | #pragma warning disable xUnit1013 // Public method should be marked as test 59 | public async Task TestOAuthHttpMessageHandler_Get() 60 | #pragma warning restore xUnit1013 // Public method should be marked as test 61 | { 62 | var userInformationUri = OAuthTestServerUri.AppendSegments("echo_api.php").ExtendQuery("name", "dapplo"); 63 | 64 | // Make sure you use your special IHttpBehaviour for the OAuth requests! 65 | _oAuthHttpBehaviour.MakeCurrent(); 66 | var response = await userInformationUri.GetAsAsync(); 67 | 68 | Assert.Contains("dapplo", response); 69 | } 70 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/Support/UriHttpListenerExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Threading.Tasks; 5 | using Dapplo.HttpExtensions.Listener; 6 | using Dapplo.Log; 7 | using Dapplo.Log.XUnit; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace Dapplo.HttpExtensions.Tests.Support; 12 | 13 | public class UriHttpListenerExtensionsTests 14 | { 15 | public UriHttpListenerExtensionsTests(ITestOutputHelper testOutputHelper) 16 | { 17 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 18 | } 19 | 20 | [Fact] 21 | public async Task TestListenAsync() 22 | { 23 | // Try listening on 8080, if this doesn't work take the first free port 24 | var listenUri = new[] {8080, 0}.CreateLocalHostUri().AppendSegments("AsyncHttpListenerTests"); 25 | var listenTask = listenUri.ListenAsync(async httpListenerContext => 26 | { 27 | // Process the request 28 | var httpListenerRequest = httpListenerContext.Request; 29 | var result = httpListenerRequest.Url.QueryToDictionary(); 30 | await httpListenerContext.RespondAsync("OK"); 31 | return result; 32 | }); 33 | // Do we need a delay for the listener to be ready? 34 | //await Task.Delay(100); 35 | var testUri = listenUri.ExtendQuery("name", "dapplo"); 36 | var okResponse = await testUri.GetAsAsync(); 37 | Assert.Equal("OK", okResponse); 38 | var actionResult = await listenTask; 39 | Assert.True(actionResult.ContainsKey("name")); 40 | Assert.True(actionResult["name"] == "dapplo"); 41 | } 42 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/TestEntities/GitHubError.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Runtime.Serialization; 5 | 6 | namespace Dapplo.HttpExtensions.Tests.TestEntities; 7 | 8 | /// 9 | /// Container for errors from GitHub 10 | /// 11 | [DataContract] 12 | public class GitHubError 13 | { 14 | [DataMember(Name = "documentation_url")] 15 | public string DocumentationUrl { get; set; } 16 | 17 | [DataMember(Name = "message")] 18 | public string Message { get; set; } 19 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/TestEntities/GitHubRelease.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Runtime.Serialization; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace Dapplo.HttpExtensions.Tests.TestEntities; 9 | 10 | /// 11 | /// Container for the release information from GitHub 12 | /// 13 | [DataContract] 14 | public class GitHubRelease 15 | { 16 | [DataMember(Name = "html_url", EmitDefaultValue = false)] 17 | [JsonPropertyName("html_url")] 18 | public string HtmlUrl { get; set; } 19 | 20 | [DataMember(Name = "prerelease", EmitDefaultValue = false)] 21 | [JsonPropertyName("prerelease")] 22 | public bool Prerelease { get; set; } 23 | 24 | [DataMember(Name = "published_at")] 25 | [JsonPropertyName("published_at")] 26 | public DateTime PublishedAt { get; set; } 27 | 28 | [DataMember(Name = "releaseType")] 29 | [JsonPropertyName("releaseType")] 30 | public ReleaseTypes ReleaseType { get; set; } 31 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/TestEntities/MyMultiPartRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Collections.Generic; 5 | using Dapplo.HttpExtensions.Support; 6 | 7 | namespace Dapplo.HttpExtensions.Tests.TestEntities; 8 | 9 | /// 10 | /// Example class wich is posted and filled automatically from the response information 11 | /// 12 | [HttpRequest] 13 | public class MyMultiPartRequest 14 | { 15 | [HttpPart(HttpParts.RequestMultipartName, Order = 1)] 16 | public string BitmapContentName { get; set; } = "File"; 17 | 18 | [HttpPart(HttpParts.RequestContentType, Order = 1)] 19 | public string BitmapContentType { get; set; } = "image/png"; 20 | 21 | [HttpPart(HttpParts.RequestMultipartFilename, Order = 1)] 22 | public string BitmapFileName { get; set; } = "empty.png"; 23 | 24 | [HttpPart(HttpParts.RequestContentType, Order = 0)] 25 | public string ContentType { get; set; } = "application/json"; 26 | 27 | [HttpPart(HttpParts.RequestHeaders)] 28 | public IDictionary Headers { get; } = new Dictionary(); 29 | 30 | [HttpPart(HttpParts.RequestContent, Order = 0)] 31 | public object JsonInformation { get; set; } 32 | 33 | [HttpPart(HttpParts.RequestContent, Order = 1)] 34 | public TBitmap OurBitmap { get; set; } 35 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/TestEntities/ReleaseTypes.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace Dapplo.HttpExtensions.Tests.TestEntities; 4 | 5 | /// 6 | /// Possible types of releases 7 | /// 8 | public enum ReleaseTypes 9 | { 10 | /// 11 | /// The release is public 12 | /// 13 | [EnumMember(Value = "public")] Public, 14 | /// 15 | /// The release is private 16 | /// 17 | [EnumMember(Value = "private")] Private 18 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/TestEntities/SerializeTestEntity.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.ComponentModel; 5 | using System.Runtime.Serialization; 6 | 7 | namespace Dapplo.HttpExtensions.Tests.TestEntities; 8 | 9 | /// 10 | /// Container for the release information from GitHub 11 | /// 12 | [DataContract] 13 | public class SerializeTestEntity 14 | { 15 | [DataMember(Name = "valueEmitDefaultFalse", EmitDefaultValue = false)] 16 | public string ValueEmitDefaultFalse { get; set; } 17 | 18 | [DataMember(Name = "valueNormal")] 19 | public string ValueNormal { get; set; } 20 | 21 | [DataMember(Name = "valueNotReadOnly")] 22 | [ReadOnly(false)] 23 | public string ValueNotReadOnly { get; set; } 24 | 25 | [DataMember(Name = "valueReadOnly")] 26 | [ReadOnly(true)] 27 | public string ValueReadOnly { get; set; } 28 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/TestEntities/WithExtensionData.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Runtime.Serialization; 6 | using Dapplo.HttpExtensions.JsonSimple; 7 | 8 | namespace Dapplo.HttpExtensions.Tests.TestEntities; 9 | 10 | /// 11 | /// Container for a test with Extension data 12 | /// 13 | [DataContract] 14 | internal class WithExtensionData 15 | { 16 | [DataMember(Name = "name")] 17 | public string Name { get; set; } 18 | 19 | [JsonExtensionData(Pattern = "customstringfield_.*")] 20 | public IDictionary StringExtensionData { get; set; } = new Dictionary(); 21 | 22 | [JsonExtensionData(Pattern = "customintfield_.*")] 23 | public IDictionary IntExtensionData { get; set; } = new Dictionary(); 24 | 25 | [JsonExtensionData] 26 | public IDictionary RestExtensionData { get; set; } = new Dictionary(); 27 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/UriActionExtensionsMultiPartTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.IO; 6 | #if NETFRAMEWORK 7 | using System.Net.Cache; 8 | #endif 9 | using System.Threading.Tasks; 10 | using Dapplo.Log; 11 | using Dapplo.Log.XUnit; 12 | using Xunit; 13 | using Xunit.Abstractions; 14 | 15 | namespace Dapplo.HttpExtensions.Tests; 16 | 17 | /// 18 | /// Should write some tests which use http://httpbin.org/ 19 | /// 20 | public class UriActionExtensionsMultiPartTests 21 | { 22 | private readonly Uri _bitmapUri = new Uri("http://getgreenshot.org/assets/greenshot-logo.png"); 23 | 24 | public UriActionExtensionsMultiPartTests(ITestOutputHelper testOutputHelper) 25 | { 26 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 27 | #if NETFRAMEWORK 28 | HttpExtensionsGlobals.HttpSettings.RequestCacheLevel = RequestCacheLevel.NoCacheNoStore; 29 | #endif 30 | } 31 | 32 | /// 33 | /// Test getting the Uri as MemoryStream 34 | /// 35 | /// 36 | [Fact] 37 | public async Task TestGetAsAsyncMemoryStream() 38 | { 39 | var stream = await _bitmapUri.GetAsAsync(); 40 | Assert.NotNull(stream); 41 | Assert.True(stream.Length > 0); 42 | } 43 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/UriParseExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Dapplo.Log; 8 | using Dapplo.Log.XUnit; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | namespace Dapplo.HttpExtensions.Tests; 13 | 14 | /// 15 | /// These are the tests for the UriParseExtensions 16 | /// 17 | public class UriParseExtensionsTests 18 | { 19 | private const string TestKey = "value1"; 20 | private const string TestValue = "1234"; 21 | private static readonly Uri TestUriSimple = new Uri("http://jira/name?somevalue=42").ExtendQuery(TestKey, TestValue); 22 | private readonly Uri _testUriComplex = TestUriSimple.ExtendQuery(TestKey, TestValue); 23 | 24 | public UriParseExtensionsTests(ITestOutputHelper testOutputHelper) 25 | { 26 | LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); 27 | } 28 | 29 | [Fact] 30 | public void TestExtendQuery_empty_dictionary() 31 | { 32 | var simpleUri = new Uri("http://dapplo.net"); 33 | var newUri = simpleUri.ExtendQuery(new Dictionary()); 34 | Assert.NotNull(newUri); 35 | Assert.Equal(simpleUri, newUri); 36 | } 37 | 38 | [Fact] 39 | public void TestQueryToDictionary() 40 | { 41 | var dictionary = _testUriComplex.QueryToDictionary(); 42 | Assert.NotNull(dictionary); 43 | Assert.True(dictionary.ContainsKey(TestKey)); 44 | Assert.Equal(TestValue, dictionary[TestKey]); 45 | } 46 | 47 | [Fact] 48 | public void TestQueryToKeyValuePairs() 49 | { 50 | var keyValuePairs = _testUriComplex.QueryToKeyValuePairs(); 51 | Assert.NotNull(keyValuePairs); 52 | Assert.Contains(keyValuePairs, x => x.Key == TestKey && x.Value == TestValue); 53 | } 54 | 55 | [Fact] 56 | public void TestQueryToLookup() 57 | { 58 | var loopkup = TestUriSimple.QueryToLookup(); 59 | Assert.NotNull(loopkup); 60 | Assert.Contains(loopkup, x => x.Key == TestKey && x.Contains(TestValue)); 61 | } 62 | 63 | [Fact] 64 | public void TestQueryToLookup_WithDuplicates() 65 | { 66 | var loopkup = _testUriComplex.QueryToLookup(); 67 | Assert.NotNull(loopkup); 68 | Assert.Equal(2, loopkup[TestKey].Count(x => x == TestValue)); 69 | } 70 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapplo/Dapplo.HttpExtensions/437df89f66bae1557bd9f206281405232662f2db/src/Dapplo.HttpExtensions.Tests/d.png -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "parallelizeTestCollections": true 4 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.WinForms/ContentConverter/BitmapConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK || NETCOREAPP3_1 || NET6_0 5 | 6 | using System.Collections.Generic; 7 | using System.Drawing.Imaging; 8 | using System.Linq; 9 | using Dapplo.HttpExtensions.Extensions; 10 | using Dapplo.HttpExtensions.Support; 11 | using Dapplo.Log; 12 | 13 | namespace Dapplo.HttpExtensions.WinForms.ContentConverter 14 | { 15 | /// 16 | /// This is a configuration class for the BitmapHttpContentConverter 17 | /// 18 | public class BitmapConfiguration : IHttpRequestConfiguration 19 | { 20 | private static readonly LogSource Log = new LogSource(); 21 | private int _quality = 80; 22 | 23 | /// 24 | /// Specify the supported content types 25 | /// 26 | public IList SupportedContentTypes { get; } = new List 27 | { 28 | MediaTypes.Bmp.EnumValueOf(), 29 | MediaTypes.Gif.EnumValueOf(), 30 | MediaTypes.Jpeg.EnumValueOf(), 31 | MediaTypes.Png.EnumValueOf(), 32 | MediaTypes.Tiff.EnumValueOf(), 33 | MediaTypes.Icon.EnumValueOf() 34 | }; 35 | 36 | /// 37 | /// Name of the configuration, this should be unique and usually is the type of the object 38 | /// 39 | public string Name { get; } = nameof(BitmapConfiguration); 40 | 41 | /// 42 | /// Check the parameters for the encoder, like setting Jpg quality 43 | /// 44 | public IList EncoderParameters { get; } = new List(); 45 | 46 | /// 47 | /// Specify the format used to write the image to 48 | /// 49 | public ImageFormat Format { get; set; } = ImageFormat.Png; 50 | 51 | /// 52 | /// Default constructor 53 | /// 54 | public BitmapConfiguration() 55 | { 56 | // Set the quality from the default 57 | EncoderParameters.Add(new EncoderParameter(Encoder.Quality, _quality)); 58 | } 59 | 60 | /// 61 | /// Set the quality EncoderParameter, for the Jpg format 0-100 62 | /// 63 | public int Quality 64 | { 65 | get => _quality; 66 | set 67 | { 68 | _quality = value; 69 | Log.Verbose().WriteLine("Setting Quality to {0}", value); 70 | var qualityParameter = EncoderParameters.FirstOrDefault(x => x.Encoder.Guid == Encoder.Quality.Guid); 71 | if (qualityParameter != null) 72 | { 73 | EncoderParameters.Remove(qualityParameter); 74 | } 75 | EncoderParameters.Add(new EncoderParameter(Encoder.Quality, value)); 76 | } 77 | } 78 | } 79 | } 80 | 81 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.WinForms/Dapplo.HttpExtensions.WinForms.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Add WinForms support to Dapplo.HttpExtensions 4 | net471;netstandard2.0;netcoreapp3.1;net6.0-windows 5 | http;rest;dapplo 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Wpf/ContentConverter/BitmapSourceConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK || NETCOREAPP3_1 || NET6_0 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Drawing.Imaging; 9 | using System.Windows.Media.Imaging; 10 | using Dapplo.HttpExtensions.Extensions; 11 | using Dapplo.HttpExtensions.Support; 12 | 13 | namespace Dapplo.HttpExtensions.Wpf.ContentConverter 14 | { 15 | /// 16 | /// This is a configuration class for the BitmapSourceHttpContentConverter 17 | /// 18 | public class BitmapSourceConfiguration : IHttpRequestConfiguration 19 | { 20 | /// 21 | /// Specify the supported content types 22 | /// 23 | public IList SupportedContentTypes { get; } = new List 24 | { 25 | MediaTypes.Bmp.EnumValueOf(), 26 | MediaTypes.Gif.EnumValueOf(), 27 | MediaTypes.Jpeg.EnumValueOf(), 28 | MediaTypes.Png.EnumValueOf(), 29 | MediaTypes.Tiff.EnumValueOf(), 30 | MediaTypes.Icon.EnumValueOf() 31 | }; 32 | 33 | /// 34 | /// Name of the configuration, this should be unique and usually is the type of the object 35 | /// 36 | public string Name { get; } = nameof(BitmapSourceConfiguration); 37 | 38 | /// 39 | /// Specify the format used to write the image to 40 | /// 41 | public ImageFormat Format { get; set; } = ImageFormat.Png; 42 | 43 | /// 44 | /// Set the quality, for the Jpg format 0-100 45 | /// 46 | public int Quality { get; set; } = 80; 47 | 48 | /// 49 | /// This creates a BitmapEncoder 50 | /// 51 | /// BitmapEncoder 52 | public BitmapEncoder CreateEncoder() 53 | { 54 | if (Format.Guid == ImageFormat.Bmp.Guid) 55 | { 56 | return new BmpBitmapEncoder(); 57 | } 58 | if (Format.Guid == ImageFormat.Gif.Guid) 59 | { 60 | return new GifBitmapEncoder(); 61 | } 62 | if (Format.Guid == ImageFormat.Jpeg.Guid) 63 | { 64 | return new JpegBitmapEncoder 65 | { 66 | QualityLevel = Quality 67 | }; 68 | } 69 | if (Format.Guid == ImageFormat.Tiff.Guid) 70 | { 71 | return new TiffBitmapEncoder(); 72 | } 73 | if (Format.Guid == ImageFormat.Png.Guid) 74 | { 75 | return new PngBitmapEncoder(); 76 | } 77 | // There is no IconBitmapEncoder 78 | // http://www.codeproject.com/Articles/687057/A-High-Quality-IconBitmapEncoder-for-WPF 79 | // if (Format.Guid == ImageFormat.Icon.Guid) 80 | throw new NotSupportedException($"Unsupported image format {Format}"); 81 | } 82 | } 83 | } 84 | 85 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.Wpf/Dapplo.HttpExtensions.Wpf.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Add WPF support to Dapplo.HttpExtensions 4 | net471;netcoreapp3.1;net6.0-windows 5 | http;rest;dapplo 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapplo/Dapplo.HttpExtensions/437df89f66bae1557bd9f206281405232662f2db/src/Dapplo.HttpExtensions.snk -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/ContentConverter/ByteArrayHttpContentConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.ContentConverter; 5 | 6 | /// 7 | /// This can convert HttpContent from/to a byte[] 8 | /// 9 | public class ByteArrayHttpContentConverter : IHttpContentConverter 10 | { 11 | #pragma warning disable IDE0090 // Use 'new(...)' 12 | private static readonly LogSource Log = new LogSource(); 13 | #pragma warning restore IDE0090 // Use 'new(...)' 14 | 15 | 16 | /// 17 | /// Instance of this IHttpContentConverter for reusing 18 | /// 19 | public static Lazy Instance { get; } = new(() => new ByteArrayHttpContentConverter()); 20 | 21 | /// 22 | /// Order or priority of the IHttpContentConverter 23 | /// 24 | public int Order => 0; 25 | 26 | /// 27 | /// Check if we can convert from the HttpContent to a byte array 28 | /// 29 | /// To what type will the result be assigned 30 | /// HttpContent 31 | /// true if we can convert the HttpContent to a ByteArray 32 | public bool CanConvertFromHttpContent(Type typeToConvertTo, HttpContent httpContent) 33 | { 34 | return typeToConvertTo == typeof(byte[]); 35 | } 36 | 37 | /// 38 | public async Task ConvertFromHttpContentAsync(Type resultType, HttpContent httpContent, CancellationToken cancellationToken = default) 39 | { 40 | if (!CanConvertFromHttpContent(resultType, httpContent)) 41 | { 42 | throw new NotSupportedException("CanConvertFromHttpContent resulted in false, this is not supposed to be called."); 43 | } 44 | Log.Debug().WriteLine("Retrieving the content as byte[], Content-Type: {0}", httpContent.Headers.ContentType); 45 | #if NET471 || NETCOREAPP3_1 || NETSTANDARD1_3 || NETSTANDARD2_0 46 | return await httpContent.ReadAsByteArrayAsync().ConfigureAwait(false); 47 | #else 48 | return await httpContent.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); 49 | #endif 50 | } 51 | 52 | /// 53 | public bool CanConvertToHttpContent(Type typeToConvert, object content) 54 | { 55 | return typeToConvert == typeof(byte[]); 56 | } 57 | 58 | /// 59 | public HttpContent ConvertToHttpContent(Type typeToConvert, object content) 60 | { 61 | var byteArray = content as byte[]; 62 | return new ByteArrayContent(byteArray); 63 | } 64 | 65 | /// 66 | public void AddAcceptHeadersForType(Type resultType, HttpRequestMessage httpRequestMessage) 67 | { 68 | if (resultType is null) 69 | { 70 | throw new ArgumentNullException(nameof(resultType)); 71 | } 72 | if (httpRequestMessage is null) 73 | { 74 | throw new ArgumentNullException(nameof(httpRequestMessage)); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/ContentConverter/DefaultJsonHttpContentConverterConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.ContentConverter; 5 | 6 | /// 7 | /// This is a configuration class for the DefaultJsonHttpContentConverter 8 | /// 9 | public class DefaultJsonHttpContentConverterConfiguration : IHttpRequestConfiguration 10 | { 11 | /// 12 | /// Name of the configuration, this should be unique and usually is the type of the object 13 | /// 14 | public string Name { get; } = nameof(DefaultJsonHttpContentConverterConfiguration); 15 | 16 | /// 17 | /// If the json content is any longer than LogThreshold AppendedWhenCut is appended to the cut string 18 | /// 19 | public string AppendedWhenCut { get; set; } = "..."; 20 | 21 | /// 22 | /// This is the amount of characters that are written to the log, if the json content is any longer that it will be cut 23 | /// (and AppendedWhenCut is appended) 24 | /// 25 | public int LogThreshold { get; set; } = 256; 26 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/ContentConverter/StringConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Dapplo.HttpExtensions.Extensions; 5 | using Dapplo.HttpExtensions.Support; 6 | 7 | namespace Dapplo.HttpExtensions.ContentConverter; 8 | 9 | /// 10 | /// Configuration for the StringHttpContentConverter 11 | /// 12 | public class StringConfiguration : IHttpRequestConfiguration 13 | { 14 | /// 15 | /// Specify the supported content types 16 | /// 17 | public IList SupportedContentTypes { get; } = new List 18 | { 19 | MediaTypes.Txt.EnumValueOf(), 20 | MediaTypes.Html.EnumValueOf(), 21 | MediaTypes.Xml.EnumValueOf(), 22 | MediaTypes.TxtJson.EnumValueOf(), 23 | MediaTypes.Json.EnumValueOf(), 24 | MediaTypes.XmlReadable.EnumValueOf() 25 | }; 26 | 27 | /// 28 | /// Name of the configuration, this should be unique and usually is the type of the object 29 | /// 30 | public string Name { get; } = nameof(StringConfiguration); 31 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/ContentConverter/StringHttpContentConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net.Http.Headers; 5 | using Dapplo.HttpExtensions.Extensions; 6 | 7 | namespace Dapplo.HttpExtensions.ContentConverter; 8 | 9 | /// 10 | /// This can convert HttpContent from/to a string 11 | /// 12 | public class StringHttpContentConverter : IHttpContentConverter 13 | { 14 | private static readonly LogSource Log = new LogSource(); 15 | 16 | /// 17 | /// Instance of this IHttpContentConverter for reusing 18 | /// 19 | public static Lazy Instance { get; } = new Lazy(() => new StringHttpContentConverter()); 20 | 21 | /// 22 | public int Order => int.MaxValue; 23 | 24 | /// 25 | public bool CanConvertFromHttpContent(Type typeToConvertTo, HttpContent httpContent) 26 | { 27 | if (typeToConvertTo != typeof(string)) 28 | { 29 | return false; 30 | } 31 | var httpBehaviour = HttpBehaviour.Current; 32 | var configuration = httpBehaviour.GetConfig(); 33 | // Set ValidateResponseContentType to false to "catch" all 34 | return !httpBehaviour.ValidateResponseContentType || configuration.SupportedContentTypes.Contains(httpContent.GetContentType()); 35 | } 36 | 37 | /// 38 | public async Task ConvertFromHttpContentAsync(Type resultType, HttpContent httpContent, CancellationToken cancellationToken = default) 39 | { 40 | if (!CanConvertFromHttpContent(resultType, httpContent)) 41 | { 42 | throw new NotSupportedException("CanConvertFromHttpContent resulted in false, this is not supposed to be called."); 43 | } 44 | return await httpContent.ReadAsStringAsync().ConfigureAwait(false); 45 | } 46 | 47 | /// 48 | public bool CanConvertToHttpContent(Type typeToConvert, object content) 49 | { 50 | return typeof(string) == typeToConvert; 51 | } 52 | 53 | /// 54 | public HttpContent ConvertToHttpContent(Type typeToConvert, object content) 55 | { 56 | return new StringContent(content as string); 57 | } 58 | 59 | /// 60 | /// Add Accept-Headers to the HttpRequestMessage, depending on the passed resultType. 61 | /// This tries to hint the Http server what we can accept, which depends on the type of the return value 62 | /// 63 | /// Result type, this where to a conversion from HttpContent is made 64 | /// HttpRequestMessage 65 | public void AddAcceptHeadersForType(Type typeToConvertTo, HttpRequestMessage httpRequestMessage) 66 | { 67 | if (typeToConvertTo is null) 68 | { 69 | throw new ArgumentNullException(nameof(typeToConvertTo)); 70 | } 71 | if (httpRequestMessage is null) 72 | { 73 | throw new ArgumentNullException(nameof(httpRequestMessage)); 74 | } 75 | if (typeToConvertTo != typeof(string)) 76 | { 77 | return; 78 | } 79 | var httpBehaviour = HttpBehaviour.Current; 80 | var configuration = httpBehaviour.GetConfig(); 81 | // add all supported content types 82 | foreach (var contentType in configuration.SupportedContentTypes) 83 | { 84 | httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(contentType)); 85 | } 86 | // TODO: Encoding header? 87 | Log.Debug().WriteLine("Modified the header(s) of the HttpRequestMessage: Accept: {0}", httpRequestMessage.Headers.Accept); 88 | } 89 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/ContentConverter/XDocumentHttpContentConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if !NETSTANDARD1_3 5 | 6 | using System.IO; 7 | using System.Net.Http.Headers; 8 | using System.Xml; 9 | using System.Xml.Linq; 10 | using Dapplo.HttpExtensions.Extensions; 11 | using Dapplo.HttpExtensions.Support; 12 | 13 | namespace Dapplo.HttpExtensions.ContentConverter 14 | { 15 | /// 16 | /// This can convert HttpContent from/to a SyndicationFeed 17 | /// 18 | public class XDocumentHttpContentConverter : IHttpContentConverter 19 | { 20 | private static readonly LogSource Log = new LogSource(); 21 | 22 | /// 23 | /// Instance of this IHttpContentConverter for reusing 24 | /// 25 | public static Lazy Instance { get; } = new Lazy(() => new XDocumentHttpContentConverter()); 26 | 27 | /// 28 | public int Order => 0; 29 | 30 | /// 31 | public bool CanConvertFromHttpContent(Type typeToConvertTo, HttpContent httpContent) 32 | { 33 | return typeToConvertTo == typeof(XDocument); 34 | } 35 | 36 | /// 37 | public async Task ConvertFromHttpContentAsync(Type resultType, HttpContent httpContent, CancellationToken cancellationToken = default) 38 | { 39 | if (!CanConvertFromHttpContent(resultType, httpContent)) 40 | { 41 | throw new NotSupportedException("CanConvertFromHttpContent resulted in false, this is not supposed to be called."); 42 | } 43 | Log.Debug().WriteLine("Retrieving the content as XDocument, Content-Type: {0}", httpContent.Headers.ContentType); 44 | 45 | using var contentStream = await httpContent.ReadAsStreamAsync().ConfigureAwait(false); 46 | return XDocument.Load(contentStream); 47 | } 48 | 49 | /// 50 | public bool CanConvertToHttpContent(Type typeToConvert, object content) 51 | { 52 | return typeToConvert == typeof(XDocument); 53 | } 54 | 55 | /// 56 | public HttpContent ConvertToHttpContent(Type typeToConvert, object content) 57 | { 58 | if (!(content is XDocument xDocument)) 59 | { 60 | return null; 61 | } 62 | 63 | using var stringWriter = new StringWriter(); 64 | using var xmlTextWriter = new XmlTextWriter(stringWriter); 65 | xDocument.WriteTo(xmlTextWriter); 66 | var httpContent = new StringContent(stringWriter.ToString()); 67 | httpContent.SetContentType($"{MediaTypes.Xml.EnumValueOf()}; charset={stringWriter.Encoding.EncodingName}"); 68 | return httpContent; 69 | } 70 | 71 | /// 72 | public void AddAcceptHeadersForType(Type resultType, HttpRequestMessage httpRequestMessage) 73 | { 74 | if (resultType is null) 75 | { 76 | throw new ArgumentNullException(nameof(resultType)); 77 | } 78 | if (httpRequestMessage is null) 79 | { 80 | throw new ArgumentNullException(nameof(httpRequestMessage)); 81 | } 82 | if (resultType != typeof(XDocument)) 83 | { 84 | return; 85 | } 86 | httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypes.Xml.EnumValueOf())); 87 | httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypes.XmlReadable.EnumValueOf())); 88 | Log.Debug().WriteLine("Modified the header(s) of the HttpRequestMessage: Accept: {0}", httpRequestMessage.Headers.Accept); 89 | } 90 | } 91 | } 92 | 93 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Dapplo.HttpExtensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | A library extensions to simplify HTTP usage. 4 | net471;netstandard1.3;netstandard2.0;netcoreapp3.1;net6.0 5 | http;rest;dapplo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Extensions/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reflection; 5 | using System.Runtime.Serialization; 6 | 7 | namespace Dapplo.HttpExtensions.Extensions; 8 | 9 | /// 10 | /// Extensions for enums 11 | /// 12 | public static class EnumExtensions 13 | { 14 | /// 15 | /// The returns the Value from the EnumMemberAttribute, or a ToString on the element. 16 | /// This can be used to create a lookup from string to enum element 17 | /// 18 | /// Enum 19 | /// string 20 | public static string EnumValueOf(this Enum enumerationItem) 21 | { 22 | if (enumerationItem is null) 23 | { 24 | return null; 25 | } 26 | var attributes = 27 | (EnumMemberAttribute[]) enumerationItem.GetType().GetRuntimeField(enumerationItem.ToString()).GetCustomAttributes(typeof(EnumMemberAttribute), false); 28 | return attributes.Length > 0 ? attributes[0].Value : enumerationItem.ToString(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Extensions/HttpBehaviourExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Extensions; 5 | 6 | /// 7 | /// Extensions for HttpBehaviour 8 | /// 9 | public static class HttpBehaviourExtensions 10 | { 11 | /// 12 | /// Get the IHttpRequestConfiguration from the IHttpBehaviour 13 | /// 14 | /// Type which implements IHttpRequestConfiguration 15 | /// IHttpBehaviour instance, if null HttpBehaviour.Current is used 16 | /// THttpReqestConfiguration 17 | public static THttpRequestConfiguration GetConfig(this IHttpBehaviour httpBehaviour) 18 | where THttpRequestConfiguration : class, IHttpRequestConfiguration, new() 19 | { 20 | // Although this probably doesn't happen... if null is passed HttpBehaviour.Current is used 21 | if (httpBehaviour is null) 22 | { 23 | httpBehaviour = HttpBehaviour.Current; 24 | } 25 | 26 | httpBehaviour.RequestConfigurations.TryGetValue(typeof(THttpRequestConfiguration).Name, out var configurationUntyped); 27 | return configurationUntyped as THttpRequestConfiguration ?? new THttpRequestConfiguration(); 28 | } 29 | 30 | /// 31 | /// Set the specified configuration, if it already exists it is overwritten 32 | /// 33 | /// IHttpBehaviour 34 | /// THttpReqestConfiguration 35 | /// IHttpBehaviour 36 | public static IHttpBehaviour SetConfig(this IHttpBehaviour httpBehaviour, IHttpRequestConfiguration configuration) 37 | { 38 | // Although this probably doesn't happen... if null is passed HttpBehaviour.Current is used 39 | if (httpBehaviour is null) 40 | { 41 | httpBehaviour = HttpBehaviour.Current; 42 | } 43 | httpBehaviour.RequestConfigurations[configuration.Name] = configuration; 44 | return httpBehaviour; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reflection; 5 | 6 | namespace Dapplo.HttpExtensions.Extensions; 7 | 8 | /// 9 | /// Extensions for the Type class 10 | /// 11 | public static class TypeExtensions 12 | { 13 | /// 14 | /// Get the name of a type which is readable, even if generics are used. 15 | /// 16 | /// Type 17 | /// string 18 | public static string FriendlyName(this Type type) 19 | { 20 | string friendlyName = type.Name; 21 | if (type.GetTypeInfo().IsGenericType) 22 | { 23 | int backtick = friendlyName.IndexOf('`'); 24 | if (backtick > 0) 25 | { 26 | friendlyName = friendlyName.Remove(backtick); 27 | } 28 | friendlyName += "<"; 29 | Type[] typeParameters = type.GetGenericArguments(); 30 | for (int i = 0; i < typeParameters.Length; i++) 31 | { 32 | string typeParamName = typeParameters[i].FriendlyName(); 33 | friendlyName += i == 0 ? typeParamName : ", " + typeParamName; 34 | } 35 | friendlyName += ">"; 36 | } 37 | 38 | if (type.IsArray) 39 | { 40 | return type.GetElementType().FriendlyName() + "[]"; 41 | } 42 | 43 | return friendlyName; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Factory/HttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Factory; 5 | 6 | /// 7 | /// Creating a HttpClient is not very straightforward, that is why the logic is capsulated in the HttpClientFactory. 8 | /// 9 | public static class HttpClientFactory 10 | { 11 | /// 12 | /// Create a HttpClient which is modified by the settings specified in the IHttpSettings of the HttpBehaviour. 13 | /// If nothing is passed, the GlobalSettings are used 14 | /// 15 | /// 16 | /// If a Uri is supplied, this is used to configure the HttpClient. Currently the 17 | /// Uri.UserInfo is used to set the basic authorization. 18 | /// 19 | /// HttpClient 20 | public static HttpClient Create(Uri uriForConfiguration = null) 21 | { 22 | var httpBehaviour = HttpBehaviour.Current; 23 | var httpSettings = httpBehaviour.HttpSettings ?? HttpExtensionsGlobals.HttpSettings; 24 | 25 | var httpClient = new HttpClient(HttpMessageHandlerFactory.Create()) 26 | { 27 | Timeout = httpSettings.RequestTimeout, 28 | MaxResponseContentBufferSize = httpSettings.MaxResponseContentBufferSize 29 | }; 30 | if (!string.IsNullOrEmpty(httpSettings.DefaultUserAgent)) 31 | { 32 | httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(httpSettings.DefaultUserAgent); 33 | } 34 | // If the uri has username/password, use this to set Basic Authorization 35 | if (uriForConfiguration != null) 36 | { 37 | httpClient.SetBasicAuthorization(uriForConfiguration); 38 | } 39 | 40 | // Copy the expect continue value 41 | httpClient.DefaultRequestHeaders.ExpectContinue = httpSettings.Expect100Continue; 42 | 43 | // Allow the passed OnCreateHttpClient action to modify the HttpClient 44 | httpBehaviour.OnHttpClientCreated?.Invoke(httpClient); 45 | return httpClient; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Factory/WebProxyFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if !NETSTANDARD1_3 5 | 6 | #if NETCOREAPP3_1 || NET6_0 7 | #endif 8 | 9 | namespace Dapplo.HttpExtensions.Factory 10 | { 11 | /// 12 | /// Creating a proxy is not very straightforward, that is why the logic is capsulated in the ProxyFactory. 13 | /// 14 | public static class WebProxyFactory 15 | { 16 | /// 17 | /// Create a IWebProxy Object which can be used to access the Internet 18 | /// This method will create a proxy according to the properties in the Settings class 19 | /// 20 | /// IWebProxy filled with all the proxy details or null if none is set/wanted 21 | public static IWebProxy Create() 22 | { 23 | var httpBehaviour = HttpBehaviour.Current; 24 | var httpSettings = httpBehaviour.HttpSettings ?? HttpExtensionsGlobals.HttpSettings; 25 | 26 | // This is already checked in the HttpClientFactory, but should be checked if this call is used elsewhere. 27 | if (!httpSettings.UseProxy) 28 | { 29 | return null; 30 | } 31 | 32 | var proxyToUse = httpSettings.UseDefaultProxy ? 33 | #if NETCOREAPP3_1 || NET6_0 34 | HttpClient.DefaultProxy 35 | #else 36 | WebRequest.GetSystemWebProxy() 37 | #endif 38 | : new WebProxy(httpSettings.ProxyUri, httpSettings.ProxyBypassOnLocal, httpSettings.ProxyBypassList); 39 | if (httpSettings.UseDefaultCredentialsForProxy) 40 | { 41 | if (proxyToUse is WebProxy webProxy) 42 | { 43 | // Read note here: https://msdn.microsoft.com/en-us/library/system.net.webproxy.credentials.aspx 44 | webProxy.UseDefaultCredentials = true; 45 | } 46 | else 47 | { 48 | #if NETCOREAPP3_1 || NET6_0 49 | if (!httpSettings.UseDefaultProxy) 50 | { 51 | proxyToUse.Credentials = CredentialCache.DefaultCredentials; 52 | } 53 | #else 54 | proxyToUse.Credentials = CredentialCache.DefaultCredentials; 55 | #endif 56 | } 57 | } 58 | else 59 | { 60 | if (proxyToUse is WebProxy webProxy) 61 | { 62 | // Read note here: https://msdn.microsoft.com/en-us/library/system.net.webproxy.credentials.aspx 63 | webProxy.UseDefaultCredentials = false; 64 | webProxy.Credentials = httpSettings.ProxyCredentials; 65 | } 66 | else 67 | { 68 | proxyToUse.Credentials = httpSettings.ProxyCredentials; 69 | } 70 | } 71 | return proxyToUse; 72 | } 73 | } 74 | } 75 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | global using System; 5 | global using System.Collections.Generic; 6 | global using System.Net; 7 | global using System.Net.Http; 8 | global using System.Net.Security; 9 | global using System.Text; 10 | global using Dapplo.Log; 11 | global using System.Threading; 12 | global using System.Threading.Tasks; 13 | 14 | #if !NETSTANDARD1_3 15 | global using System.Net.Cache; 16 | global using System.Security.Principal; 17 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/HttpBehaviourExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions; 5 | 6 | /// 7 | /// Extensions for the HttpBehaviour class 8 | /// 9 | public static class HttpBehaviourExtensions 10 | { 11 | /// 12 | /// Chain the current OnHttpContentCreated 13 | /// 14 | /// 15 | /// 16 | /// IChangeableHttpBehaviour for fluent usage 17 | public static IChangeableHttpBehaviour ChainOnHttpContentCreated(this IChangeableHttpBehaviour changeableHttpBehaviour, 18 | Func newOnHttpContentCreated) 19 | { 20 | var oldOnHttpContentCreated = changeableHttpBehaviour.OnHttpContentCreated; 21 | 22 | changeableHttpBehaviour.OnHttpContentCreated = httpContent => 23 | { 24 | if (oldOnHttpContentCreated != null) 25 | { 26 | httpContent = oldOnHttpContentCreated(httpContent); 27 | } 28 | return newOnHttpContentCreated(httpContent); 29 | }; 30 | return changeableHttpBehaviour; 31 | } 32 | 33 | /// 34 | /// Chain the current OnHttpMessageHandlerCreated 35 | /// 36 | /// 37 | /// function which accepts a HttpMessageHandler and also returns one 38 | /// IChangeableHttpBehaviour for fluent usage 39 | public static IChangeableHttpBehaviour ChainOnHttpMessageHandlerCreated(this IChangeableHttpBehaviour changeableHttpBehaviour, 40 | Func newOnHttpMessageHandlerCreated) 41 | { 42 | var oldOnHttpMessageHandlerCreated = changeableHttpBehaviour.OnHttpMessageHandlerCreated; 43 | 44 | changeableHttpBehaviour.OnHttpMessageHandlerCreated = httpMessageHandler => 45 | { 46 | if (oldOnHttpMessageHandlerCreated != null) 47 | { 48 | httpMessageHandler = oldOnHttpMessageHandlerCreated(httpMessageHandler); 49 | } 50 | return newOnHttpMessageHandlerCreated(httpMessageHandler); 51 | }; 52 | return changeableHttpBehaviour; 53 | } 54 | 55 | /// 56 | /// Chain the current OnHttpRequestMessageCreated function 57 | /// 58 | /// IChangeableHttpBehaviour 59 | /// Function which accepts and returns HttpRequestMessage 60 | /// IChangeableHttpBehaviour for fluent usage 61 | public static IChangeableHttpBehaviour ChainOnHttpRequestMessageCreated(this IChangeableHttpBehaviour changeableHttpBehaviour, 62 | Func newOnHttpRequestMessageCreated) 63 | { 64 | var onHttpRequestMessageCreated = changeableHttpBehaviour.OnHttpRequestMessageCreated; 65 | 66 | changeableHttpBehaviour.OnHttpRequestMessageCreated = httpRequestMessage => 67 | { 68 | if (onHttpRequestMessageCreated != null) 69 | { 70 | httpRequestMessage = onHttpRequestMessageCreated(httpRequestMessage); 71 | } 72 | return newOnHttpRequestMessageCreated(httpRequestMessage); 73 | }; 74 | return changeableHttpBehaviour; 75 | } 76 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/HttpExtensionsGlobals.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Dapplo.HttpExtensions.ContentConverter; 5 | using Dapplo.HttpExtensions.Support; 6 | 7 | namespace Dapplo.HttpExtensions; 8 | 9 | /// 10 | /// These are the globals for some of the important configurable settings 11 | /// When a HttpBehaviour is created, some of the values from here will be copied. (unless diffently specified) 12 | /// 13 | public static class HttpExtensionsGlobals 14 | { 15 | /// 16 | /// The global default encoding 17 | /// 18 | public static Encoding DefaultEncoding { get; set; } = Encoding.UTF8; 19 | 20 | /// 21 | /// The global list of HttpContent converters 22 | /// 23 | public static IList HttpContentConverters { get; set; } = new List 24 | { 25 | #if !NETSTANDARD1_3 26 | SyndicationFeedHttpContentConverter.Instance.Value, 27 | XDocumentHttpContentConverter.Instance.Value, 28 | #endif 29 | ByteArrayHttpContentConverter.Instance.Value, 30 | FormUriEncodedContentConverter.Instance.Value, 31 | DefaultJsonHttpContentConverter.Instance.Value, 32 | StreamHttpContentConverter.Instance.Value, 33 | StringHttpContentConverter.Instance.Value 34 | }; 35 | 36 | /// 37 | /// The GLOBAL default function for the uri escaping, this is Uri.EscapeDataString 38 | /// Some projects might rather use Uri.EscapeUriString, be careful changing this! 39 | /// 40 | public static Func DefaultUriEscapeFunc { get; } = Uri.EscapeDataString; 41 | 42 | /// 43 | /// The global IHttpSettings 44 | /// 45 | public static IHttpSettings HttpSettings { get; set; } = new HttpSettings(); 46 | 47 | /// 48 | /// The global JsonSerializer 49 | /// 50 | public static IJsonSerializer JsonSerializer { get; set; } 51 | 52 | /// 53 | /// This offset is used in the OAuth2Setting.IsAccessTokenExpired to check the OAuth2AccessTokenExpires 54 | /// Now + this > OAuth2AccessTokenExpires 55 | /// 56 | public static int OAuth2ExpireOffset { get; set; } = 5; 57 | 58 | /// 59 | /// The global read buffer-size 60 | /// 61 | public static int ReadBufferSize { get; set; } = 4096; 62 | 63 | /// 64 | /// Global value for ThrowOnError, see IHttpBehaviour 65 | /// 66 | public static bool ThrowOnError { get; set; } = true; 67 | 68 | /// 69 | /// Global value for UseProgressStream, see IHttpBehaviour 70 | /// 71 | public static bool UseProgressStream { get; set; } = false; 72 | 73 | /// 74 | /// Global validate response content-type 75 | /// 76 | public static bool ValidateResponseContentType { get; set; } = true; 77 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/HttpRequestMessageConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions; 5 | 6 | /// 7 | /// Use this to configure the HttpRequestMessage, created in the HttpRequestMessageFactory 8 | /// 9 | public class HttpRequestMessageConfiguration : IHttpRequestConfiguration 10 | { 11 | /// 12 | /// Name of the configuration, this should be unique and usually is the type of the object 13 | /// 14 | public string Name { get; } = nameof(HttpRequestMessageConfiguration); 15 | 16 | /// 17 | /// A set of properties for the HTTP request. 18 | /// 19 | public IDictionary Properties { get; set; } = new Dictionary(); 20 | 21 | /// 22 | /// The HTTP Message version, default is 1.1 23 | /// 24 | public Version HttpMessageVersion { get; set; } = new Version(1, 1); 25 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Net.Http.Headers; 5 | using Dapplo.HttpExtensions.Support; 6 | 7 | namespace Dapplo.HttpExtensions; 8 | 9 | /// 10 | /// This container can be used to get the details of a response. 11 | /// You can specify your own container, by using the HttpAttribute. 12 | /// 13 | [HttpResponse] 14 | public class HttpResponse 15 | { 16 | /// 17 | /// The Content-Type of the response 18 | /// Will be filled due to the annotation 19 | /// 20 | [HttpPart(HttpParts.ResponseContentType)] 21 | public string ContentType { get; set; } 22 | 23 | /// 24 | /// The reponse headers 25 | /// Will be filled due to the annotation 26 | /// 27 | [HttpPart(HttpParts.ResponseHeaders)] 28 | public HttpResponseHeaders Headers { get; set; } 29 | 30 | /// 31 | /// The response http status code 32 | /// Will be filled due to the annotation 33 | /// 34 | [HttpPart(HttpParts.ResponseStatuscode)] 35 | public HttpStatusCode StatusCode { get; set; } 36 | } 37 | 38 | /// 39 | /// This container can be used to get the details of a response without content but with a potential error 40 | /// 41 | /// Type for the error response 42 | [HttpResponse] 43 | public class HttpResponseWithError : HttpResponse 44 | where TErrorResponse : class 45 | { 46 | /// 47 | /// The response if there was an error 48 | /// Will be filled due to the annotation 49 | /// 50 | [HttpPart(HttpParts.ResponseErrorContent)] 51 | public TErrorResponse ErrorResponse { get; set; } 52 | 53 | /// 54 | /// Was there an error? 55 | /// 56 | public bool HasError => ErrorResponse != null; 57 | } 58 | 59 | /// 60 | /// This container can be used to get the details of a response. 61 | /// 62 | /// Type for the normal response 63 | [HttpResponse] 64 | public class HttpResponse : HttpResponse 65 | where TResponse : class 66 | { 67 | /// 68 | /// The response, if there was no error 69 | /// Will be filled due to the annotation 70 | /// 71 | [HttpPart(HttpParts.ResponseContent)] 72 | public TResponse Response { get; set; } 73 | } 74 | 75 | /// 76 | /// This container can be used to get the details of a response. 77 | /// It also makes it possible to process the error information, and eventually do something different. 78 | /// You can specify your own container, by using the HttpAttribute. 79 | /// 80 | /// Type for the normal response 81 | /// Type for the error response 82 | [HttpResponse] 83 | public class HttpResponse : HttpResponseWithError 84 | where TResponse : class 85 | where TErrorResponse : class 86 | { 87 | /// 88 | /// The response, if there was no error 89 | /// Will be filled due to the annotation 90 | /// 91 | [HttpPart(HttpParts.ResponseContent)] 92 | public TResponse Response { get; set; } 93 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/IHttpContentConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions; 5 | 6 | /// 7 | /// This interface is used for all 8 | /// 9 | public interface IHttpContentConverter 10 | { 11 | /// 12 | /// Specify the order in that this IHttpContentConverter is used 13 | /// 14 | int Order { get; } 15 | 16 | /// 17 | /// This will add accept headers depending on the result type 18 | /// 19 | /// Type to read into 20 | /// HttpClient for the response headers 21 | void AddAcceptHeadersForType(Type resultType, HttpRequestMessage httpRequestMessage); 22 | 23 | /// 24 | /// Check if this IHttpContentProcessor can convert the HttpContent into the specified type 25 | /// 26 | /// Type from which a conversion should be made 27 | /// HttpContent object to process 28 | /// true if this processor can do the conversion 29 | bool CanConvertFromHttpContent(Type typeToConvertTo, HttpContent httpContent); 30 | 31 | /// 32 | /// Check if this IHttpContentProcessor can convert the specified type to a HttpContent 33 | /// 34 | /// Type to convert 35 | /// Content to place into a HttpContent 36 | /// true if this processor can do the conversion 37 | bool CanConvertToHttpContent(Type typeToConvertFrom, object content); 38 | 39 | /// 40 | /// Create the target object from the supplied HttpContent 41 | /// 42 | /// Typ to process the HttpContent to 43 | /// HttpContent 44 | /// CancellationToken 45 | /// object of type resultType 46 | Task ConvertFromHttpContentAsync(Type resultType, HttpContent httpContent, CancellationToken cancellationToken = default); 47 | 48 | /// 49 | /// Create HttpContent for the supplied object/type 50 | /// 51 | /// Type of the content to convert 52 | /// Content to place into a HttpContent 53 | /// HttpContent 54 | HttpContent ConvertToHttpContent(Type typeToConvert, object content); 55 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/IHttpRequestConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions; 5 | 6 | /// 7 | /// This interface is the base interface for configuration information. 8 | /// It makes it possible to supply configuration to different parts of the library during a request, where as a caller 9 | /// you normally don't interact with directly. 10 | /// The interface only specifies the name of the configuration, specific implementations should be used. 11 | /// Instances of this interface are added to the HttpBehaviour, so they are available throughout a request. 12 | /// 13 | public interface IHttpRequestConfiguration 14 | { 15 | /// 16 | /// Name of the configuration, this should be unique 17 | /// 18 | string Name { get; } 19 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/IJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions; 5 | 6 | /// 7 | /// This interface makes it possible to change the Json serializer which is used for de- serializing JSON. 8 | /// The default implementation for this, SimpleJson, is included in this project 9 | /// 10 | public interface IJsonSerializer 11 | { 12 | /// 13 | /// Test if the specified type can be serialized to JSON 14 | /// 15 | /// Type to check 16 | /// bool 17 | bool CanSerializeTo(Type sourceType); 18 | 19 | /// 20 | /// Test if the specified type can be deserialized 21 | /// 22 | /// Type to check 23 | /// bool 24 | bool CanDeserializeFrom(Type targetType); 25 | 26 | /// 27 | /// Deserialize a string with Json to the specified type 28 | /// 29 | /// Type to deserialize to 30 | /// string with json content 31 | /// an object of type targetType or null 32 | object Deserialize(Type targetType, string jsonString); 33 | 34 | /// 35 | /// Serialize the generic object into a string with Json content 36 | /// 37 | /// Type to serialize 38 | /// Object to serialize 39 | /// string with Json content 40 | string Serialize(T jsonObject); 41 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Listener/HttpListenerContextExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK || NETSTANDARD2_0 || NETCOREAPP3_1 || NET6_0 5 | 6 | using Dapplo.HttpExtensions.Factory; 7 | 8 | namespace Dapplo.HttpExtensions.Listener 9 | { 10 | /// 11 | /// Extensions for the HttpListener 12 | /// 13 | public static class HttpListenerContextExtensions 14 | { 15 | private static readonly LogSource Log = new LogSource(); 16 | 17 | /// 18 | /// This writes the supplied content to the response of the httpListenerContext 19 | /// It's actually a bit overkill, as it converts to HttpContent and writes this to a stream 20 | /// But performance and memory usage are currently not our main concern for the HttpListener 21 | /// 22 | /// Type of the content 23 | /// HttpListenerContext 24 | /// TContent object 25 | /// CancellationToken 26 | /// Task 27 | public static async Task RespondAsync(this HttpListenerContext httpListenerContext, TContent content, CancellationToken cancellationToken = default) where TContent : class 28 | { 29 | HttpContent httpContent; 30 | if (typeof(HttpContent).IsAssignableFrom(typeof(TContent))) 31 | { 32 | httpContent = content as HttpContent; 33 | } 34 | else 35 | { 36 | httpContent = HttpContentFactory.Create(content); 37 | } 38 | 39 | using var response = httpListenerContext.Response; 40 | if (httpContent is null) 41 | { 42 | Log.Error().WriteLine("Nothing to respond with..."); 43 | response.StatusCode = (int) HttpStatusCode.InternalServerError; 44 | return; 45 | } 46 | // Write to response stream. 47 | response.ContentLength64 = httpContent.Headers?.ContentLength ?? 0; 48 | response.ContentType = httpContent.GetContentType(); 49 | response.StatusCode = (int) HttpStatusCode.OK; 50 | Log.Debug().WriteLine("Responding with {0}", response.ContentType); 51 | using var stream = response.OutputStream; 52 | if (!cancellationToken.IsCancellationRequested) 53 | { 54 | await httpContent.CopyToAsync(stream).ConfigureAwait(false); 55 | } 56 | } 57 | } 58 | } 59 | 60 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Listener/ListenerPortExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK || NETSTANDARD2_0 || NETCOREAPP3_1 || NET6_0 5 | 6 | using System.Net.Sockets; 7 | 8 | namespace Dapplo.HttpExtensions.Listener 9 | { 10 | /// 11 | /// int[] extensions, which in this case is an array of ports 12 | /// 13 | public static class ListenerPortExtensions 14 | { 15 | private static readonly LogSource Log = new LogSource(); 16 | 17 | /// 18 | /// Create an Localhost Uri for an unused port 19 | /// 20 | /// An int array with ports, the routine will return the first free port. 21 | /// Uri 22 | public static Uri CreateLocalHostUri(this int[] possiblePorts) 23 | { 24 | return new Uri($"http://localhost:{possiblePorts.GetFreeListenerPort()}"); 25 | } 26 | 27 | /// 28 | /// Returns an unused port. 29 | /// A port of 0 in the list will have the following behaviour: https://msdn.microsoft.com/en-us/library/c6z86e63.aspx 30 | /// If you do not care which local port is used, you can specify 0 for the port number. In this case, the service 31 | /// provider will assign an available port number between 1024 and 5000. 32 | /// 33 | /// An int array with ports, the routine will return the first free port. 34 | /// A free port 35 | public static int GetFreeListenerPort(this int[] possiblePorts) 36 | { 37 | possiblePorts ??= new[] {0}; 38 | 39 | foreach (var portToCheck in possiblePorts) 40 | { 41 | var listener = new TcpListener(IPAddress.Loopback, portToCheck); 42 | try 43 | { 44 | listener.Start(); 45 | // As the LocalEndpoint is of type EndPoint, this doesn't have the port, we need to cast it to IPEndPoint 46 | var port = ((IPEndPoint) listener.LocalEndpoint).Port; 47 | Log.Debug().WriteLine("Found free listener port {0}.", port); 48 | return port; 49 | } 50 | catch 51 | { 52 | Log.Debug().WriteLine("Port {0} isn't free.", portToCheck); 53 | } 54 | finally 55 | { 56 | listener.Stop(); 57 | } 58 | } 59 | var message = $"No free ports in the range {possiblePorts} found!"; 60 | Log.Debug().WriteLine(message); 61 | throw new ApplicationException(message); 62 | } 63 | } 64 | } 65 | 66 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Listener/UriHttpListenerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK || NETSTANDARD2_0 || NETCOREAPP3_1 || NET6_0 5 | 6 | namespace Dapplo.HttpExtensions.Listener 7 | { 8 | /// 9 | /// Async helpers for the HttpListener 10 | /// 11 | public static class UriHttpListenerExtensions 12 | { 13 | private static readonly LogSource Log = new LogSource(); 14 | 15 | /// 16 | /// This method starts a HttpListener to make it possible to listen async for a SINGLE request. 17 | /// 18 | /// 19 | /// The Uri to listen to, use CreateFreeLocalHostUri (and add segments) if you don't have a 20 | /// specific reason 21 | /// 22 | /// A function which gets a HttpListenerContext and returns a value 23 | /// CancellationToken 24 | /// The value from the httpListenerContextHandler 25 | public static Task ListenAsync(this Uri listenUri, Func> httpListenerContextHandler, 26 | CancellationToken cancellationToken = default) 27 | { 28 | var listenUriString = listenUri.AbsoluteUri.EndsWith("/") ? listenUri.AbsoluteUri : listenUri.AbsoluteUri + "/"; 29 | Log.Debug().WriteLine("Start listening on {0}", listenUriString); 30 | var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); 31 | 32 | // ReSharper disable once UnusedVariable 33 | var listenTask = Task.Factory.StartNew(async () => 34 | { 35 | using var httpListener = new HttpListener(); 36 | try 37 | { 38 | // Add the URI to listen to, this SHOULD be localhost to prevent a lot of problemens with rights 39 | httpListener.Prefixes.Add(listenUriString); 40 | // Start listening 41 | httpListener.Start(); 42 | Log.Debug().WriteLine("Started listening on {0}", listenUriString); 43 | 44 | // Make the listener stop if the token is cancelled. 45 | // This registratrion is disposed before the httpListener is disposed: 46 | // ReSharper disable once AccessToDisposedClosure 47 | var cancellationTokenRegistration = cancellationToken.Register(() => { httpListener.Stop(); }); 48 | 49 | // Get the context 50 | var httpListenerContext = await httpListener.GetContextAsync().ConfigureAwait(false); 51 | 52 | // Call the httpListenerContextHandler with the context we got for the result 53 | var result = await httpListenerContextHandler(httpListenerContext).ConfigureAwait(false); 54 | 55 | // Dispose the registration, so the stop isn't called on a disposed httpListener 56 | cancellationTokenRegistration.Dispose(); 57 | 58 | // Set the result to the TaskCompletionSource, so the await on the task finishes 59 | taskCompletionSource.TrySetResult(result); 60 | } 61 | catch (Exception ex) 62 | { 63 | Log.Error().WriteLine(ex, "Error while wait for or processing a request"); 64 | 65 | // Check if cancel was requested, is so set the taskCompletionSource as cancelled 66 | if (cancellationToken.IsCancellationRequested) 67 | { 68 | taskCompletionSource.TrySetCanceled(); 69 | } 70 | else 71 | { 72 | // Not cancelled, so we use the exception 73 | taskCompletionSource.TrySetException(ex); 74 | } 75 | throw; 76 | } 77 | }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); 78 | 79 | // Return the taskCompletionSource.Task so the caller can await on it 80 | return taskCompletionSource.Task; 81 | } 82 | } 83 | } 84 | 85 | #endif -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/MiscExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions; 5 | 6 | /// 7 | /// Misc extensions 8 | /// 9 | public static class MiscExtensions 10 | { 11 | /// 12 | /// Create a query string from a list of keyValuePairs 13 | /// 14 | /// type for the value, sometimes it's easier to let this method call ToString on your type. 15 | /// list of keyValuePair with string,T 16 | /// name1=value1&name2=value2 etc... 17 | public static string ToQueryString(this IEnumerable> keyValuePairs) 18 | { 19 | if (keyValuePairs is null) 20 | { 21 | throw new ArgumentNullException(nameof(keyValuePairs)); 22 | } 23 | var queryBuilder = new StringBuilder(); 24 | 25 | foreach (var keyValuePair in keyValuePairs) 26 | { 27 | queryBuilder.Append($"{keyValuePair.Key}"); 28 | if (keyValuePair.Value != null) 29 | { 30 | var encodedValue = Uri.EscapeDataString(keyValuePair.Value?.ToString()); 31 | queryBuilder.Append($"={encodedValue}"); 32 | } 33 | queryBuilder.Append('&'); 34 | } 35 | if (queryBuilder.Length > 0) 36 | { 37 | queryBuilder.Length -= 1; 38 | } 39 | return queryBuilder.ToString(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Support/ContentItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Support; 5 | 6 | /// 7 | /// This class contains all the information on the content that will be used to build a request 8 | /// 9 | internal class ContentItem 10 | { 11 | public object Content { get; set; } 12 | public string ContentFileName { get; set; } 13 | public string ContentName { get; set; } 14 | public string ContentType { get; set; } 15 | public int Order { get; set; } 16 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Support/HttpPartAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Support; 5 | 6 | /// 7 | /// This attribute marks a property in a HttpRequestAttributed or HttpResponseAttribute class as being a part for 8 | /// processing 9 | /// 10 | [AttributeUsage(AttributeTargets.Property)] 11 | public class HttpPartAttribute : Attribute 12 | { 13 | /// 14 | /// Constructor 15 | /// 16 | /// HttpParts 17 | public HttpPartAttribute(HttpParts part) 18 | { 19 | Part = part; 20 | } 21 | 22 | /// 23 | /// Order of the content when using multi-part content 24 | /// 25 | public int Order { get; set; } 26 | 27 | /// 28 | /// Use this to specify what the property is representing 29 | /// 30 | public HttpParts Part { get; set; } 31 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Support/HttpParts.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Support; 5 | 6 | /// 7 | /// Marker for the response 8 | /// 9 | public enum HttpParts 10 | { 11 | /// 12 | /// Default value. 13 | /// 14 | None, 15 | 16 | /// 17 | /// The property specifies the boundary of a multi-part 18 | /// 19 | MultipartBoundary, 20 | 21 | /// 22 | /// The property specifies the name of the content in a multi-part post 23 | /// 24 | RequestMultipartName, 25 | 26 | /// 27 | /// The property specifies the filename of the content in a multi-part post 28 | /// 29 | RequestMultipartFilename, 30 | 31 | /// 32 | /// Specifies the content for uploading 33 | /// 34 | RequestContent, 35 | 36 | /// 37 | /// Specifies the content-type for uploading 38 | /// 39 | RequestContentType, 40 | 41 | /// 42 | /// Specifies the request headers to send on the request, this should be of type IDictionary where key is string and 43 | /// value is string 44 | /// 45 | RequestHeaders, 46 | 47 | /// 48 | /// The property will get the response content, HttpResponseMessage can also be used 49 | /// 50 | ResponseContent, 51 | 52 | /// 53 | /// The property will get the response content, when an error occured 54 | /// 55 | ResponseErrorContent, 56 | 57 | /// 58 | /// Specifies the content-type, either for uploading or for the response 59 | /// 60 | ResponseContentType, 61 | 62 | /// 63 | /// The Http-Status code, should be of type HttpStatusCode 64 | /// 65 | ResponseStatuscode, 66 | 67 | /// 68 | /// Marks HttpResponseHeaders, 69 | /// 70 | ResponseHeaders 71 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Support/HttpRequestAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Support; 5 | 6 | /// 7 | /// This attribute marks a class as "http content" for a request 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class HttpRequestAttribute : Attribute 11 | { 12 | /// 13 | /// "Force" multi-part, even if there is only one content 14 | /// 15 | public bool MultiPart { get; set; } 16 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Support/HttpResponseAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Support; 5 | 6 | /// 7 | /// This attribute marks a class as "http content" for a response 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class HttpResponseAttribute : Attribute 11 | { 12 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Support/MediaTypes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Runtime.Serialization; 5 | 6 | namespace Dapplo.HttpExtensions.Support; 7 | 8 | /// 9 | /// Use this enum for the creating the accept header or checking the content-type 10 | /// 11 | public enum MediaTypes 12 | { 13 | /// 14 | /// Used for Json 15 | /// 16 | [EnumMember(Value = "application/json")] Json, 17 | 18 | /// 19 | /// Used for Xml 20 | /// 21 | [EnumMember(Value = "application/xml")] Xml, 22 | 23 | /// 24 | /// Used for Xml 25 | /// 26 | [EnumMember(Value = "text/xml")] XmlReadable, 27 | 28 | /// 29 | /// Used for Html 30 | /// 31 | [EnumMember(Value = "text/html")] Html, 32 | 33 | /// 34 | /// Used for Text 35 | /// 36 | [EnumMember(Value = "text/plain")] Txt, 37 | 38 | 39 | /// 40 | /// Used for json 41 | /// 42 | [EnumMember(Value = "text/json")] TxtJson, 43 | 44 | /// 45 | /// Used for a www form which is url encoded 46 | /// 47 | [EnumMember(Value = "application/x-www-form-urlencoded")] WwwFormUrlEncoded, 48 | 49 | /// 50 | /// Image type gif 51 | /// 52 | [EnumMember(Value = "image/gif")] Gif, 53 | 54 | /// 55 | /// Image type jpeg 56 | /// 57 | [EnumMember(Value = "image/jpeg")] Jpeg, 58 | 59 | /// 60 | /// Image type png 61 | /// 62 | [EnumMember(Value = "image/png")] Png, 63 | 64 | /// 65 | /// Image type bmp 66 | /// 67 | [EnumMember(Value = "image/bmp")] Bmp, 68 | 69 | /// 70 | /// Image type tiff 71 | /// 72 | [EnumMember(Value = "image/tiff")] Tiff, 73 | 74 | /// 75 | /// Image type Icon (.ico) 76 | /// 77 | [EnumMember(Value = "image/x-icon")] Icon, 78 | 79 | /// 80 | /// Image type SVG 81 | /// 82 | [EnumMember(Value = "image/svg+xml")] Svg, 83 | 84 | /// 85 | /// Rss feed (not Atom) 86 | /// 87 | [EnumMember(Value = "application/rss+xml")] Rss 88 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/Support/ProgressStreamReport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Dapplo.HttpExtensions.Support; 5 | 6 | /// 7 | /// Contains the pertinent data for a ProgressStream Report event. 8 | /// 9 | public class ProgressStreamReport 10 | { 11 | /// 12 | /// Default constructor for ProgressStreamReport. 13 | /// 14 | public ProgressStreamReport() 15 | { 16 | } 17 | 18 | /// 19 | /// Creates a new ProgressStreamReport initializing its members. 20 | /// 21 | /// The number of bytes that were read/written to/from the stream. 22 | /// The total length of the stream in bytes. 23 | /// The current position in the stream. 24 | /// True if the bytes were read from the stream, false if they were written. 25 | public ProgressStreamReport(int bytesMoved, long streamLength, long streamPosition, bool wasRead) : this() 26 | { 27 | BytesMoved = bytesMoved; 28 | StreamLength = streamLength; 29 | StreamPosition = streamPosition; 30 | WasRead = wasRead; 31 | } 32 | 33 | /// 34 | /// The number of bytes that were read/written to/from the stream. 35 | /// 36 | public int BytesMoved { get; private set; } 37 | 38 | /// 39 | /// The total length of the stream in bytes, 0 if the stream isn't seekable 40 | /// 41 | public long StreamLength { get; private set; } 42 | 43 | /// 44 | /// The current position in the stream, 0 if the stream isn't seekable 45 | /// 46 | public long StreamPosition { get; private set; } 47 | 48 | /// 49 | /// True if the bytes were read from the stream, false if they were written. 50 | /// 51 | public bool WasRead { get; } 52 | } -------------------------------------------------------------------------------- /src/Dapplo.HttpExtensions/UriParseExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dapplo and contributors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace Dapplo.HttpExtensions; 8 | 9 | /// 10 | /// Uri extensions which help with parsing 11 | /// 12 | public static class UriParseExtensions 13 | { 14 | /// 15 | /// Query-string To Dictionary creates a IDictionary 16 | /// 17 | /// query string which is processed 18 | /// IDictionary string, string 19 | public static IDictionary QueryStringToDictionary(string queryString) 20 | { 21 | var parameters = new SortedDictionary(); 22 | foreach (var keyValuePair in QueryStringToKeyValuePairs(queryString)) 23 | { 24 | if (parameters.ContainsKey(keyValuePair.Key)) 25 | { 26 | parameters[keyValuePair.Key] = keyValuePair.Value; 27 | } 28 | else 29 | { 30 | parameters.Add(keyValuePair.Key, keyValuePair.Value); 31 | } 32 | } 33 | return parameters; 34 | } 35 | 36 | /// 37 | /// Query-string To KeyValuePairs creates a List with KeyValuePair which have the name-values 38 | /// 39 | /// query string which is processed 40 | /// List KeyValuePair string, string 41 | public static IEnumerable> QueryStringToKeyValuePairs(string queryString) 42 | { 43 | if (string.IsNullOrEmpty(queryString)) 44 | { 45 | yield break; 46 | } 47 | // remove starting ? from query-string if needed 48 | if (queryString.StartsWith("?")) 49 | { 50 | queryString = queryString.Substring(1); 51 | } 52 | foreach (var vp in Regex.Split(queryString, "&")) 53 | { 54 | if (string.IsNullOrEmpty(vp)) 55 | { 56 | continue; 57 | } 58 | var singlePair = Regex.Split(vp, "="); 59 | var name = singlePair[0]; 60 | string value = null; 61 | if (singlePair.Length == 2) 62 | { 63 | value = Uri.UnescapeDataString(singlePair[1]); 64 | } 65 | yield return new KeyValuePair(name, value); 66 | } 67 | } 68 | 69 | /// 70 | /// QueryToDictionary creates a IDictionary with name-values 71 | /// 72 | /// Uri of which the query is processed 73 | /// IDictionary string, string 74 | public static IDictionary QueryToDictionary(this Uri uri) 75 | { 76 | var parameters = new SortedDictionary(); 77 | foreach (var keyValuePair in uri.QueryToKeyValuePairs()) 78 | { 79 | if (parameters.ContainsKey(keyValuePair.Key)) 80 | { 81 | parameters[keyValuePair.Key] = keyValuePair.Value; 82 | } 83 | else 84 | { 85 | parameters.Add(keyValuePair.Key, keyValuePair.Value); 86 | } 87 | } 88 | return parameters; 89 | } 90 | 91 | /// 92 | /// QueryToKeyValuePairs creates a List with KeyValuePair which have the name-values 93 | /// 94 | /// Uri of which the query is processed 95 | /// List KeyValuePair string, string 96 | public static IEnumerable> QueryToKeyValuePairs(this Uri uri) 97 | { 98 | return QueryStringToKeyValuePairs(uri?.Query); 99 | } 100 | 101 | /// 102 | /// QueryToLookup creates a ILookup with name-values 103 | /// 104 | /// Uri of which the query is processed 105 | /// ILookup string, string 106 | public static ILookup QueryToLookup(this Uri uri) 107 | { 108 | var parameters = uri.QueryToKeyValuePairs(); 109 | return parameters.ToLookup(k => k.Key, e => e.Value); 110 | } 111 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | Copyright © Dapplo 2016-2022 4 | Dapplo 5 | icon.png 6 | https://github.com/dapplo/Dapplo.HttpExtensions 7 | git 8 | http://www.dapplo.net/blocks/Dapplo.HttpExtensions/ 9 | MIT 10 | latest 11 | 12 | true 13 | 14 | true 15 | true 16 | 17 | 18 | 19 | true 20 | true 21 | true 22 | 23 | 24 | 25 | true 26 | ..\Dapplo.HttpExtensions.snk 27 | false 28 | 29 | 30 | 31 | false 32 | false 33 | 34 | 35 | 36 | DEBUG;TRACE 37 | True 38 | true 39 | embedded 40 | false 41 | 42 | 43 | 44 | true 45 | embedded 46 | True 47 | 48 | 49 | 50 | 51 | all 52 | runtime; build; native; contentfiles; analyzers 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.0", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapplo/Dapplo.HttpExtensions/437df89f66bae1557bd9f206281405232662f2db/src/icon.png -------------------------------------------------------------------------------- /src/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "1.1", 4 | "publicReleaseRefSpec": [ 5 | ".*/master$" // we release out of master 6 | ], 7 | "nugetPackageVersion": { 8 | "semVer": 2 // optional. Set to either 1 or 2 to control how the NuGet package version string is generated. Default is 1. 9 | }, 10 | "cloudBuild": { 11 | "setVersionVariables": true, 12 | "buildNumber": { 13 | "enabled": true, 14 | "includeCommitId": { 15 | "when": "nonPublicReleaseOnly", 16 | "where": "buildMetadata" 17 | } 18 | } 19 | }, 20 | "inherit": false 21 | } --------------------------------------------------------------------------------