├── .github └── workflows │ └── retype-action.yml ├── .gitignore ├── Acknowledgments.md ├── AuthorBio.md ├── Chapter01 ├── Images │ ├── Chap01-OverviewNetworkProg.png │ ├── Chapter01-01.png │ └── Chapter01-02.png └── chapter01.md ├── Chapter02 ├── Images │ ├── Chap02-FoundationNetworkConcepts.png │ ├── Chapter02-01.jpg │ ├── Chapter02-02.jpg │ ├── Chapter02-03.jpg │ ├── Chapter02-04.jpg │ └── Chapter02-05.jpg └── chapter02.md ├── Chapter03 ├── Images │ └── Chap03-SocketProgramming.png └── chapter03.md ├── Chapter04 ├── Images │ └── Chap04-AsyncProgramming.png └── chapter04.md ├── Chapter05 ├── Images │ ├── Chap05-Multithreaded.png │ ├── ConcurrencyVisualizer.png │ ├── DiagnosticTools.png │ ├── MonitoringToolWindow.png │ └── ParallelStack.png └── chapter05.md ├── Chapter06 ├── Images │ ├── Chap06-ErrorHandling.png │ └── ExceptionHierarchy.png └── chapter06.md ├── Chapter07 ├── Images │ └── Chap07-DataSerialization.png └── chapter07.md ├── Chapter08 ├── Images │ ├── 2024-06-03_08-19-47.png │ ├── 2024-06-03_08-20-57.png │ ├── 2024-06-10_09-25-25.png │ ├── 2024-06-10_11-23-50.png │ ├── 2024-06-10_11-28-34.png │ ├── 2024-06-10_11-30-04.png │ ├── 2024-06-10_11-32-00.png │ ├── 2024-06-10_21-12-49.png │ └── Chap08-NetworkPerformance.png └── chapter08.md ├── Chapter09 ├── Images │ ├── Chap09-HTTP-REST-Web-APIs.png │ ├── HTTP-Status-Codes.png │ ├── HTTP-Verbs.png │ ├── VS22-WebAPI-Additional.png │ ├── VS22-WebAPI-Creation.png │ ├── VS22-WebAPI-Structure.png │ └── VS22-WebAPI-Swagger.png └── chapter09.md ├── Chapter10 ├── Images │ └── Chap10-WebSockets.png └── chapter10.md ├── Chapter11 ├── Images │ ├── Chap11-WebRTC.png │ ├── WebRTC-Architecture-Overview.png │ └── WebRTC-Signaling-Flow-Diagram.png └── chapter11.md ├── Chapter12 ├── Images │ ├── Chap12-MQTT.png │ ├── MQTT-Publish-Subscribe-Model-Diagram.png │ └── QoS-Level-Workflow-Diagram.png └── chapter12.md ├── Chapter13 ├── Images │ ├── Chap13-gRPC.png │ ├── Comparison-gRPC-REST.png │ └── gRPC-Architecture-Overview.png └── chapter13.md ├── Chapter14 ├── Images │ ├── Chap14-WebHooks.png │ └── WebHook-Lifecycle-Diagram.png └── chapter14.md ├── Chapter15 ├── Images │ ├── Chap15-Message_Queuing.png │ └── Message-Queue-Architecture-Overview.png └── chapter15.md ├── Chapter16 ├── Images │ ├── Chap16-SignalR.png │ └── SignalR-Architecture-Overview.png └── chapter16.md ├── Chapter17 ├── Images │ ├── Debug-Console-HTTP3.png │ ├── Detailed-Logs-HTTP3.png │ ├── Show-HTTP3-Response-Headers.png │ └── chap17-QUIC.png └── chapter17.md ├── Copyright.md ├── Dedication.md ├── Epigraph.md ├── Foreward.md ├── Preface.md ├── README.md ├── Translations.md ├── Updates-Corrections.md ├── images ├── Chris-Woodruff-Presenting.jpg ├── favicon.png ├── network200.png └── networkheader.png ├── index.md ├── license.md ├── retype.yml └── src ├── Ch03 ├── ch03-sockets.sln ├── client │ ├── Program.cs │ └── client.csproj └── server │ ├── Program.cs │ └── server.csproj ├── Ch04 ├── ch04.sln └── csharp-async-example │ ├── AsynchronousChef.cs │ ├── Program.cs │ ├── SynchronousChef.cs │ └── csharp-async-example.csproj ├── Ch05 ├── ClientServer │ ├── Client │ │ ├── Client.csproj │ │ └── TcpClientExample.cs │ └── TcpServer │ │ ├── SecureMultithreadedServer.cs │ │ └── TcpServer.csproj └── ch05.sln ├── Ch06 └── ch06.sln ├── Ch07 └── ch07.sln ├── Ch08 └── ch08.sln ├── Ch09 └── ch09.sln ├── Ch10 └── ch10.sln ├── Ch11 ├── BlazorWebRTC │ ├── App.razor │ ├── BlazorWebRTC.csproj │ ├── Data │ │ ├── WeatherForecast.cs │ │ └── WeatherForecastService.cs │ ├── Pages │ │ ├── Counter.razor │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── FetchData.razor │ │ ├── Index.razor │ │ └── _Host.cshtml │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Shared │ │ ├── MainLayout.razor │ │ ├── MainLayout.razor.css │ │ ├── NavMenu.razor │ │ ├── NavMenu.razor.css │ │ └── SurveyPrompt.razor │ ├── _Imports.razor │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ ├── bootstrap │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ ├── open-iconic │ │ │ ├── FONT-LICENSE │ │ │ ├── ICON-LICENSE │ │ │ └── font │ │ │ │ ├── css │ │ │ │ └── open-iconic-bootstrap.min.css │ │ │ │ └── fonts │ │ │ │ ├── open-iconic.eot │ │ │ │ ├── open-iconic.otf │ │ │ │ ├── open-iconic.svg │ │ │ │ ├── open-iconic.ttf │ │ │ │ └── open-iconic.woff │ │ └── site.css │ │ ├── favicon.png │ │ └── webrtc.js ├── WebRTCSignalingServer │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── WebRTCSignalingServer.csproj │ ├── WebRTCSignalingServer.http │ ├── appsettings.Development.json │ └── appsettings.json └── ch11.sln ├── Ch12 ├── MQTT-Simple │ ├── MQTTClient │ │ ├── MQTTClient.csproj │ │ └── Program.cs │ └── MQTTServer │ │ ├── MQTTServer.csproj │ │ └── Program.cs ├── MQTT-TLS │ ├── MqttBroker │ │ ├── MqttBroker.csproj │ │ └── Program.cs │ └── MqttClientApp │ │ ├── MqttClientApp.csproj │ │ └── Program.cs └── ch12.sln ├── Ch13 ├── GrpcClient │ ├── GrpcClient.csproj │ ├── Program.cs │ └── Protos │ │ └── todo.proto ├── GrpcServer │ ├── GrpcServer.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Protos │ │ └── todo.proto │ ├── Services │ │ └── TodoService.cs │ ├── appsettings.Development.json │ └── appsettings.json └── ch13.sln ├── Ch14 ├── WebHookReceiver │ ├── Controllers │ │ └── WebHookController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SecurityHelpers.cs │ ├── WebHookPayload.cs │ ├── WebHookReceiver.csproj │ ├── WebHookReceiver.http │ ├── appsettings.Development.json │ └── appsettings.json ├── WebHookSender │ ├── OrderService.cs │ ├── Program.cs │ ├── SecurityHelpers.cs │ ├── SenderPayload.cs │ ├── WebHookSender.cs │ └── WebHookSender.csproj ├── ch14.sln └── global.json ├── Ch15 ├── MessageConsumerApp │ ├── MessageConsumer.cs │ ├── MessageConsumerApp.csproj │ └── Program.cs ├── MessageConsumerTests │ ├── MessageConsumerTests.csproj │ └── UnitTest1.cs ├── MessageProducerApp │ ├── MessageProducer.cs │ ├── MessageProducerApp.csproj │ └── Program.cs ├── MessageQueueTests │ ├── MessageQueueTests.csproj │ └── UnitTest1.cs └── ch15.sln ├── Ch16 ├── SignalRClient │ ├── Program.cs │ └── SignalRClient.csproj ├── SignalRServerExample │ ├── ChatHub.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SignalRServerExample.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ └── index.html └── ch16.sln └── Ch17 ├── HTTP3-Perf-Client-Demo ├── HTTP3-Perf-Client-Demo.csproj └── Program.cs ├── HTTP3-Perf-Server-Demo ├── HTTP3-Perf-Server-Demo.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.Development.json └── appsettings.json ├── QUICConsole ├── Program.cs └── QUICConsole.csproj ├── QUICServer ├── Program.cs ├── Properties │ └── launchSettings.json ├── QUICServer.csproj ├── appsettings.Development.json └── appsettings.json ├── QuicConnection-Client ├── Program.cs └── QuicConnection-Client.csproj ├── QuicListener-Server ├── Program.cs └── QuicListener-Server.csproj └── ch17.sln /.github/workflows/retype-action.yml: -------------------------------------------------------------------------------- 1 | name: Publish Retype powered website to GitHub Pages 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | name: Publish to retype branch 11 | 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: write 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-dotnet@v1 21 | with: 22 | dotnet-version: 7.0.x 23 | 24 | - uses: retypeapp/action-build@latest 25 | 26 | - uses: retypeapp/action-github-pages@latest 27 | with: 28 | update-branch: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | 400 | .idea/ 401 | .DS_Store 402 | .DS_Store? 403 | .DS_Store 404 | .DS_Store 405 | .DS_Store 406 | .idea/workspace.xml 407 | .DS_Store 408 | .DS_Store 409 | .DS_Store 410 | .DS_Store 411 | -------------------------------------------------------------------------------- /Acknowledgments.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 22 3 | icon: thumbsup 4 | --- 5 | # Acknowledgments 6 | 7 | This is a basic page, with only a title and some text content. -------------------------------------------------------------------------------- /AuthorBio.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: -1 3 | icon: id-badge 4 | label: Author Bio 5 | meta: 6 | title: "Author Bio" 7 | --- 8 | # Author Bio 9 | 10 | ![Chris Woodruff Presenting](images/Chris-Woodruff-Presenting.jpg) 11 | 12 | Chris Woodruff, also known as Woody, is an Architect at Real Times Technologies and brings nearly three decades of industry expertise, having launched his career before the first .COM boom. Renowned for his contributions to software development and architecture, Woody is a regular speaker at international conferences, where he shares his deep knowledge on topics ranging from database development to APIs and web technologies. 13 | 14 | A dedicated mentor, Woody thrives on guiding fellow developers and enhancing their skills through his talks, written work, and digital content. He co-hosts the popular “Breakpoint Show” podcast and YouTube channel, which he uses to connect with and educate the tech community. He is also writing a book covering network programming with C# and .NET. 15 | 16 | Woody’s interests extend beyond his professional life, adding a personal touch to his character. He is a passionate bourbon enthusiast, often embarking on adventures along the Bourbon Trail in search of unique finds to savor and share with friends. Family time is a cherished part of his life, and he often shares insights from his professional journey on his blog at https://woodruff.dev. To stay updated on his latest projects and adventures, follow him on BlueSky at https://bsky.app/profile/woodruff.dev or Mastodon at https://mastodon.social/@cwoodruff, where he shares his thoughts and experiences, fostering a sense of connection with his audience. 17 | 18 | ## Contact Info 19 | 20 | * Email - [chris@woodruff.dev](mailto:chris@woodruff.dev) 21 | * WhatsApp - https://wa.me/16167246885 22 | * GitHub - https://github.com/cwoodruff -------------------------------------------------------------------------------- /Chapter01/Images/Chap01-OverviewNetworkProg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter01/Images/Chap01-OverviewNetworkProg.png -------------------------------------------------------------------------------- /Chapter01/Images/Chapter01-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter01/Images/Chapter01-01.png -------------------------------------------------------------------------------- /Chapter01/Images/Chapter01-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter01/Images/Chapter01-02.png -------------------------------------------------------------------------------- /Chapter02/Images/Chap02-FoundationNetworkConcepts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter02/Images/Chap02-FoundationNetworkConcepts.png -------------------------------------------------------------------------------- /Chapter02/Images/Chapter02-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter02/Images/Chapter02-01.jpg -------------------------------------------------------------------------------- /Chapter02/Images/Chapter02-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter02/Images/Chapter02-02.jpg -------------------------------------------------------------------------------- /Chapter02/Images/Chapter02-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter02/Images/Chapter02-03.jpg -------------------------------------------------------------------------------- /Chapter02/Images/Chapter02-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter02/Images/Chapter02-04.jpg -------------------------------------------------------------------------------- /Chapter02/Images/Chapter02-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter02/Images/Chapter02-05.jpg -------------------------------------------------------------------------------- /Chapter03/Images/Chap03-SocketProgramming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter03/Images/Chap03-SocketProgramming.png -------------------------------------------------------------------------------- /Chapter04/Images/Chap04-AsyncProgramming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter04/Images/Chap04-AsyncProgramming.png -------------------------------------------------------------------------------- /Chapter05/Images/Chap05-Multithreaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter05/Images/Chap05-Multithreaded.png -------------------------------------------------------------------------------- /Chapter05/Images/ConcurrencyVisualizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter05/Images/ConcurrencyVisualizer.png -------------------------------------------------------------------------------- /Chapter05/Images/DiagnosticTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter05/Images/DiagnosticTools.png -------------------------------------------------------------------------------- /Chapter05/Images/MonitoringToolWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter05/Images/MonitoringToolWindow.png -------------------------------------------------------------------------------- /Chapter05/Images/ParallelStack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter05/Images/ParallelStack.png -------------------------------------------------------------------------------- /Chapter06/Images/Chap06-ErrorHandling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter06/Images/Chap06-ErrorHandling.png -------------------------------------------------------------------------------- /Chapter06/Images/ExceptionHierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter06/Images/ExceptionHierarchy.png -------------------------------------------------------------------------------- /Chapter07/Images/Chap07-DataSerialization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter07/Images/Chap07-DataSerialization.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-03_08-19-47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-03_08-19-47.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-03_08-20-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-03_08-20-57.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-10_09-25-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-10_09-25-25.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-10_11-23-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-10_11-23-50.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-10_11-28-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-10_11-28-34.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-10_11-30-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-10_11-30-04.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-10_11-32-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-10_11-32-00.png -------------------------------------------------------------------------------- /Chapter08/Images/2024-06-10_21-12-49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/2024-06-10_21-12-49.png -------------------------------------------------------------------------------- /Chapter08/Images/Chap08-NetworkPerformance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter08/Images/Chap08-NetworkPerformance.png -------------------------------------------------------------------------------- /Chapter09/Images/Chap09-HTTP-REST-Web-APIs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter09/Images/Chap09-HTTP-REST-Web-APIs.png -------------------------------------------------------------------------------- /Chapter09/Images/HTTP-Status-Codes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter09/Images/HTTP-Status-Codes.png -------------------------------------------------------------------------------- /Chapter09/Images/HTTP-Verbs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter09/Images/HTTP-Verbs.png -------------------------------------------------------------------------------- /Chapter09/Images/VS22-WebAPI-Additional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter09/Images/VS22-WebAPI-Additional.png -------------------------------------------------------------------------------- /Chapter09/Images/VS22-WebAPI-Creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter09/Images/VS22-WebAPI-Creation.png -------------------------------------------------------------------------------- /Chapter09/Images/VS22-WebAPI-Structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter09/Images/VS22-WebAPI-Structure.png -------------------------------------------------------------------------------- /Chapter09/Images/VS22-WebAPI-Swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter09/Images/VS22-WebAPI-Swagger.png -------------------------------------------------------------------------------- /Chapter10/Images/Chap10-WebSockets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter10/Images/Chap10-WebSockets.png -------------------------------------------------------------------------------- /Chapter11/Images/Chap11-WebRTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter11/Images/Chap11-WebRTC.png -------------------------------------------------------------------------------- /Chapter11/Images/WebRTC-Architecture-Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter11/Images/WebRTC-Architecture-Overview.png -------------------------------------------------------------------------------- /Chapter11/Images/WebRTC-Signaling-Flow-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter11/Images/WebRTC-Signaling-Flow-Diagram.png -------------------------------------------------------------------------------- /Chapter12/Images/Chap12-MQTT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter12/Images/Chap12-MQTT.png -------------------------------------------------------------------------------- /Chapter12/Images/MQTT-Publish-Subscribe-Model-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter12/Images/MQTT-Publish-Subscribe-Model-Diagram.png -------------------------------------------------------------------------------- /Chapter12/Images/QoS-Level-Workflow-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter12/Images/QoS-Level-Workflow-Diagram.png -------------------------------------------------------------------------------- /Chapter13/Images/Chap13-gRPC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter13/Images/Chap13-gRPC.png -------------------------------------------------------------------------------- /Chapter13/Images/Comparison-gRPC-REST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter13/Images/Comparison-gRPC-REST.png -------------------------------------------------------------------------------- /Chapter13/Images/gRPC-Architecture-Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter13/Images/gRPC-Architecture-Overview.png -------------------------------------------------------------------------------- /Chapter14/Images/Chap14-WebHooks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter14/Images/Chap14-WebHooks.png -------------------------------------------------------------------------------- /Chapter14/Images/WebHook-Lifecycle-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter14/Images/WebHook-Lifecycle-Diagram.png -------------------------------------------------------------------------------- /Chapter15/Images/Chap15-Message_Queuing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter15/Images/Chap15-Message_Queuing.png -------------------------------------------------------------------------------- /Chapter15/Images/Message-Queue-Architecture-Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter15/Images/Message-Queue-Architecture-Overview.png -------------------------------------------------------------------------------- /Chapter16/Images/Chap16-SignalR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter16/Images/Chap16-SignalR.png -------------------------------------------------------------------------------- /Chapter16/Images/SignalR-Architecture-Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter16/Images/SignalR-Architecture-Overview.png -------------------------------------------------------------------------------- /Chapter17/Images/Debug-Console-HTTP3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter17/Images/Debug-Console-HTTP3.png -------------------------------------------------------------------------------- /Chapter17/Images/Detailed-Logs-HTTP3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter17/Images/Detailed-Logs-HTTP3.png -------------------------------------------------------------------------------- /Chapter17/Images/Show-HTTP3-Response-Headers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter17/Images/Show-HTTP3-Response-Headers.png -------------------------------------------------------------------------------- /Chapter17/Images/chap17-QUIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/Chapter17/Images/chap17-QUIC.png -------------------------------------------------------------------------------- /Copyright.md: -------------------------------------------------------------------------------- 1 | --- 2 | visibility: hidden 3 | --- 4 | # Copyright 5 | 6 | Copyright © 2024-2025 by Christopher Woodruff 7 | 8 | All rights reserved. No part of this publication may be reproduced, distributed, or transmitted in any form or by any means, including photocopying, recording, or other electronic or mechanical methods, without the prior written permission of the publisher, except in the case of brief quotations embodied in critical reviews and certain other noncommercial uses permitted by copyright law. 9 | 10 | [//]: # (ISBN: [Insert ISBN] ) 11 | 12 | [//]: # () 13 | [//]: # ([Publisher Name] ) 14 | 15 | [//]: # () 16 | [//]: # ([Publisher Address] ) 17 | 18 | [//]: # () 19 | [//]: # ([City, State, Zip Code]) 20 | 21 | [//]: # () 22 | [//]: # (www.publisherwebsite.com ) 23 | 24 | [//]: # () 25 | [//]: # (Printed in [Country] ) 26 | 27 | [//]: # () 28 | [//]: # ([Optional: Additional legal notices or disclaimers]) 29 | -------------------------------------------------------------------------------- /Dedication.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 24 3 | icon: heart-fill 4 | --- 5 | # Dedication 6 | 7 | >To Tracy, my steadfast partner and the light of my life, whose support and love make everything possible. And to our children, Spencer, Nolan, and Mallory, who inspire me daily with their curiosity, joy, and boundless energy. While technical, this book is imbued with the motivation and strength you give me. May you always know how deeply you influence my world and the work I create. 8 | > 9 | >Thank you for being my anchor and my sail, making this journey not only possible but immensely rewarding. 10 | > 11 | >With all my love and gratitude. 12 | 13 | -------------------------------------------------------------------------------- /Epigraph.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 23 3 | icon: quote 4 | --- 5 | # Epigraph 6 | 7 | 8 | > _The Internet is not just one thing, it's a collection of things - of 9 | > numerous communications networks that all speak the same digital language._ 10 | > 11 | > -- James H. Clark -------------------------------------------------------------------------------- /Foreward.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 21 3 | icon: star-fill 4 | --- 5 | # Forward 6 | 7 | This is a basic page, with only a title and some text content. -------------------------------------------------------------------------------- /Preface.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 20 3 | icon: info 4 | --- 5 | # Preface 6 | 7 | This is a basic page, with only a title and some text content. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # book-network-programming-csharp -------------------------------------------------------------------------------- /Translations.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 20 3 | icon: info 4 | --- 5 | # Translations 6 | 7 | 8 | | Language | Author | Notes | URL | 9 | |:--------:|:------:|:-----:|:---:| 10 | | Chinese | | | | 11 | | Spanish | | | | 12 | | French | | | | 13 | | Japanese | | | | 14 | | Swedish | | | | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Updates-Corrections.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | icon: sparkle-fill 4 | label: Updates-Corrections 5 | meta: 6 | title: "Updates and Corrections" 7 | --- 8 | # Updates and Corrections 9 | 10 | I want to thank every reader who took the time to share their feedback and corrections on my book. Your insights and meticulous attention to detail have helped enhance the work's quality and enriched the reading experience for others. It's through your engagement and thoughtful contributions that the book has evolved and improved. Thank you for your invaluable support and for being an integral part of this journey. Your feedback is genuinely appreciated. 11 | 12 | | Date | Chapter | Section | Acknowledgement | Notes | 13 | |:---------------:|:-------:|:-------------------------------:|:------------------------:|:--------------------------------------------------:| 14 | | 10 April 2024 | 3 | Handling data of unknown length | Stephen Cleary | Expanding upon and demostrating the Decoder class | 15 | | 6 May 2024 | 1 | Technical Requirements | @CodeConscious | Update repository link in Chapter 1 | 16 | | 2 June 2024 | 2, 3 | Typos and code suggestions | Michał Turczyn @mturczyn | Numerous typos and code suggestions about sockets | 17 | | 4 October 2024 | 2 | Subnetting techniques | Chukwuma Akunyili | fixed an inline mathematical expression expression | 18 | | 6 October 2024 | 3 | Session timeouts | Chukwuma Akunyili | fixed a bug in the code | 19 | | 6 December 2024 | 3 | Concurrent collections for client management | @semuserable | fixed an issue in the code | -------------------------------------------------------------------------------- /images/Chris-Woodruff-Presenting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/images/Chris-Woodruff-Presenting.jpg -------------------------------------------------------------------------------- /images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/images/favicon.png -------------------------------------------------------------------------------- /images/network200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/images/network200.png -------------------------------------------------------------------------------- /images/networkheader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/images/networkheader.png -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | # Beyond Boundaries - Networking Programming with C# 12 and .NET 8 Book Home 2 | 3 | ![](http://woodruff.dev/wp-content/uploads/2024/03/networkheader.png) 4 | 5 | **NEWS!!** Now you can get the book in PDF or EPub formats! [Leanpub Book Page](https://leanpub.com/csharp-networking) 6 | 7 | Have questions or feedback about the book? My email is [cwoodruff@live.com](mailto:cwoodruff@live.com). 8 | 9 | For the source code for the book, visit the book's GitHub repo. [book-network-programming-csharp 10 | ](https://github.com/cwoodruff/book-network-programming-csharp) 11 | 12 | To find translations, please check out the [Translations](./Translations.md) page. 13 | 14 | | Chapter | Title | Published | 15 | |---------|----------------------------------------------------------------------------|-----------| 16 | | 1 | [Overview of Network Programming](./Chapter01/chapter01.md) | ✔️ | 17 | | 2 | [Fundamentals of Networking Concepts](./Chapter02/chapter02.md) | ✔️ | 18 | | 3 | [Introduction to Socket Programming](./Chapter03/chapter03.md) | ✔️ | 19 | | 4 | [Asynchronous Programming with Async/Await](./Chapter04/chapter04.md) | ✔️ | 20 | | 5 | [Multithreading in Network Applications](./Chapter05/chapter05.md) | ✔️ | 21 | | 6 | [Error Handling and Fault Tolerance Strategies](./Chapter06/Chapter06.md) | ✔️ | 22 | | 7 | [Data Serialization Techniques](./Chapter07/Chapter07.md) | ✔️ | 23 | | 8 | [Network Performance Optimization](./Chapter08/Chapter08.md) | ✔️ | 24 | | 9 | [Working with REST APIs](./Chapter09/chapter09.md) | ✔️ | 25 | | 10 | [Working with WebSockets](./Chapter10/chapter10.md) | ✔️ | 26 | | 11 | [Working with WebRTC](./Chapter11/chapter11.md) | ✔️ | 27 | | 12 | [Working with MQTT for IoT (Internet of Things)](./Chapter12/chapter12.md) | ✔️ | 28 | | 13 | [Working with gRPC](./Chapter13/chapter13.md) | ✔️ | 29 | | 14 | [Working with WebHooks](./Chapter14/chapter14.md) | ✔️ | 30 | | 15 | [Implementing Message Queuing](./Chapter15/chapter15.md) | ✔️ | 31 | | 16 | [Using SignalR](./Chapter16/chapter16.md) | ✔️ | 32 | | 17 | [Looking to the Future with QUIC](./Chapter17/chapter17.md) | ✔️ | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | --- 2 | visibility: hidden 3 | --- 4 | 5 | # License for the Book's Code 6 | 7 | The MIT License (MIT) 8 | Copyright © 2023 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”) to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /retype.yml: -------------------------------------------------------------------------------- 1 | input: ./ 2 | output: .retype 3 | url: https://cwoodruff.github.io/book-network-programming-csharp/ 4 | branding: 5 | title: Networking Programming with C/# 12 and /.NET 8 6 | label: v1 7 | logo: images/network200.png 8 | logoDark: images/network200.png 9 | edit: 10 | repo: github.com/cwoodruff/book-network-programming-csharp 11 | links: 12 | - text: Home 13 | link: /index.md 14 | icon: home 15 | 16 | - text: Issues 17 | link: https://github.com/cwoodruff/book-network-programming-csharp/issues 18 | icon: comment-discussion 19 | target: blank 20 | 21 | - text: Bluesky 22 | link: https://bsky.app/profile/woodruff.dev 23 | icon: 24 | target: blank 25 | 26 | - text: Mastodon 27 | link: https://mastodon.social/@cwoodruff 28 | icon: 29 | target: blank 30 | footer: 31 | copyright: "© Copyright {{ year }}. All rights reserved Chris Woodruff." 32 | links: 33 | - text: Copyright 34 | link: Copyright.md 35 | icon: question 36 | - text: License 37 | link: License.md 38 | icon: shield-check 39 | editor: 40 | enabled: false 41 | favicon: images/favicon.png 42 | search: 43 | hotkeys: 44 | - "/" 45 | maxResults: 20 46 | minChars: 2 47 | mode: full 48 | noResultsFoundMsg: "No results" 49 | placeholder: Search 50 | integrations: 51 | googleAnalytics: 52 | id: "G-D5P830S5Z3" -------------------------------------------------------------------------------- /src/Ch03/ch03-sockets.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client", "client\client.csproj", "{76113D1D-6169-4768-8C3B-48AF2F4365A7}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server", "server\server.csproj", "{3C674A63-05A0-483B-924A-3B879E33F462}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {76113D1D-6169-4768-8C3B-48AF2F4365A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {76113D1D-6169-4768-8C3B-48AF2F4365A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {76113D1D-6169-4768-8C3B-48AF2F4365A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {76113D1D-6169-4768-8C3B-48AF2F4365A7}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {3C674A63-05A0-483B-924A-3B879E33F462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {3C674A63-05A0-483B-924A-3B879E33F462}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {3C674A63-05A0-483B-924A-3B879E33F462}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {3C674A63-05A0-483B-924A-3B879E33F462}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Ch03/client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | 6 | class Program 7 | { 8 | public static void Main() 9 | { 10 | ExecuteClient(); 11 | } 12 | 13 | static void ExecuteClient() 14 | { 15 | try 16 | { 17 | // Create a TcpClient. 18 | // The client requires a TcpServer that is connected 19 | // to 127.0.0.1 on port 13000. 20 | TcpClient client = new TcpClient("127.0.0.1", 13000); 21 | 22 | // Translate the passed message into ASCII and store it as a Byte array. 23 | String data = "Hello Server!"; 24 | Byte[] bytes = Encoding.ASCII.GetBytes(data); 25 | 26 | // Get a client stream for reading and writing. 27 | NetworkStream stream = client.GetStream(); 28 | 29 | // Send the message to the connected TcpServer. 30 | stream.Write(bytes, 0, bytes.Length); 31 | 32 | Console.WriteLine("Sent: {0}", data); 33 | 34 | // Buffer to store the response bytes. 35 | bytes = new Byte[256]; 36 | 37 | // String to store the response ASCII representation. 38 | String responseData = String.Empty; 39 | 40 | // Read the first batch of the TcpServer response bytes. 41 | Int32 i = stream.Read(bytes, 0, bytes.Length); 42 | responseData = Encoding.ASCII.GetString(bytes, 0, i); 43 | Console.WriteLine("Received: {0}", responseData); 44 | 45 | // Close everything. 46 | stream.Close(); 47 | client.Close(); 48 | } 49 | catch (ArgumentNullException e) 50 | { 51 | Console.WriteLine("ArgumentNullException: {0}", e); 52 | } 53 | catch (SocketException e) 54 | { 55 | Console.WriteLine("SocketException: {0}", e); 56 | } 57 | 58 | Console.WriteLine("\n Press any key to continue..."); 59 | Console.ReadKey(); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Ch03/client/client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Ch03/server/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | 6 | class Program 7 | { 8 | public static void Main() 9 | { 10 | ExecuteServer(); 11 | } 12 | 13 | static void ExecuteServer() 14 | { 15 | // Set the TcpListener on port 13000. 16 | int port = 13000; 17 | TcpListener server = null; 18 | 19 | try 20 | { 21 | // Set the TcpListener on port 13000. 22 | server = new TcpListener(IPAddress.Parse("127.0.0.1"), port); 23 | // Start listening for client requests. 24 | server.Start(); 25 | 26 | // Buffer for reading data 27 | Byte[] bytes = new Byte[256]; 28 | String data = null; 29 | 30 | // Enter the listening loop. 31 | while (true) 32 | { 33 | Console.Write("Waiting for a connection... "); 34 | 35 | // Perform a blocking call to accept requests. 36 | TcpClient client = server.AcceptTcpClient(); 37 | Console.WriteLine("Connected!"); 38 | 39 | data = null; 40 | 41 | // Get a stream object for reading and writing 42 | NetworkStream stream = client.GetStream(); 43 | 44 | int i; 45 | 46 | // Loop to receive all the data sent by the client. 47 | while ((i = stream.Read(bytes, 0, bytes.Length)) != 0) 48 | { 49 | // Translate data bytes to a ASCII string. 50 | data = Encoding.ASCII.GetString(bytes, 0, i); 51 | Console.WriteLine("Received: {0}", data); 52 | 53 | // Process the data sent by the client. 54 | data = data.ToUpper(); 55 | 56 | byte[] msg = Encoding.ASCII.GetBytes(data); 57 | 58 | // Send back a response. 59 | stream.Write(msg, 0, msg.Length); 60 | Console.WriteLine("Sent: {0}", data); 61 | } 62 | 63 | // Shutdown and end connection 64 | client.Close(); 65 | } 66 | } 67 | catch (SocketException e) 68 | { 69 | Console.WriteLine("SocketException: {0}", e); 70 | } 71 | finally 72 | { 73 | // Stop listening for new clients. 74 | server.Stop(); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/Ch03/server/server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Ch04/ch04.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp-async-example", "csharp-async-example\csharp-async-example.csproj", "{0CC619F2-A17E-46DE-9CCD-BA51E9C145F9}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {0CC619F2-A17E-46DE-9CCD-BA51E9C145F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {0CC619F2-A17E-46DE-9CCD-BA51E9C145F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {0CC619F2-A17E-46DE-9CCD-BA51E9C145F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {0CC619F2-A17E-46DE-9CCD-BA51E9C145F9}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /src/Ch04/csharp-async-example/AsynchronousChef.cs: -------------------------------------------------------------------------------- 1 | namespace csharp_async_example; 2 | 3 | public class AsynchronousChef 4 | { 5 | private int DelayMultiply { get; set; } = 100; 6 | 7 | public async Task MakeBreakfast() 8 | { 9 | var waterTask = BoilWater(); 10 | var eggsTask = BoilEggs(); 11 | var baconTask = FryBacon(); 12 | var breadTask = ToastBread(); 13 | 14 | await breadTask; 15 | await ApplyButter(); 16 | await ApplyJam(); 17 | 18 | await Task.WhenAll(eggsTask, baconTask, waterTask); 19 | await PourCoffee(); 20 | await PourJuice(); 21 | } 22 | 23 | private async Task PourCoffee() 24 | { 25 | Console.WriteLine("[Chef] Start pouring coffee"); 26 | await Task.Delay(5 * DelayMultiply); 27 | Console.WriteLine("[Chef] Coffee Ready"); 28 | } 29 | 30 | private async Task BoilEggs() 31 | { 32 | Console.WriteLine("[Chef] Put Eggs into boiling water"); 33 | await Task.Delay(70 * DelayMultiply); 34 | Console.WriteLine("[Chef] Eggs Boiled"); 35 | } 36 | 37 | private async Task FryBacon() 38 | { 39 | Console.WriteLine("[Chef] Throw bacon in pan"); 40 | await Task.Delay(40 * DelayMultiply); 41 | Console.WriteLine("[Chef] Bacon Fried"); 42 | } 43 | 44 | private async Task ToastBread() 45 | { 46 | Console.WriteLine("[Chef] Put bread in toaster"); 47 | await Task.Delay(20 * DelayMultiply); 48 | Console.WriteLine("[Chef] Bread Toasted"); 49 | } 50 | 51 | private async Task ApplyButter() 52 | { 53 | Console.WriteLine("[Chef] Start spreading Butter"); 54 | await Task.Delay(15 * DelayMultiply); 55 | Console.WriteLine("[Chef] Butter applied"); 56 | } 57 | 58 | private async Task ApplyJam() 59 | { 60 | Console.WriteLine("[Chef] Start spreading Jam"); 61 | await Task.Delay(15 * DelayMultiply); 62 | Console.WriteLine("[Chef] Jam applied"); 63 | } 64 | 65 | public async Task PourJuice() 66 | { 67 | Console.WriteLine("[Chef] Start pouring Juice"); 68 | await Task.Delay(5 * DelayMultiply); 69 | Console.WriteLine("[Chef] Juice Ready"); 70 | } 71 | 72 | private async Task BoilWater() 73 | { 74 | Console.WriteLine("[Chef] Set coffee water to boil"); 75 | await Task.Delay(200 * DelayMultiply); 76 | Console.WriteLine("[Chef] Coffee Water is boiled"); 77 | } 78 | 79 | public async Task TellAJoke() 80 | { 81 | return "Joke"; 82 | } 83 | } -------------------------------------------------------------------------------- /src/Ch04/csharp-async-example/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace csharp_async_example; 4 | 5 | public abstract class Program 6 | { 7 | private static Stopwatch? Stopwatch { get; set; } 8 | 9 | static void Main(string[] args) 10 | { 11 | Stopwatch = new Stopwatch(); 12 | Stopwatch.Start(); 13 | Console.WriteLine($"[Kitchen] Started making breakfast - Synchronous Chef"); 14 | 15 | MakeBreakfastWithSynchronousChef(); 16 | 17 | Console.WriteLine($"[Kitchen] Breakfast done in '{Stopwatch.Elapsed.Seconds}'s '{Stopwatch.Elapsed.Milliseconds}'ms"); 18 | Stopwatch.Restart(); 19 | 20 | Console.WriteLine("---"); 21 | Console.WriteLine($"[Kitchen] Started making breakfast - Asynchronous Chef"); 22 | 23 | MakeBreakfastWithAsynchronousChef().Wait(); 24 | 25 | Console.WriteLine($"[Kitchen] Breakfast done in '{Stopwatch.Elapsed.Seconds}'s '{Stopwatch.Elapsed.Milliseconds}'ms"); 26 | } 27 | 28 | static void MakeBreakfastWithSynchronousChef() 29 | { 30 | var chef = new SynchronousChef(); 31 | chef.MakeBreakfast(); 32 | } 33 | 34 | static async Task MakeBreakfastWithAsynchronousChef() 35 | { 36 | var chef = new AsynchronousChef(); 37 | await chef.MakeBreakfast(); 38 | } 39 | 40 | static async Task MakeBreakfastAndDisturbChef() 41 | { 42 | var chef = new AsynchronousChef(); 43 | var breakfastTask = chef.MakeBreakfast(); 44 | 45 | Task.Delay(1500).Wait(); 46 | Console.WriteLine($"[Kitchen] a joke from chef: '{chef.TellAJoke().Result}'"); 47 | 48 | Task.Delay(9000).Wait(); 49 | chef.PourJuice().Wait(); 50 | 51 | await breakfastTask; 52 | } 53 | } -------------------------------------------------------------------------------- /src/Ch04/csharp-async-example/SynchronousChef.cs: -------------------------------------------------------------------------------- 1 | namespace csharp_async_example; 2 | 3 | public class SynchronousChef 4 | { 5 | private int DelayMultiply { get; set; } = 100; 6 | 7 | public void MakeBreakfast() 8 | { 9 | BoilWater(); 10 | BoilEggs(); 11 | FryBacon(); 12 | ToastBread(); 13 | ApplyButter(); 14 | ApplyJam(); 15 | PourCoffee(); 16 | PourJuice(); 17 | } 18 | 19 | private void PourCoffee() 20 | { 21 | Console.WriteLine("[Chef] Start pouring coffee"); 22 | Thread.Sleep(5 * DelayMultiply); 23 | Console.WriteLine("[Chef] Coffee Ready"); 24 | } 25 | 26 | private void BoilEggs() 27 | { 28 | Console.WriteLine("[Chef] Put Eggs into boiling water"); 29 | Thread.Sleep(70 * DelayMultiply); 30 | Console.WriteLine("[Chef] Eggs Boiled"); 31 | } 32 | 33 | private void FryBacon() 34 | { 35 | Console.WriteLine("[Chef] Throw bacon in pan"); 36 | Thread.Sleep(40 * DelayMultiply); 37 | Console.WriteLine("[Chef] Bacon Fried"); 38 | } 39 | 40 | private void ToastBread() 41 | { 42 | Console.WriteLine("[Chef] Put bread in toaster"); 43 | Thread.Sleep(20 * DelayMultiply); 44 | Console.WriteLine("[Chef] Bread Toasted"); 45 | } 46 | 47 | private void ApplyButter() 48 | { 49 | Console.WriteLine("[Chef] Start spreading Butter"); 50 | Thread.Sleep(15 * DelayMultiply); 51 | Console.WriteLine("[Chef] Butter applied"); 52 | } 53 | 54 | private void ApplyJam() 55 | { 56 | Console.WriteLine("[Chef] Start spreading Jam"); 57 | Thread.Sleep(15 * DelayMultiply); 58 | Console.WriteLine("[Chef] Jam applied"); 59 | } 60 | 61 | private void PourJuice() 62 | { 63 | Console.WriteLine("[Chef] Start pouring Juice"); 64 | Thread.Sleep(5 * DelayMultiply); 65 | Console.WriteLine("[Chef] Juice Ready"); 66 | } 67 | 68 | private void BoilWater() 69 | { 70 | Console.WriteLine("[Chef] Set coffee water to boil"); 71 | Thread.Sleep(200 * DelayMultiply); 72 | Console.WriteLine("[Chef] Coffee Water is boiled"); 73 | } 74 | } -------------------------------------------------------------------------------- /src/Ch04/csharp-async-example/csharp-async-example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | csharp_async_example 7 | enable 8 | enable 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Ch05/ClientServer/Client/Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Ch05/ClientServer/Client/TcpClientExample.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | using System.Text; 3 | 4 | namespace Client; 5 | 6 | class TcpClientExample 7 | { 8 | static void Main(string[] args) 9 | { 10 | try 11 | { 12 | // Create a TcpClient. 13 | // Note, for this client to work you need to have a TcpServer 14 | // connected to the same address as specified by the server, port combination. 15 | int port = 13000; 16 | TcpClient client = new TcpClient("127.0.0.1", port); 17 | 18 | // Translate the passed message into ASCII and store it as a byte array. 19 | string messageToSend = "Hello from the client!"; 20 | byte[] data = Encoding.ASCII.GetBytes(messageToSend); 21 | 22 | // Get a client stream for reading and writing. 23 | NetworkStream stream = client.GetStream(); 24 | 25 | // Send the message to the connected TcpServer. 26 | stream.Write(data, 0, data.Length); 27 | Console.WriteLine($"Sent: {messageToSend}"); 28 | 29 | // Receive the TcpServer.response. 30 | // Buffer to store the response bytes. 31 | data = new byte[256]; 32 | // String to store the response ASCII representation. 33 | string responseData; 34 | 35 | // Read the first batch of the TcpServer response bytes. 36 | int bytes = stream.Read(data, 0, data.Length); 37 | responseData = Encoding.ASCII.GetString(data, 0, bytes); 38 | Console.WriteLine($"Received: {responseData}"); 39 | 40 | // Close everything. 41 | stream.Close(); 42 | client.Close(); 43 | } 44 | catch (ArgumentNullException e) 45 | { 46 | Console.WriteLine($"ArgumentNullException: {e}"); 47 | } 48 | catch (SocketException e) 49 | { 50 | Console.WriteLine($"SocketException: {e}"); 51 | } 52 | 53 | Console.WriteLine("\n Press Enter to continue..."); 54 | Console.ReadLine(); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Ch05/ClientServer/TcpServer/SecureMultithreadedServer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | 6 | namespace Server; 7 | 8 | public class SecureMultithreadedServer(string ipAddress, int port) 9 | { 10 | private TcpListener listener; 11 | private volatile bool isRunning; 12 | private readonly IPAddress ipAddress = IPAddress.Parse(ipAddress); 13 | private readonly ConcurrentDictionary clients = new ConcurrentDictionary(); 14 | private static readonly object _lock = new object(); 15 | 16 | private void Start() 17 | { 18 | listener = new TcpListener(ipAddress, port); 19 | listener.Start(); 20 | isRunning = true; 21 | Console.WriteLine("Server started..."); 22 | 23 | while (isRunning) 24 | { 25 | Console.WriteLine("Waiting for connections..."); 26 | var client = listener.AcceptTcpClient(); 27 | var clientId = client.GetHashCode(); 28 | clients.TryAdd(clientId, client); 29 | 30 | Thread clientThread = new Thread(() => HandleClient(client, clientId)); 31 | clientThread.Start(); 32 | } 33 | } 34 | 35 | private void HandleClient(TcpClient client, int clientId) 36 | { 37 | try 38 | { 39 | using var networkStream = client.GetStream(); 40 | using var reader = new StreamReader(networkStream, Encoding.UTF8); 41 | using var writer = new StreamWriter(networkStream, Encoding.UTF8); 42 | string? message = null; 43 | while (!string.IsNullOrEmpty(message = reader.ReadLine())) 44 | { 45 | Console.WriteLine($"Received from {clientId}: {message}"); 46 | writer.WriteLine($"Echo {clientId}: {message}"); 47 | writer.Flush(); 48 | } 49 | } 50 | catch (Exception ex) 51 | { 52 | Console.WriteLine($"Error with client {clientId}: {ex.Message}"); 53 | } 54 | finally 55 | { 56 | client.Close(); 57 | clients.TryRemove(clientId, out _); 58 | Console.WriteLine($"Client {clientId} disconnected."); 59 | } 60 | } 61 | 62 | public void Stop() 63 | { 64 | isRunning = false; 65 | listener.Stop(); 66 | foreach (var client in clients.Values) 67 | { 68 | client.Close(); 69 | } 70 | Console.WriteLine("Server stopped."); 71 | } 72 | 73 | public static void Main(string[] args) 74 | { 75 | var server = new SecureMultithreadedServer("127.0.0.1", 13000); 76 | server.Start(); 77 | } 78 | } -------------------------------------------------------------------------------- /src/Ch05/ClientServer/TcpServer/TcpServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Ch05/ch05.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "ClientServer\Client\Client.csproj", "{967E3C57-0594-4DBF-812F-F01EB353DAF9}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TcpServer", "ClientServer\TcpServer\TcpServer.csproj", "{8CC4BEA4-8378-44DB-BA0C-F7AEE8C9C314}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {967E3C57-0594-4DBF-812F-F01EB353DAF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {967E3C57-0594-4DBF-812F-F01EB353DAF9}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {967E3C57-0594-4DBF-812F-F01EB353DAF9}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {967E3C57-0594-4DBF-812F-F01EB353DAF9}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {8CC4BEA4-8378-44DB-BA0C-F7AEE8C9C314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {8CC4BEA4-8378-44DB-BA0C-F7AEE8C9C314}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {8CC4BEA4-8378-44DB-BA0C-F7AEE8C9C314}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {8CC4BEA4-8378-44DB-BA0C-F7AEE8C9C314}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Ch06/ch06.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Global 4 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 5 | Debug|Any CPU = Debug|Any CPU 6 | Release|Any CPU = Release|Any CPU 7 | EndGlobalSection 8 | EndGlobal 9 | -------------------------------------------------------------------------------- /src/Ch07/ch07.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Global 4 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 5 | Debug|Any CPU = Debug|Any CPU 6 | Release|Any CPU = Release|Any CPU 7 | EndGlobalSection 8 | EndGlobal 9 | -------------------------------------------------------------------------------- /src/Ch08/ch08.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Global 4 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 5 | Debug|Any CPU = Debug|Any CPU 6 | Release|Any CPU = Release|Any CPU 7 | EndGlobalSection 8 | EndGlobal 9 | -------------------------------------------------------------------------------- /src/Ch09/ch09.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Global 4 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 5 | Debug|Any CPU = Debug|Any CPU 6 | Release|Any CPU = Release|Any CPU 7 | EndGlobalSection 8 | EndGlobal 9 | -------------------------------------------------------------------------------- /src/Ch10/ch10.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Global 4 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 5 | Debug|Any CPU = Debug|Any CPU 6 | Release|Any CPU = Release|Any CPU 7 | EndGlobalSection 8 | EndGlobal 9 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/BlazorWebRTC.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Data/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorWebRTC.Data; 2 | 3 | public class WeatherForecast 4 | { 5 | public DateOnly Date { get; set; } 6 | 7 | public int TemperatureC { get; set; } 8 | 9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 10 | 11 | public string? Summary { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Data/WeatherForecastService.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorWebRTC.Data; 2 | 3 | public class WeatherForecastService 4 | { 5 | private static readonly string[] Summaries = new[] 6 | { 7 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 8 | }; 9 | 10 | public Task GetForecastAsync(DateOnly startDate) 11 | { 12 | return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast 13 | { 14 | Date = startDate.AddDays(index), 15 | TemperatureC = Random.Shared.Next(-20, 55), 16 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 17 | }).ToArray()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 | Counter 4 | 5 |

Counter

6 | 7 |

Current count: @currentCount

8 | 9 | 10 | 11 | @code { 12 | private int currentCount = 0; 13 | 14 | private void IncrementCount() 15 | { 16 | currentCount++; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model BlazorWebRTC.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Error.

19 |

An error occurred while processing your request.

20 | 21 | @if (Model.ShowRequestId) 22 | { 23 |

24 | Request ID: @Model.RequestId 25 |

26 | } 27 | 28 |

Development Mode

29 |

30 | Swapping to the Development environment displays detailed information about the error that occurred. 31 |

32 |

33 | The Development environment shouldn't be enabled for deployed applications. 34 | It can result in displaying sensitive information from exceptions to end users. 35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 36 | and restarting the app. 37 |

38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BlazorWebRTC.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Pages/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | @using BlazorWebRTC.Data 3 | @inject WeatherForecastService ForecastService 4 | 5 | Weather forecast 6 | 7 |

Weather forecast

8 | 9 |

This component demonstrates fetching data from a service.

10 | 11 | @if (forecasts == null) 12 | { 13 |

Loading...

14 | } 15 | else 16 | { 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | @foreach (var forecast in forecasts) 28 | { 29 | 30 | 31 | 32 | 33 | 34 | 35 | } 36 | 37 |
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
38 | } 39 | 40 | @code { 41 | private WeatherForecast[]? forecasts; 42 | 43 | protected override async Task OnInitializedAsync() 44 | { 45 | forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inject IJSRuntime JS 3 | 4 |

WebRTC Video Chat

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | @code { 16 | private async Task InitializeConnection() 17 | { 18 | await JS.InvokeVoidAsync("webRTCInterop.initializeConnection"); 19 | } 20 | 21 | private async Task StartCall() 22 | { 23 | await JS.InvokeVoidAsync("webRTCInterop.startCall"); 24 | } 25 | 26 | public async Task AddTrack() 27 | { 28 | await JS.InvokeVoidAsync("webRTCInterop.addTrack"); 29 | } 30 | 31 | private async Task RemoveTrack() 32 | { 33 | // Assuming you have the trackId to remove 34 | string trackId = "your-track-id"; 35 | await JS.InvokeVoidAsync("webRTCInterop.removeTrack", trackId); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using Microsoft.AspNetCore.Components.Web 3 | @namespace BlazorWebRTC.Pages 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | An error has occurred. This application may no longer respond until reloaded. 25 | 26 | 27 | An unhandled exception has occurred. See browser dev tools for details. 28 | 29 | Reload 30 | 🗙 31 |
32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using BlazorWebRTC.Data; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | // Add services to the container. 8 | builder.Services.AddRazorPages(); 9 | builder.Services.AddServerSideBlazor(); 10 | builder.Services.AddSingleton(); 11 | 12 | var app = builder.Build(); 13 | 14 | // Configure the HTTP request pipeline. 15 | if (!app.Environment.IsDevelopment()) 16 | { 17 | app.UseExceptionHandler("/Error"); 18 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 19 | app.UseHsts(); 20 | } 21 | 22 | app.UseHttpsRedirection(); 23 | 24 | app.UseStaticFiles(); 25 | 26 | app.UseRouting(); 27 | 28 | app.MapBlazorHub(); 29 | app.MapFallbackToPage("/_Host"); 30 | 31 | app.Run(); 32 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:11687", 7 | "sslPort": 44375 8 | } 9 | }, 10 | "profiles": { 11 | "http": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "http://localhost:5082", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "https": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": true, 23 | "launchBrowser": true, 24 | "applicationUrl": "https://localhost:7111;http://localhost:5082", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | }, 29 | "IIS Express": { 30 | "commandName": "IISExpress", 31 | "launchBrowser": true, 32 | "environmentVariables": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | BlazorWebRTC 4 | 5 |
6 | 9 | 10 |
11 |
12 | About 13 |
14 | 15 |
16 | @Body 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | } 28 | 29 | .top-row a:first-child { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | } 33 | 34 | @media (max-width: 640.98px) { 35 | .top-row:not(.auth) { 36 | display: none; 37 | } 38 | 39 | .top-row.auth { 40 | justify-content: space-between; 41 | } 42 | 43 | .top-row a, .top-row .btn-link { 44 | margin-left: 0; 45 | } 46 | } 47 | 48 | @media (min-width: 641px) { 49 | .page { 50 | flex-direction: row; 51 | } 52 | 53 | .sidebar { 54 | width: 250px; 55 | height: 100vh; 56 | position: sticky; 57 | top: 0; 58 | } 59 | 60 | .top-row { 61 | position: sticky; 62 | top: 0; 63 | z-index: 1; 64 | } 65 | 66 | .top-row, article { 67 | padding-left: 2rem !important; 68 | padding-right: 1.5rem !important; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 | 29 | 30 | @code { 31 | private bool collapseNavMenu = true; 32 | 33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 34 | 35 | private void ToggleNavMenu() 36 | { 37 | collapseNavMenu = !collapseNavMenu; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | 63 | .nav-scrollable { 64 | /* Allow sidebar to scroll for tall menus */ 65 | height: calc(100vh - 3.5rem); 66 | overflow-y: auto; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/Shared/SurveyPrompt.razor: -------------------------------------------------------------------------------- 1 | 
2 | 3 | @Title 4 | 5 | 6 | Please take our 7 | brief survey 8 | 9 | and tell us what you think. 10 |
11 | 12 | @code { 13 | // Demonstrates how a parent component can supply parameters 14 | [Parameter] 15 | public string? Title { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using BlazorWebRTC 10 | @using BlazorWebRTC.Shared 11 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/src/Ch11/BlazorWebRTC/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1:focus { 8 | outline: none; 9 | } 10 | 11 | a, .btn-link { 12 | color: #0071c1; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 22 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 23 | } 24 | 25 | .content { 26 | padding-top: 1.1rem; 27 | } 28 | 29 | .valid.modified:not([type=checkbox]) { 30 | outline: 1px solid #26b050; 31 | } 32 | 33 | .invalid { 34 | outline: 1px solid red; 35 | } 36 | 37 | .validation-message { 38 | color: red; 39 | } 40 | 41 | #blazor-error-ui { 42 | background: lightyellow; 43 | bottom: 0; 44 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 45 | display: none; 46 | left: 0; 47 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 48 | position: fixed; 49 | width: 100%; 50 | z-index: 1000; 51 | } 52 | 53 | #blazor-error-ui .dismiss { 54 | cursor: pointer; 55 | position: absolute; 56 | right: 0.75rem; 57 | top: 0.5rem; 58 | } 59 | 60 | .blazor-error-boundary { 61 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 62 | padding: 1rem 1rem 1rem 3.7rem; 63 | color: white; 64 | } 65 | 66 | .blazor-error-boundary::after { 67 | content: "An error has occurred." 68 | } 69 | -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodruff/book-network-programming-csharp/08842f71ed51e2450ab1ea95360b30deab0dda73/src/Ch11/BlazorWebRTC/wwwroot/favicon.png -------------------------------------------------------------------------------- /src/Ch11/BlazorWebRTC/wwwroot/webrtc.js: -------------------------------------------------------------------------------- 1 | // wwwroot/webrtc.js 2 | 3 | let localConnection = null; 4 | let remoteConnection = null; 5 | 6 | window.webRTCInterop = { 7 | initializeConnection: function () { 8 | localConnection = new RTCPeerConnection(); 9 | remoteConnection = new RTCPeerConnection(); 10 | 11 | // Handle ICE candidates 12 | localConnection.onicecandidate = (event) => { 13 | if (event.candidate) { 14 | remoteConnection.addIceCandidate(event.candidate); 15 | } 16 | }; 17 | 18 | remoteConnection.onicecandidate = (event) => { 19 | if (event.candidate) { 20 | localConnection.addIceCandidate(event.candidate); 21 | } 22 | }; 23 | 24 | // Handle remote stream 25 | remoteConnection.ontrack = (event) => { 26 | const remoteVideo = document.getElementById("remoteVideo"); 27 | remoteVideo.srcObject = event.streams[0]; 28 | }; 29 | }, 30 | 31 | startCall: async function () { 32 | const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); 33 | const localVideo = document.getElementById("localVideo"); 34 | localVideo.srcObject = stream; 35 | 36 | stream.getTracks().forEach(track => localConnection.addTrack(track, stream)); 37 | 38 | const offer = await localConnection.createOffer(); 39 | await localConnection.setLocalDescription(offer); 40 | await remoteConnection.setRemoteDescription(offer); 41 | 42 | const answer = await remoteConnection.createAnswer(); 43 | await remoteConnection.setLocalDescription(answer); 44 | await localConnection.setRemoteDescription(answer); 45 | }, 46 | 47 | addTrack: async function() { 48 | // Assuming you already have an RTCPeerConnection object 49 | let peerConnection = new RTCPeerConnection({ 50 | iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] 51 | }); 52 | 53 | // Obtain the media stream from user media (audio/video) 54 | navigator.mediaDevices.getUserMedia({ video: true, audio: true }) 55 | .then(stream => { 56 | // Add each track to the peer connection 57 | stream.getTracks().forEach(track => { 58 | peerConnection.addTrack(track, stream); 59 | }); 60 | console.log('Tracks added to the peer connection.'); 61 | }) 62 | .catch(error => { 63 | console.error('Error accessing media devices.', error); 64 | }); 65 | }, 66 | 67 | removeTrack: async function(trackId) { 68 | // Assuming you already have an RTCPeerConnection object 69 | let peerConnection = new RTCPeerConnection({ 70 | iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] 71 | }); 72 | 73 | // Assumes you have a global RTCPeerConnection instance named 'peer' 74 | const senders = peerConnection.getSenders(); 75 | const sender = senders.find(s => s.track && s.track.id === trackId); 76 | if (sender) { 77 | peer.removeTrack(sender); 78 | console.log(`Track with id ${trackId} removed`); 79 | } else { 80 | console.log(`Track with id ${trackId} not found`); 81 | } 82 | } 83 | }; -------------------------------------------------------------------------------- /src/Ch11/WebRTCSignalingServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | using System.Text; 3 | using System.Text.Json; 4 | 5 | const int BufferSize = 1024 * 4; 6 | 7 | List connectedClients = new List(); 8 | 9 | var builder = WebApplication.CreateBuilder(args); 10 | var app = builder.Build(); 11 | 12 | app.UseWebSockets(); 13 | app.UseRouting(); 14 | 15 | app.UseEndpoints(endpoints => 16 | { 17 | endpoints.MapGet("/signal", async context => 18 | { 19 | await HandleWebSocketRequest(context); 20 | }); 21 | }); 22 | 23 | app.Run(); 24 | 25 | async Task HandleWebSocketRequest(HttpContext context) 26 | { 27 | if (context.WebSockets.IsWebSocketRequest) 28 | { 29 | var webSocket = await context.WebSockets.AcceptWebSocketAsync(); 30 | await HandleWebSocketSignaling(webSocket); 31 | } 32 | else 33 | { 34 | context.Response.StatusCode = 400; 35 | } 36 | } 37 | 38 | async Task HandleWebSocketSignaling(WebSocket webSocket) 39 | { 40 | connectedClients.Add(webSocket); 41 | var buffer = new byte[1024 * 4]; 42 | 43 | try 44 | { 45 | while (webSocket.State == WebSocketState.Open) 46 | { 47 | var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); 48 | var message = Encoding.UTF8.GetString(buffer, 0, result.Count); 49 | 50 | // Parsing JSON message to differentiate message types 51 | var signalingMessage = JsonSerializer.Deserialize(message); 52 | 53 | switch (signalingMessage.Type) 54 | { 55 | case "offer": 56 | case "answer": 57 | case "candidate": 58 | await ForwardSignalingMessage(signalingMessage, webSocket); 59 | break; 60 | default: 61 | Console.WriteLine($"Unknown message type: {signalingMessage.Type}"); 62 | break; 63 | } 64 | } 65 | } 66 | catch (Exception ex) 67 | { 68 | Console.WriteLine($"Error: {ex.Message}"); 69 | } 70 | finally 71 | { 72 | connectedClients.Remove(webSocket); 73 | await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); 74 | } 75 | } 76 | 77 | async Task ForwardSignalingMessage(SignalingMessage signalingMessage, WebSocket sender) 78 | { 79 | foreach (var client in connectedClients) 80 | { 81 | if (client != sender && client.State == WebSocketState.Open) 82 | { 83 | var messageJson = JsonSerializer.Serialize(signalingMessage); 84 | await client.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(messageJson)), WebSocketMessageType.Text, true, CancellationToken.None); 85 | } 86 | } 87 | } 88 | 89 | public class SignalingMessage 90 | { 91 | public string Type { get; set; } 92 | public string Payload { get; set; } 93 | } -------------------------------------------------------------------------------- /src/Ch11/WebRTCSignalingServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:31505", 8 | "sslPort": 44372 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5184", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7165;http://localhost:5184", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Ch11/WebRTCSignalingServer/WebRTCSignalingServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Ch11/WebRTCSignalingServer/WebRTCSignalingServer.http: -------------------------------------------------------------------------------- 1 | @WebRTCSignalingServer_HostAddress = http://localhost:5184 2 | 3 | GET {{WebRTCSignalingServer_HostAddress}}/weatherforecast/ 4 | Accept: application/json 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /src/Ch11/WebRTCSignalingServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Ch11/WebRTCSignalingServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Ch11/ch11.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorWebRTC", "BlazorWebRTC\BlazorWebRTC.csproj", "{77C1CEC3-B33F-4159-85E7-28E18571B776}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebRTCSignalingServer", "WebRTCSignalingServer\WebRTCSignalingServer.csproj", "{A66FEFC4-0812-4D99-A019-2E86EB459E50}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {77C1CEC3-B33F-4159-85E7-28E18571B776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {77C1CEC3-B33F-4159-85E7-28E18571B776}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {77C1CEC3-B33F-4159-85E7-28E18571B776}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {77C1CEC3-B33F-4159-85E7-28E18571B776}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {A66FEFC4-0812-4D99-A019-2E86EB459E50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {A66FEFC4-0812-4D99-A019-2E86EB459E50}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {A66FEFC4-0812-4D99-A019-2E86EB459E50}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {A66FEFC4-0812-4D99-A019-2E86EB459E50}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Ch12/MQTT-Simple/MQTTClient/MQTTClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Ch12/MQTT-Simple/MQTTClient/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using MQTTnet.Protocol; 3 | using MQTTnet; 4 | using MQTTnet.Client; 5 | 6 | namespace MQTTClient; 7 | 8 | class Program 9 | { 10 | static async Task Main(string[] args) 11 | { 12 | Console.WriteLine("Starting MQTT Client..."); 13 | 14 | // Define the MQTT client options 15 | var options = new MqttClientOptionsBuilder() 16 | .WithClientId("Client1") 17 | .WithTcpServer("localhost", 1883) // Connect to the MQTT server 18 | .WithCredentials("testuser", "testpassword") // Use valid credentials 19 | .WithCleanSession() 20 | .Build(); 21 | 22 | // Create the MQTT client 23 | var mqttClient = new MqttFactory().CreateMqttClient(); 24 | 25 | // Handlers 26 | mqttClient.ConnectedAsync += async e => 27 | { 28 | Console.WriteLine("Connected to the broker!"); 29 | 30 | // Subscribe to a topic 31 | await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder() //FIX 32 | .WithTopic("test/topic") 33 | .Build()); 34 | 35 | Console.WriteLine("Subscribed to topic 'test/topic'."); 36 | }; 37 | 38 | mqttClient.DisconnectedAsync += e => 39 | { 40 | Console.WriteLine("Disconnected from the broker."); 41 | return Task.CompletedTask; 42 | }; 43 | 44 | mqttClient.ApplicationMessageReceivedAsync += e => 45 | { 46 | Console.WriteLine($"Message received on topic {e.ApplicationMessage.Topic}: {Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment)}"); 47 | return Task.CompletedTask; 48 | }; 49 | 50 | // Connect to the MQTT server 51 | await mqttClient.ConnectAsync(options); 52 | Console.WriteLine("MQTT Client is running. Press any key to publish a message."); 53 | 54 | Console.ReadKey(); 55 | 56 | // Publish a message 57 | var message = new MqttApplicationMessageBuilder() 58 | .WithTopic("test/topic") 59 | .WithPayload("Hello MQTT!") 60 | .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce) 61 | .Build(); 62 | 63 | await mqttClient.PublishAsync(message); 64 | Console.WriteLine("Message published!"); 65 | 66 | Console.WriteLine("Press any key to disconnect..."); 67 | Console.ReadKey(); 68 | 69 | // Disconnect from the MQTT server 70 | await mqttClient.DisconnectAsync(); 71 | Console.WriteLine("MQTT Client disconnected."); 72 | } 73 | } -------------------------------------------------------------------------------- /src/Ch12/MQTT-Simple/MQTTServer/MQTTServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Ch12/MQTT-Simple/MQTTServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using MQTTnet; 3 | using MQTTnet.Diagnostics; 4 | using MQTTnet.Server; 5 | 6 | namespace MQTTServer; 7 | 8 | class Program 9 | { 10 | static async Task Main(string[] args) 11 | { 12 | Console.WriteLine("Starting MQTT Server..."); 13 | 14 | // Create the MQTT server options 15 | var optionsBuilder = new MqttServerOptionsBuilder() 16 | .WithDefaultEndpoint() 17 | .WithDefaultEndpointPort(1883); 18 | 19 | // Create and start the MQTT server 20 | var mqttServer = new MqttFactory(new ConsoleLogger()).CreateMqttServer(optionsBuilder.Build()); 21 | 22 | await mqttServer.StartAsync(); 23 | Console.WriteLine("MQTT Server is running on port 1883."); 24 | 25 | Console.WriteLine("Press any key to stop the server..."); 26 | Console.ReadKey(); 27 | 28 | await mqttServer.StopAsync(); 29 | Console.WriteLine("MQTT Server stopped."); 30 | } 31 | } 32 | 33 | class ConsoleLogger : IMqttNetLogger 34 | { 35 | readonly object _consoleSyncRoot = new(); 36 | 37 | public bool IsEnabled => true; 38 | 39 | public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception) 40 | { 41 | var foregroundColor = ConsoleColor.White; 42 | switch (logLevel) 43 | { 44 | case MqttNetLogLevel.Verbose: 45 | foregroundColor = ConsoleColor.White; 46 | break; 47 | 48 | case MqttNetLogLevel.Info: 49 | foregroundColor = ConsoleColor.Green; 50 | break; 51 | 52 | case MqttNetLogLevel.Warning: 53 | foregroundColor = ConsoleColor.DarkYellow; 54 | break; 55 | 56 | case MqttNetLogLevel.Error: 57 | foregroundColor = ConsoleColor.Red; 58 | break; 59 | } 60 | 61 | if (parameters?.Length > 0) 62 | { 63 | message = string.Format(message, parameters); 64 | } 65 | 66 | lock (_consoleSyncRoot) 67 | { 68 | Console.ForegroundColor = foregroundColor; 69 | Console.WriteLine(message); 70 | 71 | if (exception != null) 72 | { 73 | Console.WriteLine(exception); 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/Ch12/MQTT-TLS/MqttBroker/MqttBroker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Ch12/MQTT-TLS/MqttBroker/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Authentication; 2 | using System.Security.Cryptography.X509Certificates; 3 | using MQTTnet; 4 | using MQTTnet.Server; 5 | 6 | namespace MqttBroker; 7 | 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | Console.WriteLine("Starting MQTT Server..."); 13 | 14 | // Load server certificate 15 | string certPath = "path_to_server_cert.pfx"; // Provide the path to your .pfx certificate file 16 | string certPassword = "your_cert_password"; // Provide the certificate password 17 | var serverCertificate = new X509Certificate2(certPath, certPassword); 18 | 19 | // Create MQTT broker 20 | var mqttServerOptions = new MqttServerOptionsBuilder() 21 | .WithEncryptedEndpoint() 22 | .WithEncryptedEndpointPort(8883) 23 | .WithEncryptionCertificate(serverCertificate.Export(X509ContentType.Pfx)) 24 | .WithEncryptionSslProtocol(SslProtocols.Tls12) 25 | .Build(); 26 | 27 | var mqttServer = new MqttFactory().CreateMqttServer(mqttServerOptions); 28 | mqttServer.ClientConnectedAsync += (e) => 29 | { 30 | Console.WriteLine($"Client Connected: " + e.ClientId); 31 | return Task.CompletedTask; 32 | }; 33 | mqttServer.ClientDisconnectedAsync += (e) => 34 | { 35 | Console.WriteLine("Client Disconnected: " + e.ClientId); 36 | return Task.CompletedTask; 37 | }; 38 | 39 | // Start the server 40 | mqttServer.StartAsync(); 41 | Console.WriteLine("MQTT Server started on port 8883 with TLS enabled."); 42 | 43 | Console.WriteLine("Press any key to stop the server..."); 44 | Console.ReadKey(); 45 | mqttServer.StopAsync(); 46 | Console.WriteLine("MQTT Server stopped."); 47 | } 48 | } -------------------------------------------------------------------------------- /src/Ch12/MQTT-TLS/MqttClientApp/MqttClientApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Ch12/MQTT-TLS/MqttClientApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | using System.Text; 3 | using uPLibrary.Networking.M2Mqtt; 4 | using uPLibrary.Networking.M2Mqtt.Messages; 5 | 6 | namespace MqttClientApp; 7 | 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | Console.WriteLine("Starting MQTT Client..."); 13 | 14 | // Load client certificate 15 | string certPath = "path_to_client_cert.pfx"; // Provide the path to your .pfx certificate file 16 | string certPassword = "your_cert_password"; // Provide the certificate password 17 | var clientCertificate = new X509Certificate2(certPath, certPassword); 18 | 19 | // Create MQTT client 20 | string brokerAddress = "your_broker_address"; // Replace with your MQTT server's address 21 | int brokerPort = 8883; 22 | 23 | var mqttClient = new MqttClient( 24 | brokerAddress, 25 | brokerPort, 26 | true, 27 | MqttSslProtocols.TLSv1_2, 28 | (sender, certificate, chain, sslPolicyErrors) => true, 29 | (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => clientCertificate); 30 | 31 | mqttClient.MqttMsgPublishReceived += (sender, e) => 32 | { 33 | Console.WriteLine($"Message Received: {Encoding.UTF8.GetString(e.Message)} on Topic {e.Topic}"); 34 | }; 35 | 36 | mqttClient.ConnectionClosed += (sender, e) => 37 | { 38 | Console.WriteLine("Disconnected from the broker."); 39 | }; 40 | 41 | string clientId = Guid.NewGuid().ToString(); 42 | mqttClient.Connect(clientId); 43 | Console.WriteLine("Connected to MQTT Broker!"); 44 | 45 | // Subscribe to a topic 46 | string topic = "test/topic"; 47 | mqttClient.Subscribe([topic], [MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE]); 48 | Console.WriteLine($"Subscribed to topic: {topic}"); 49 | 50 | // Publish a message 51 | string message = "Hello MQTT with TLS!"; 52 | mqttClient.Publish(topic, Encoding.UTF8.GetBytes(message)); 53 | Console.WriteLine($"Published message: {message}"); 54 | 55 | Console.WriteLine("Press any key to disconnect..."); 56 | Console.ReadKey(); 57 | 58 | mqttClient.Disconnect(); 59 | Console.WriteLine("MQTT Client disconnected."); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Ch12/ch12.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MqttBroker", "MQTT-TLS\MqttBroker\MqttBroker.csproj", "{A830863E-E051-4564-A5D2-02C0D5FD5B5F}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MqttClientApp", "MQTT-TLS\MqttClientApp\MqttClientApp.csproj", "{2F1E7260-BE37-433F-A216-186972357F8F}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTServer", "MQTT-Simple\MQTTServer\MQTTServer.csproj", "{10E0EAE5-FF86-4198-B965-77930F3F336E}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTClient", "MQTT-Simple\MQTTClient\MQTTClient.csproj", "{58AE9E23-331C-418F-95CA-F058BD972817}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {A830863E-E051-4564-A5D2-02C0D5FD5B5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {A830863E-E051-4564-A5D2-02C0D5FD5B5F}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {A830863E-E051-4564-A5D2-02C0D5FD5B5F}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {A830863E-E051-4564-A5D2-02C0D5FD5B5F}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {2F1E7260-BE37-433F-A216-186972357F8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {2F1E7260-BE37-433F-A216-186972357F8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {2F1E7260-BE37-433F-A216-186972357F8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {2F1E7260-BE37-433F-A216-186972357F8F}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {10E0EAE5-FF86-4198-B965-77930F3F336E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {10E0EAE5-FF86-4198-B965-77930F3F336E}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {10E0EAE5-FF86-4198-B965-77930F3F336E}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {10E0EAE5-FF86-4198-B965-77930F3F336E}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {58AE9E23-331C-418F-95CA-F058BD972817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {58AE9E23-331C-418F-95CA-F058BD972817}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {58AE9E23-331C-418F-95CA-F058BD972817}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {58AE9E23-331C-418F-95CA-F058BD972817}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/Ch13/GrpcClient/GrpcClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Ch13/GrpcClient/Program.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | using Grpc.Net.Client; 3 | using GrpcServer; 4 | 5 | class Program 6 | { 7 | static async Task Main(string[] args) 8 | { 9 | // Create a channel to connect to the gRPC server 10 | using var channel = GrpcChannel.ForAddress("https://localhost:7204"); 11 | 12 | // Create a client for the TodoService 13 | var client = new TodoService.TodoServiceClient(channel); 14 | 15 | Console.WriteLine("gRPC Client started. Choose an option:"); 16 | while (true) 17 | { 18 | Console.WriteLine("1. Add Todo"); 19 | Console.WriteLine("2. Get Todos"); 20 | Console.WriteLine("3. Exit"); 21 | Console.Write("Enter your choice: "); 22 | var choice = Console.ReadLine(); 23 | 24 | switch (choice) 25 | { 26 | case "1": 27 | await AddTodoAsync(client); 28 | break; 29 | case "2": 30 | await GetTodosAsync(client); 31 | break; 32 | case "3": 33 | Console.WriteLine("Exiting..."); 34 | return; 35 | default: 36 | Console.WriteLine("Invalid choice. Please try again."); 37 | break; 38 | } 39 | } 40 | } 41 | 42 | private static async Task AddTodoAsync(TodoService.TodoServiceClient client) 43 | { 44 | Console.Write("Enter title: "); 45 | var title = Console.ReadLine(); 46 | 47 | Console.Write("Enter description: "); 48 | var description = Console.ReadLine(); 49 | 50 | var request = new TodoRequest { Title = title, Description = description }; 51 | 52 | try 53 | { 54 | var response = await client.AddTodoAsync(request); 55 | Console.WriteLine($"Server Response: {response.Status}"); 56 | } 57 | catch (Exception ex) 58 | { 59 | Console.WriteLine($"Error: {ex.Message}"); 60 | } 61 | } 62 | 63 | private static async Task GetTodosAsync(TodoService.TodoServiceClient client) 64 | { 65 | try 66 | { 67 | using var call = client.GetTodos(new Empty()); 68 | Console.WriteLine("Fetching Todos from the server:"); 69 | 70 | await foreach (var todo in call.ResponseStream.ReadAllAsync()) 71 | { 72 | Console.WriteLine($"- {todo.Title}: {todo.Description}"); 73 | } 74 | } 75 | catch (Exception ex) 76 | { 77 | Console.WriteLine($"Error: {ex.Message}"); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Ch13/GrpcClient/Protos/todo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "GrpcServer"; 4 | 5 | service TodoService { 6 | rpc AddTodo (TodoRequest) returns (TodoReply); 7 | rpc GetTodos (Empty) returns (stream TodoItem); 8 | } 9 | 10 | message TodoRequest { 11 | string title = 1; 12 | string description = 2; 13 | } 14 | 15 | message TodoReply { 16 | string status = 1; 17 | } 18 | 19 | message TodoItem { 20 | string title = 1; 21 | string description = 2; 22 | } 23 | 24 | message Empty {} 25 | -------------------------------------------------------------------------------- /src/Ch13/GrpcServer/GrpcServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Ch13/GrpcServer/Program.cs: -------------------------------------------------------------------------------- 1 | using GrpcServer.Services; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | // Add services to the container. 6 | builder.Services.AddGrpc(); 7 | builder.Services.AddGrpcReflection(); 8 | 9 | var app = builder.Build(); 10 | 11 | // Configure the HTTP request pipeline. 12 | app.MapGrpcService(); 13 | 14 | IWebHostEnvironment env = app.Environment; 15 | 16 | if (env.IsDevelopment()) 17 | { 18 | app.MapGrpcReflectionService(); 19 | } 20 | 21 | app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); 22 | 23 | app.Run(); 24 | -------------------------------------------------------------------------------- /src/Ch13/GrpcServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": false, 8 | "applicationUrl": "http://localhost:5121", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": true, 16 | "launchBrowser": false, 17 | "applicationUrl": "https://localhost:7204;http://localhost:5121", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ch13/GrpcServer/Protos/todo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "GrpcServer"; 4 | 5 | service TodoService { 6 | rpc AddTodo (TodoRequest) returns (TodoReply); 7 | rpc GetTodos (Empty) returns (stream TodoItem); 8 | } 9 | 10 | message TodoRequest { 11 | string title = 1; 12 | string description = 2; 13 | } 14 | 15 | message TodoReply { 16 | string status = 1; 17 | } 18 | 19 | message TodoItem { 20 | string title = 1; 21 | string description = 2; 22 | } 23 | 24 | message Empty {} 25 | -------------------------------------------------------------------------------- /src/Ch13/GrpcServer/Services/TodoService.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | 3 | namespace GrpcServer.Services; 4 | 5 | public class TodoService : GrpcServer.TodoService.TodoServiceBase 6 | { 7 | private readonly List _todoList = new(); 8 | 9 | public override Task AddTodo(TodoRequest request, ServerCallContext context) 10 | { 11 | var todoItem = new TodoItem { Title = request.Title, Description = request.Description }; 12 | _todoList.Add(todoItem); 13 | 14 | return Task.FromResult(new TodoReply 15 | { 16 | Status = $"Task '{request.Title}' added successfully!" 17 | }); 18 | } 19 | 20 | public override async Task GetTodos(Empty request, IServerStreamWriter responseStream, ServerCallContext context) 21 | { 22 | foreach (var todo in _todoList) 23 | { 24 | await responseStream.WriteAsync(todo); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Ch13/GrpcServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Ch13/GrpcServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "Kestrel": { 10 | "EndpointDefaults": { 11 | "Protocols": "Http2" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Ch13/ch13.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcServer", "GrpcServer\GrpcServer.csproj", "{E426CCF7-A75D-47D9-8FB6-7A1D84D1F95F}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcClient", "GrpcClient\GrpcClient.csproj", "{5FD5584D-BED4-4997-B5DF-9009E62512FE}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {E426CCF7-A75D-47D9-8FB6-7A1D84D1F95F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {E426CCF7-A75D-47D9-8FB6-7A1D84D1F95F}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {E426CCF7-A75D-47D9-8FB6-7A1D84D1F95F}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {E426CCF7-A75D-47D9-8FB6-7A1D84D1F95F}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {5FD5584D-BED4-4997-B5DF-9009E62512FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {5FD5584D-BED4-4997-B5DF-9009E62512FE}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {5FD5584D-BED4-4997-B5DF-9009E62512FE}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {5FD5584D-BED4-4997-B5DF-9009E62512FE}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/Controllers/WebHookController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | namespace WebHookReceiver.Controllers; 5 | 6 | [ApiController] 7 | [Route("api/[controller]")] 8 | public class WebHookController : ControllerBase 9 | { 10 | 11 | private readonly ILogger _logger; 12 | 13 | public WebHookController(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | [HttpPost] 19 | public IActionResult HandleWebHook([FromBody] WebHookPayload payload) 20 | { 21 | _logger.LogInformation("Received WebHook request. Headers: {Headers}, Payload: {Payload}", 22 | Request.Headers, payload); 23 | 24 | if (payload == null) 25 | { 26 | _logger.LogError("Payload is null"); 27 | return BadRequest("Invalid payload"); 28 | } 29 | 30 | // Process the payload based on the event type 31 | switch (payload.EventType) 32 | { 33 | case "payment_success": 34 | ProcessPaymentSuccess(payload); 35 | break; 36 | case "subscription_updated": 37 | ProcessSubscriptionUpdate(payload); 38 | break; 39 | default: 40 | return BadRequest("Unknown event type"); 41 | } 42 | 43 | _logger.LogInformation("Processing event: {EventType}", payload.EventType); 44 | return Ok(); 45 | } 46 | 47 | private void ProcessPaymentSuccess(WebHookPayload payload) 48 | { 49 | // Custom logic for handling payment success 50 | Console.WriteLine($"Payment received: {payload.Data}"); 51 | } 52 | 53 | private void ProcessSubscriptionUpdate(WebHookPayload payload) 54 | { 55 | // Custom logic for handling subscription updates 56 | Console.WriteLine($"Subscription updated: {payload.Data}"); 57 | } 58 | 59 | private async Task SaveEventToDatabase(WebHookPayload payload) 60 | { 61 | using var context = new AppDbContext(); 62 | context.WebHookPayloads.Add(new WebHookPayload 63 | { 64 | EventType = payload.EventType, 65 | Data = payload.Data, 66 | ReceivedAt = DateTime.UtcNow 67 | }); 68 | await context.SaveChangesAsync(); 69 | } 70 | } 71 | 72 | public class AppDbContext : DbContext 73 | { 74 | public DbSet WebHookPayloads { get; set; } 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | builder.Services.AddControllers(); 3 | builder.Services.AddEndpointsApiExplorer(); 4 | builder.Services.AddSwaggerGen(); 5 | 6 | var app = builder.Build(); 7 | app.UseSwagger(); 8 | app.UseSwaggerUI(); 9 | app.UseHttpsRedirection(); 10 | 11 | app.Use(async (context, next) => 12 | { 13 | var allowedIPs = new[] { "192.168.1.1", "203.0.113.10" }; 14 | var remoteIp = context.Connection.RemoteIpAddress?.ToString(); 15 | 16 | if (!allowedIPs.Contains(remoteIp)) 17 | { 18 | context.Response.StatusCode = StatusCodes.Status403Forbidden; 19 | return; 20 | } 21 | 22 | await next(); 23 | }); 24 | 25 | app.MapControllers(); 26 | app.Run(); -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:25149", 8 | "sslPort": 44347 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5032", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7135;http://localhost:5032", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/SecurityHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace WebHookReceiver; 5 | 6 | public static class SecurityHelpers 7 | { 8 | public static bool ValidateSignature(string payload, string signature, string secret) 9 | { 10 | using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); 11 | var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)); 12 | var computedSignature = Convert.ToBase64String(hash); 13 | return signature == computedSignature; 14 | } 15 | 16 | private static bool IsValidRequest(HttpRequest request) 17 | { 18 | if (!request.Headers.TryGetValue("X-Signature", out var signature)) 19 | { 20 | return false; 21 | } 22 | 23 | // Compute the expected signature using a secret key 24 | var secret = "your_secret_key"; 25 | var payload = new StreamReader(request.Body).ReadToEnd(); 26 | var expectedSignature = ComputeHmacSha256(payload, secret); 27 | 28 | return signature == expectedSignature; 29 | } 30 | 31 | private static string ComputeHmacSha256(string payload, string secret) 32 | { 33 | using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); 34 | var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)); 35 | return Convert.ToBase64String(hash); 36 | } 37 | 38 | private static bool IsRecentRequest(string timestampHeader) 39 | { 40 | if (DateTime.TryParse(timestampHeader, out var timestamp)) 41 | { 42 | return (DateTime.UtcNow - timestamp).TotalMinutes <= 5; 43 | } 44 | 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/WebHookPayload.cs: -------------------------------------------------------------------------------- 1 | namespace WebHookReceiver; 2 | 3 | public class WebHookPayload 4 | { 5 | public string? EventType { get; set; } 6 | public object? Data { get; set; } 7 | public DateTime ReceivedAt { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/WebHookReceiver.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/WebHookReceiver.http: -------------------------------------------------------------------------------- 1 | @WebHookReceiver_HostAddress = http://localhost:5032 2 | 3 | GET {{WebHookReceiver_HostAddress}}/weatherforecast/ 4 | Accept: application/json 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Ch14/WebHookReceiver/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Ch14/WebHookSender/OrderService.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace WebHookSender; 4 | 5 | public class OrderService 6 | { 7 | public event EventHandler OrderPlaced; 8 | 9 | public void PlaceOrder(Order order) 10 | { 11 | // Business logic for placing an order 12 | OrderPlaced?.Invoke(this, new OrderEventArgs(order)); 13 | } 14 | } 15 | 16 | public class OrderEventArgs : EventArgs 17 | { 18 | public Order Order { get; } 19 | 20 | public OrderEventArgs(Order order) 21 | { 22 | Order = order; 23 | } 24 | } 25 | 26 | public class Order 27 | { 28 | public string Id { get; } 29 | public object CustomerName { get; set; } 30 | public object Total { get; set; } 31 | } 32 | 33 | public class PayloadService 34 | { 35 | public string CreatePayload(Order order) 36 | { 37 | var payload = new 38 | { 39 | eventType = "order_placed", 40 | data = new 41 | { 42 | orderId = order.Id, 43 | customer = order.CustomerName, 44 | total = order.Total 45 | } 46 | }; 47 | 48 | return JsonSerializer.Serialize(payload); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/Ch14/WebHookSender/Program.cs: -------------------------------------------------------------------------------- 1 | namespace WebHookSender; 2 | 3 | class Program 4 | { 5 | static async Task Main(string[] args) 6 | { 7 | var sender = new WebHookSender("https://localhost:5001/api/webhooks", "your_secret_key"); 8 | await sender.SendDataAsync("event_type", new { message = "Hello, WebHook!" }); 9 | 10 | SenderPayload payload = new SenderPayload() {EventType = "event_type", Data = "Hello, WebHook!"}; 11 | await sender.SendPayloadAsync(payload); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Ch14/WebHookSender/SecurityHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace WebHookSender; 5 | 6 | public static class SecurityHelpers 7 | { 8 | public static string GenerateSignature(string payload, string secret) 9 | { 10 | using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); 11 | var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)); 12 | return Convert.ToBase64String(hash); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Ch14/WebHookSender/SenderPayload.cs: -------------------------------------------------------------------------------- 1 | namespace WebHookSender; 2 | 3 | public class SenderPayload 4 | { 5 | public string? EventType { get; set; } 6 | public object? Data { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Ch14/WebHookSender/WebHookSender.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json; 3 | 4 | namespace WebHookSender; 5 | 6 | public class WebHookSender 7 | { 8 | private readonly string _endpointUrl; 9 | private readonly string _secret; 10 | 11 | private readonly HttpClient _httpClient; 12 | 13 | public WebHookSender(HttpClient httpClient) 14 | { 15 | _httpClient = httpClient; 16 | } 17 | 18 | public WebHookSender(string endpointUrl, string secret) 19 | { 20 | _httpClient = new HttpClient(); 21 | _endpointUrl = endpointUrl; 22 | _secret = secret; 23 | } 24 | 25 | public async Task SendDataAsync(string eventType, object data) 26 | { 27 | var payload = new SenderPayload { EventType = eventType, Data = data }; 28 | var payloadJson = JsonSerializer.Serialize(payload); 29 | 30 | var signature = SecurityHelpers.GenerateSignature(payloadJson, _secret); 31 | 32 | var request = new HttpRequestMessage(HttpMethod.Post, _endpointUrl) 33 | { 34 | Content = new StringContent(payloadJson, Encoding.UTF8, "application/json") 35 | }; 36 | request.Headers.Add("X-Signature", signature); 37 | 38 | var response = await _httpClient.SendAsync(request); 39 | if (response.IsSuccessStatusCode) 40 | { 41 | Console.WriteLine("WebHook sent successfully!"); 42 | } 43 | else 44 | { 45 | Console.WriteLine($"Failed to send WebHook: {response.StatusCode}"); 46 | } 47 | } 48 | 49 | public async Task SendPayloadAsync(SenderPayload payload) 50 | { 51 | var payloadJson = JsonSerializer.Serialize(payload); 52 | 53 | var signature = SecurityHelpers.GenerateSignature(payloadJson, _secret); 54 | 55 | var request = new HttpRequestMessage(HttpMethod.Post, _endpointUrl) 56 | { 57 | Content = new StringContent(payloadJson, Encoding.UTF8, "application/json") 58 | }; 59 | request.Headers.Add("X-Signature", signature); 60 | 61 | var response = await _httpClient.SendAsync(request); 62 | if (!response.IsSuccessStatusCode) 63 | { 64 | Console.WriteLine($"Failed to send WebHook: {response.StatusCode}"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Ch14/WebHookSender/WebHookSender.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Ch14/ch14.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebHookSender", "WebHookSender\WebHookSender.csproj", "{79167FEF-828D-415B-B5D3-A19614BCD405}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebHookReceiver", "WebHookReceiver\WebHookReceiver.csproj", "{E901A5FD-519E-4158-ADC5-AA0D6A7B4098}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {79167FEF-828D-415B-B5D3-A19614BCD405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {79167FEF-828D-415B-B5D3-A19614BCD405}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {79167FEF-828D-415B-B5D3-A19614BCD405}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {79167FEF-828D-415B-B5D3-A19614BCD405}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {E901A5FD-519E-4158-ADC5-AA0D6A7B4098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {E901A5FD-519E-4158-ADC5-AA0D6A7B4098}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {E901A5FD-519E-4158-ADC5-AA0D6A7B4098}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {E901A5FD-519E-4158-ADC5-AA0D6A7B4098}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Ch14/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /src/Ch15/MessageConsumerApp/MessageConsumer.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client; 2 | using RabbitMQ.Client.Events; 3 | using System.Text; 4 | 5 | namespace MessageConsumerApp; 6 | 7 | public class MessageConsumer : IDisposable 8 | { 9 | private readonly IConnection _connection; 10 | private readonly IChannel _channel; 11 | private readonly string _queueName; 12 | 13 | public MessageConsumer(string hostName, string queueName) 14 | { 15 | _queueName = queueName; 16 | var factory = new ConnectionFactory { HostName = hostName }; 17 | _connection = factory.CreateConnectionAsync().GetAwaiter().GetResult(); 18 | _channel = _connection.CreateChannelAsync().GetAwaiter().GetResult(); 19 | _channel.QueueDeclareAsync(queue: _queueName, durable: true, exclusive: false, autoDelete: false, arguments: null); 20 | } 21 | 22 | public void StartListening() 23 | { 24 | var consumer = new AsyncEventingBasicConsumer(_channel); 25 | consumer.ReceivedAsync += async (model, ea) => 26 | { 27 | var body = ea.Body.ToArray(); 28 | var message = Encoding.UTF8.GetString(body); 29 | Console.WriteLine($"[x] Received: {message}"); 30 | // Simulate processing 31 | await _channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false); 32 | }; 33 | 34 | _channel.BasicConsumeAsync(queue: _queueName, autoAck: false, consumer: consumer); 35 | Console.WriteLine("Consumer listening for messages..."); 36 | } 37 | 38 | public void Dispose() 39 | { 40 | _channel.CloseAsync(); 41 | _connection.CloseAsync(); 42 | } 43 | } -------------------------------------------------------------------------------- /src/Ch15/MessageConsumerApp/MessageConsumerApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Ch15/MessageConsumerApp/Program.cs: -------------------------------------------------------------------------------- 1 | using MessageConsumerApp; 2 | 3 | var consumer = new MessageConsumer("localhost", "demo-queue"); 4 | consumer.StartListening(); 5 | 6 | Console.WriteLine("Press [Enter] to exit."); 7 | Console.ReadLine(); 8 | consumer.Dispose(); 9 | -------------------------------------------------------------------------------- /src/Ch15/MessageConsumerTests/MessageConsumerTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Ch15/MessageConsumerTests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using MessageConsumerApp; 2 | using Moq; 3 | using RabbitMQ.Client; 4 | using Xunit; 5 | 6 | 7 | namespace MessageConsumerTests; 8 | 9 | public class MessageConsumerTests 10 | { 11 | [Fact] 12 | public void StartListening_ShouldProcessMessage() 13 | { 14 | var mockChannel = new Mock(); 15 | var consumer = new MessageConsumer("localhost", "test-queue"); 16 | 17 | Assert.NotNull(consumer); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/Ch15/MessageProducerApp/MessageProducer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using RabbitMQ.Client; 5 | 6 | namespace MessageProducerApp; 7 | 8 | public class MessageProducer : IDisposable, IAsyncDisposable 9 | { 10 | private IConnection _connection; 11 | private IChannel _channel; 12 | private readonly string _queueName; 13 | 14 | public MessageProducer(string hostName, string queueName) 15 | { 16 | _queueName = queueName; 17 | var factory = new ConnectionFactory { HostName = hostName }; 18 | InitializeAsync(factory).GetAwaiter().GetResult(); 19 | } 20 | 21 | private async Task InitializeAsync(ConnectionFactory factory) 22 | { 23 | _connection = await factory.CreateConnectionAsync(); 24 | _channel = await _connection.CreateChannelAsync(); 25 | await _channel.QueueDeclareAsync(queue: _queueName, durable: true, exclusive: false, autoDelete: false, arguments: null); 26 | } 27 | 28 | public async Task SendMessageAsync(string message) 29 | { 30 | try 31 | { 32 | var body = Encoding.UTF8.GetBytes(message); 33 | var props = new BasicProperties(); 34 | props.ContentType = "text/plain"; 35 | props.DeliveryMode = DeliveryModes.Persistent; 36 | await _channel.BasicPublishAsync(exchange: "", routingKey: _queueName, mandatory: false, basicProperties: props, body: body); 37 | Console.WriteLine($"[x] Sent: {message}"); 38 | } 39 | catch (Exception ex) 40 | { 41 | Console.WriteLine($"[!] Error sending message: {ex.Message}"); 42 | // Add additional error logging or handling here 43 | } 44 | } 45 | 46 | public void Dispose() 47 | { 48 | _channel.CloseAsync(); 49 | _connection?.Dispose(); 50 | } 51 | 52 | public async ValueTask DisposeAsync() 53 | { 54 | await _channel.CloseAsync(); 55 | await _connection.DisposeAsync(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/Ch15/MessageProducerApp/MessageProducerApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Ch15/MessageProducerApp/Program.cs: -------------------------------------------------------------------------------- 1 | using MessageProducerApp; 2 | 3 | var producer = new MessageProducer("localhost", "demo-queue"); 4 | 5 | Console.WriteLine("Enter messages to send. Type 'exit' to quit."); 6 | string message; 7 | while ((message = Console.ReadLine())?.ToLower() != "exit") 8 | { 9 | await producer.SendMessageAsync(message); 10 | } 11 | 12 | await producer.DisposeAsync(); 13 | Console.WriteLine("Producer application terminated."); -------------------------------------------------------------------------------- /src/Ch15/MessageQueueTests/MessageQueueTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Ch15/MessageQueueTests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using MessageProducerApp; 2 | using Moq; 3 | using RabbitMQ.Client; 4 | 5 | namespace MessageQueueTests; 6 | 7 | public class MessageProducerTests 8 | { 9 | [Fact] 10 | public void SendMessage_ShouldPublishMessage() 11 | { 12 | var mockChannel = new Mock(); 13 | var producer = new MessageProducer("localhost", "test-queue"); 14 | 15 | producer.SendMessageAsync("Test Message"); 16 | 17 | var props = new BasicProperties(); 18 | props.ContentType = "text/plain"; 19 | props.DeliveryMode = DeliveryModes.Transient; 20 | mockChannel.Verify(c => c.BasicPublishAsync( 21 | It.IsAny(), 22 | "test-queue", 23 | false, 24 | props, 25 | It.IsAny(), new CancellationToken()), Times.Once); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Ch15/ch15.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageProducerApp", "MessageProducerApp\MessageProducerApp.csproj", "{704BD08F-C8E4-436C-B063-19EE179E001B}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageConsumerApp", "MessageConsumerApp\MessageConsumerApp.csproj", "{787B36AC-E586-42EC-9981-7E45B11D2116}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageQueueTests", "MessageQueueTests\MessageQueueTests.csproj", "{CFFD17F7-E6E2-47FB-BCAC-5F8716D4D9C9}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageConsumerTests", "MessageConsumerTests\MessageConsumerTests.csproj", "{0EBCE050-2687-4890-A7D1-A01DCC635599}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {704BD08F-C8E4-436C-B063-19EE179E001B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {704BD08F-C8E4-436C-B063-19EE179E001B}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {704BD08F-C8E4-436C-B063-19EE179E001B}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {704BD08F-C8E4-436C-B063-19EE179E001B}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {787B36AC-E586-42EC-9981-7E45B11D2116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {787B36AC-E586-42EC-9981-7E45B11D2116}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {787B36AC-E586-42EC-9981-7E45B11D2116}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {787B36AC-E586-42EC-9981-7E45B11D2116}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {CFFD17F7-E6E2-47FB-BCAC-5F8716D4D9C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {CFFD17F7-E6E2-47FB-BCAC-5F8716D4D9C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {CFFD17F7-E6E2-47FB-BCAC-5F8716D4D9C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {CFFD17F7-E6E2-47FB-BCAC-5F8716D4D9C9}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {0EBCE050-2687-4890-A7D1-A01DCC635599}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {0EBCE050-2687-4890-A7D1-A01DCC635599}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {0EBCE050-2687-4890-A7D1-A01DCC635599}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {0EBCE050-2687-4890-A7D1-A01DCC635599}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/Ch16/SignalRClient/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR.Client; 2 | 3 | var connection = new HubConnectionBuilder() 4 | .WithUrl("https://localhost:5001/chathub") 5 | .WithAutomaticReconnect([TimeSpan.Zero, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10)]) 6 | .Build(); 7 | 8 | try 9 | { 10 | await connection.StartAsync(); 11 | Console.WriteLine("Connection started successfully."); 12 | } 13 | catch (Exception ex) 14 | { 15 | Console.WriteLine($"Connection failed: {ex.Message}"); 16 | } 17 | 18 | connection.On("ReceiveMessage", (user, message) => 19 | { 20 | Console.WriteLine($"{user}: {message}"); 21 | }); 22 | 23 | await connection.InvokeAsync("SendMessage", "User1", "Hello, SignalR!"); 24 | 25 | connection.Closed += async (error) => 26 | { 27 | Console.WriteLine($"Connection closed. Error: {error?.Message}"); 28 | await Task.Delay(5000); // Wait before attempting to reconnect 29 | try 30 | { 31 | await connection.StartAsync(); 32 | Console.WriteLine("Reconnection successful."); 33 | } 34 | catch (Exception ex) 35 | { 36 | Console.WriteLine($"Reconnection failed: {ex.Message}"); 37 | } 38 | }; -------------------------------------------------------------------------------- /src/Ch16/SignalRClient/SignalRClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | latest 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Ch16/SignalRServerExample/ChatHub.cs: -------------------------------------------------------------------------------- 1 | namespace SignalRServerExample; 2 | 3 | using Microsoft.AspNetCore.SignalR; 4 | 5 | public class ChatHub : Hub 6 | { 7 | public async Task SendMessage(string user, string message) 8 | { 9 | await Clients.All.SendAsync("ReceiveMessage", user, message); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Ch16/SignalRServerExample/Program.cs: -------------------------------------------------------------------------------- 1 | using SignalRServerExample; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | builder.Services.AddSignalR(); 6 | 7 | var app = builder.Build(); 8 | 9 | app.UseHttpsRedirection(); 10 | 11 | app.UseDefaultFiles(); 12 | 13 | app.UseStaticFiles(); 14 | 15 | app.UseRouting(); 16 | 17 | app.Use(async (context, next) => 18 | { 19 | Console.WriteLine($"Incoming connection: {context.Request.Path}"); 20 | await next(); 21 | }); 22 | 23 | app.UseEndpoints(endpoints => 24 | { 25 | endpoints.MapHub("/chathub"); 26 | endpoints.MapFallback(context => 27 | { 28 | context.Response.StatusCode = 404; 29 | return context.Response.WriteAsync("Endpoint not found."); 30 | }); 31 | }); 32 | 33 | app.Run(); -------------------------------------------------------------------------------- /src/Ch16/SignalRServerExample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "http://localhost:5000", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": true, 16 | "launchBrowser": true, 17 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ch16/SignalRServerExample/SignalRServerExample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Ch16/SignalRServerExample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Ch16/SignalRServerExample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Ch16/SignalRServerExample/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SignalR Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 | 14 | 34 | 35 | -------------------------------------------------------------------------------- /src/Ch16/ch16.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalRServerExample", "SignalRServerExample\SignalRServerExample.csproj", "{D130D4A9-88F6-47D8-A409-4F64F8AD4691}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalRClient", "SignalRClient\SignalRClient.csproj", "{B12A3362-5795-4FEF-9FBC-102253CD6BB4}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {D130D4A9-88F6-47D8-A409-4F64F8AD4691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {D130D4A9-88F6-47D8-A409-4F64F8AD4691}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {D130D4A9-88F6-47D8-A409-4F64F8AD4691}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {D130D4A9-88F6-47D8-A409-4F64F8AD4691}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {B12A3362-5795-4FEF-9FBC-102253CD6BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {B12A3362-5795-4FEF-9FBC-102253CD6BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {B12A3362-5795-4FEF-9FBC-102253CD6BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {B12A3362-5795-4FEF-9FBC-102253CD6BB4}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Ch17/HTTP3-Perf-Client-Demo/HTTP3-Perf-Client-Demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | HTTP3_Perf_Demo 7 | latest 8 | enable 9 | enable 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Ch17/HTTP3-Perf-Client-Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Net; 3 | 4 | async Task MeasurePerformance(Version httpVersion, int requestCount) 5 | { 6 | using var client = new HttpClient() 7 | { 8 | DefaultRequestVersion = httpVersion, 9 | DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact 10 | }; 11 | 12 | var tasks = new Task[requestCount]; 13 | var stopwatch = Stopwatch.StartNew(); 14 | 15 | for (int i = 0; i < requestCount; i++) 16 | { 17 | tasks[i] = client.GetAsync("https://localhost:5001/process"); 18 | } 19 | 20 | await Task.WhenAll(tasks); 21 | stopwatch.Stop(); 22 | 23 | var totalDuration = stopwatch.ElapsedMilliseconds; 24 | Console.WriteLine($"{httpVersion}: Processed {requestCount} requests in {totalDuration}ms"); 25 | } 26 | 27 | // Measure HTTP/2 performance 28 | await MeasurePerformance(HttpVersion.Version20, 50); 29 | 30 | // Measure HTTP/3 performance 31 | await MeasurePerformance(HttpVersion.Version30, 50); -------------------------------------------------------------------------------- /src/Ch17/HTTP3-Perf-Server-Demo/HTTP3-Perf-Server-Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | HTTP3_Perf_Server_Demo 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Ch17/HTTP3-Perf-Server-Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | using Microsoft.AspNetCore.Server.Kestrel.Core; 3 | using Microsoft.AspNetCore.Server.Kestrel.Https; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false); 8 | 9 | // Configure Kestrel to support HTTP/3, HTTP/2, and HTTP/1.1 10 | builder.WebHost.ConfigureKestrel(options => 11 | { 12 | options.ListenAnyIP(5001, listenOptions => 13 | { 14 | listenOptions.UseConnectionLogging(); 15 | listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; 16 | listenOptions.UseHttps(httpsOptions => 17 | { 18 | httpsOptions.ServerCertificateSelector = (context, host) => cert; 19 | }); 20 | }); 21 | }); 22 | 23 | var app = builder.Build(); 24 | 25 | // Define a simple endpoint to respond with HTTP version and message 26 | app.MapGet("/process", async context => 27 | { 28 | var protocol = context.Request.Protocol; // Retrieve HTTP protocol version 29 | await context.Response.WriteAsync($"Hello! You are using {protocol}"); 30 | }); 31 | 32 | app.Run(); -------------------------------------------------------------------------------- /src/Ch17/HTTP3-Perf-Server-Demo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "http://localhost:5000/process", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": true, 16 | "launchBrowser": true, 17 | "applicationUrl": "https://localhost:5001/process", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ch17/HTTP3-Perf-Server-Demo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Ch17/HTTP3-Perf-Server-Demo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Ch17/QUICConsole/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | using var client = new HttpClient(); 4 | client.DefaultRequestVersion = HttpVersion.Version30; 5 | 6 | Console.WriteLine("--- localhost:5001 ---"); 7 | 8 | // The client falls back to HTTP2 or HTTP1 if HTTP3 is not supported 9 | client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; 10 | 11 | // Will use HTTP3 if the server supports it 12 | var resp = await client.GetAsync("https://localhost:5001/"); 13 | string body = await resp.Content.ReadAsStringAsync(); 14 | 15 | Console.WriteLine( 16 | $"status: {resp.StatusCode}, version: {resp.Version}, " + 17 | $"body: {body.Substring(0, Math.Min(100, body.Length))}"); -------------------------------------------------------------------------------- /src/Ch17/QUICConsole/QUICConsole.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | latest 7 | enable 8 | enable 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Ch17/QUICServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | using Microsoft.AspNetCore.Server.Kestrel.Core; 3 | using Microsoft.AspNetCore.Server.Kestrel.Https; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false); 8 | 9 | builder.WebHost.ConfigureKestrel(options => 10 | { 11 | options.ListenAnyIP(5001, listenOptions => 12 | { 13 | listenOptions.UseConnectionLogging(); 14 | listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; 15 | listenOptions.UseHttps(httpsOptions => 16 | { 17 | httpsOptions.ServerCertificateSelector = (context, host) => cert; 18 | }); 19 | }); 20 | }); 21 | 22 | var app = builder.Build(); 23 | 24 | app.MapGet("/", () => "Hello, QUIC!"); 25 | 26 | app.Run(); -------------------------------------------------------------------------------- /src/Ch17/QUICServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Ch17/QUICServer/QUICServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | True 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Ch17/QUICServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Ch17/QUICServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning", 7 | "Microsoft.AspNetCore.Hosting.Diagnostics": "Information" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /src/Ch17/QuicConnection-Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Quic; 3 | using System.Net.Security; 4 | using System.Security.Cryptography.X509Certificates; 5 | using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; 6 | 7 | try 8 | { 9 | bool isRunning = true; 10 | Console.WriteLine("Starting QUIC client..."); 11 | 12 | // First, check if QUIC is supported. 13 | if (!QuicConnection.IsSupported) 14 | { 15 | Console.WriteLine("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3."); 16 | return; 17 | } 18 | 19 | // Share configuration for each incoming connection. 20 | // This represents the minimal configuration necessary. 21 | var serverConnectionOptions = new QuicServerConnectionOptions 22 | { 23 | // Used to abort stream if it's not properly closed by the user. 24 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 25 | DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code. 26 | 27 | // Used to close the connection if it's not done by the user. 28 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 29 | DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code. 30 | 31 | // Same options as for server side SslStream. 32 | ServerAuthenticationOptions = new SslServerAuthenticationOptions 33 | { 34 | // Specify the application protocols that the server supports. This list must be a subset of the protocols specified in QuicListenerOptions.ApplicationProtocols. 35 | ApplicationProtocols = [new SslApplicationProtocol("protocol1")], 36 | // Server certificate, it can also be provided via ServerCertificateContext or ServerCertificateSelectionCallback. 37 | ServerCertificate = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), 38 | StoreLocation.CurrentUser, false) 39 | } 40 | }; 41 | // Initialize, configure the listener and start listening. 42 | var listener = await QuicListener.ListenAsync(new QuicListenerOptions 43 | { 44 | // Define the endpoint on which the server will listen for incoming connections. The port number 0 can be replaced with any valid port number as needed. 45 | ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), 46 | // List of all supported application protocols by this listener. 47 | ApplicationProtocols = [new SslApplicationProtocol("protocol1")], 48 | // Callback to provide options for the incoming connections, it gets called once per each connection. 49 | ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(serverConnectionOptions) 50 | }); 51 | // This represents the minimal configuration necessary to open a connection. 52 | var clientConnectionOptions = new QuicClientConnectionOptions 53 | { 54 | // End point of the server to connect to. 55 | RemoteEndPoint = listener.LocalEndPoint, 56 | 57 | // Used to abort stream if it's not properly closed by the user. 58 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 59 | DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code. 60 | 61 | // Used to close the connection if it's not done by the user. 62 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 63 | DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code. 64 | 65 | // Optionally set limits for inbound streams. 66 | MaxInboundUnidirectionalStreams = 10, 67 | MaxInboundBidirectionalStreams = 100, 68 | 69 | // Same options as for client side SslStream. 70 | ClientAuthenticationOptions = new SslClientAuthenticationOptions 71 | { 72 | // List of supported application protocols. 73 | ApplicationProtocols = [new SslApplicationProtocol("protocol1")], 74 | // The name of the server the client is trying to connect to. Used for server certificate validation. 75 | TargetHost = "localhost", 76 | RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true 77 | } 78 | }; 79 | 80 | // Initialize, configure and connect to the server. 81 | var connection = await QuicConnection.ConnectAsync(clientConnectionOptions); 82 | 83 | Console.WriteLine($"Connected {connection.LocalEndPoint} --> {connection.RemoteEndPoint}"); 84 | 85 | // Open a bidirectional (can both read and write) outbound stream. 86 | // Opening a stream reserves it but does not notify the peer or send any data. If you don't send data, the peer 87 | // won't be informed about the stream, which can cause AcceptInboundStreamAsync() to hang. To avoid this, ensure 88 | // you send data on the stream to properly initiate communication. 89 | var outgoingStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); 90 | 91 | // Work with the outgoing stream ... 92 | 93 | // To accept any stream on a client connection, at least one of MaxInboundBidirectionalStreams or MaxInboundUnidirectionalStreams of QuicConnectionOptions must be set. 94 | while (isRunning) 95 | { 96 | // Accept an inbound stream. 97 | var incomingStream = await connection.AcceptInboundStreamAsync(); 98 | 99 | // Work with the incoming stream ... 100 | byte[] buffer = new byte[1024]; 101 | await incomingStream.ReadExactlyAsync(buffer); 102 | string recoveredString = System.Text.Encoding.UTF8.GetString(buffer); 103 | 104 | Console.WriteLine(recoveredString + "Received: " + DateTime.UtcNow.ToString()); 105 | } 106 | 107 | // Close the connection with the custom code. 108 | await connection.CloseAsync(0x0C); 109 | 110 | // Dispose the connection. 111 | await connection.DisposeAsync(); 112 | } 113 | catch (Exception ex) 114 | { 115 | throw new Exception(ex.Message, ex); 116 | } -------------------------------------------------------------------------------- /src/Ch17/QuicConnection-Client/QuicConnection-Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | QuicConnection_Client 7 | latest 8 | enable 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Ch17/QuicListener-Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Quic; 3 | using System.Net.Security; 4 | using System.Security.Cryptography.X509Certificates; 5 | using System.Text; 6 | using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; 7 | 8 | try 9 | { 10 | bool isRunning = true; 11 | Console.WriteLine("Starting QUIC client..."); 12 | 13 | // First, check if QUIC is supported. 14 | if (!QuicConnection.IsSupported) 15 | { 16 | Console.WriteLine("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3."); 17 | return; 18 | } 19 | 20 | // Share configuration for each incoming connection. 21 | // This represents the minimal configuration necessary. 22 | var serverConnectionOptions = new QuicServerConnectionOptions 23 | { 24 | // Used to abort stream if it's not properly closed by the user. 25 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 26 | DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code. 27 | 28 | // Used to close the connection if it's not done by the user. 29 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 30 | DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code. 31 | 32 | // Same options as for server side SslStream. 33 | ServerAuthenticationOptions = new SslServerAuthenticationOptions 34 | { 35 | // Specify the application protocols that the server supports. This list must be a subset of the protocols specified in QuicListenerOptions.ApplicationProtocols. 36 | ApplicationProtocols = [new SslApplicationProtocol("protocol1")], 37 | // Server certificate, it can also be provided via ServerCertificateContext or ServerCertificateSelectionCallback. 38 | ServerCertificate = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), 39 | StoreLocation.CurrentUser, false) 40 | } 41 | }; 42 | // Initialize, configure the listener and start listening. 43 | var listener = await QuicListener.ListenAsync(new QuicListenerOptions 44 | { 45 | // Define the endpoint on which the server will listen for incoming connections. The port number 0 can be replaced with any valid port number as needed. 46 | ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), 47 | // List of all supported application protocols by this listener. 48 | ApplicationProtocols = [new SslApplicationProtocol("protocol1")], 49 | // Callback to provide options for the incoming connections, it gets called once per each connection. 50 | ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(serverConnectionOptions) 51 | }); 52 | // This represents the minimal configuration necessary to open a connection. 53 | var clientConnectionOptions = new QuicClientConnectionOptions 54 | { 55 | // End point of the server to connect to. 56 | RemoteEndPoint = listener.LocalEndPoint, 57 | 58 | // Used to abort stream if it's not properly closed by the user. 59 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 60 | DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code. 61 | 62 | // Used to close the connection if it's not done by the user. 63 | // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2 64 | DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code. 65 | 66 | // Optionally set limits for inbound streams. 67 | MaxInboundUnidirectionalStreams = 10, 68 | MaxInboundBidirectionalStreams = 100, 69 | 70 | // Same options as for client side SslStream. 71 | ClientAuthenticationOptions = new SslClientAuthenticationOptions 72 | { 73 | // List of supported application protocols. 74 | ApplicationProtocols = [new SslApplicationProtocol("protocol1")], 75 | // The name of the server the client is trying to connect to. Used for server certificate validation. 76 | TargetHost = "localhost", 77 | RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true 78 | } 79 | }; 80 | 81 | // Initialize, configure and connect to the server. 82 | var connection = await QuicConnection.ConnectAsync(clientConnectionOptions); 83 | 84 | Console.WriteLine($"Connected {connection.LocalEndPoint} --> {connection.RemoteEndPoint}"); 85 | 86 | // Open a bidirectional (can both read and write) outbound stream. 87 | // Opening a stream reserves it but does not notify the peer or send any data. If you don't send data, the peer 88 | // won't be informed about the stream, which can cause AcceptInboundStreamAsync() to hang. To avoid this, ensure 89 | // you send data on the stream to properly initiate communication. 90 | var outgoingStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); 91 | 92 | // Work with the outgoing stream ... 93 | 94 | // To accept any stream on a client connection, at least one of MaxInboundBidirectionalStreams or MaxInboundUnidirectionalStreams of QuicConnectionOptions must be set. 95 | while (isRunning) 96 | { 97 | // Accept an inbound stream. 98 | var incomingStream = await connection.AcceptInboundStreamAsync(); 99 | 100 | // Work with the incoming stream ... 101 | byte[] buffer = new byte[1024]; 102 | await incomingStream.ReadExactlyAsync(buffer); 103 | string recoveredString = System.Text.Encoding.UTF8.GetString(buffer); 104 | 105 | Console.WriteLine(recoveredString + "Received: " + DateTime.UtcNow.ToString()); 106 | } 107 | 108 | // Close the connection with the custom code. 109 | await connection.CloseAsync(0x0C); 110 | 111 | // Dispose the connection. 112 | await connection.DisposeAsync(); 113 | } 114 | catch (Exception ex) 115 | { 116 | throw new Exception(ex.Message, ex); 117 | } -------------------------------------------------------------------------------- /src/Ch17/QuicListener-Server/QuicListener-Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | QuicListener_Server 7 | latest 8 | enable 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Ch17/ch17.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QUICServer", "QUICServer\QUICServer.csproj", "{86A3638C-CB35-4EA5-A21D-DC7A6A17E8F6}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QUICConsole", "QUICConsole\QUICConsole.csproj", "{86478148-88AB-4C71-A0DE-42BAB0BF2BB8}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuicListener-Server", "QuicListener-Server\QuicListener-Server.csproj", "{2431ABF5-9196-4327-9C5C-19E045F21221}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuicConnection-Client", "QuicConnection-Client\QuicConnection-Client.csproj", "{DC39E601-77EE-4AA3-B910-6FD8835F0DE5}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HTTP3-Perf-Client-Demo", "HTTP3-Perf-Client-Demo\HTTP3-Perf-Client-Demo.csproj", "{4E7E5468-E88A-49A9-B51F-B0B6112FBC39}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HTTP3-Perf-Server-Demo", "HTTP3-Perf-Server-Demo\HTTP3-Perf-Server-Demo.csproj", "{76828FDE-FAE9-46AB-B2EA-F44C86D39A96}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HTTP3-Perf-Demo", "HTTP3-Perf-Demo", "{2E4BE413-58A9-40C4-AEA0-3585B8D654D3}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QuicConnection", "QuicConnection", "{5C0DBDCA-7C19-4337-AD0A-870D2A3EC563}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HTTP3-Demo", "HTTP3-Demo", "{8ACAF84B-4D93-407A-9B9F-65467B8B5572}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QuicListener", "QuicListener", "{040C3298-4FE7-4941-9926-1758E61110E3}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {86A3638C-CB35-4EA5-A21D-DC7A6A17E8F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {86A3638C-CB35-4EA5-A21D-DC7A6A17E8F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {86A3638C-CB35-4EA5-A21D-DC7A6A17E8F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {86A3638C-CB35-4EA5-A21D-DC7A6A17E8F6}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {86478148-88AB-4C71-A0DE-42BAB0BF2BB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {86478148-88AB-4C71-A0DE-42BAB0BF2BB8}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {86478148-88AB-4C71-A0DE-42BAB0BF2BB8}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {86478148-88AB-4C71-A0DE-42BAB0BF2BB8}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {2431ABF5-9196-4327-9C5C-19E045F21221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {2431ABF5-9196-4327-9C5C-19E045F21221}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {2431ABF5-9196-4327-9C5C-19E045F21221}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {2431ABF5-9196-4327-9C5C-19E045F21221}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {DC39E601-77EE-4AA3-B910-6FD8835F0DE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {DC39E601-77EE-4AA3-B910-6FD8835F0DE5}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {DC39E601-77EE-4AA3-B910-6FD8835F0DE5}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {DC39E601-77EE-4AA3-B910-6FD8835F0DE5}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {4E7E5468-E88A-49A9-B51F-B0B6112FBC39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {4E7E5468-E88A-49A9-B51F-B0B6112FBC39}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {4E7E5468-E88A-49A9-B51F-B0B6112FBC39}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {4E7E5468-E88A-49A9-B51F-B0B6112FBC39}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {76828FDE-FAE9-46AB-B2EA-F44C86D39A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {76828FDE-FAE9-46AB-B2EA-F44C86D39A96}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {76828FDE-FAE9-46AB-B2EA-F44C86D39A96}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {76828FDE-FAE9-46AB-B2EA-F44C86D39A96}.Release|Any CPU.Build.0 = Release|Any CPU 53 | EndGlobalSection 54 | GlobalSection(NestedProjects) = preSolution 55 | {4E7E5468-E88A-49A9-B51F-B0B6112FBC39} = {2E4BE413-58A9-40C4-AEA0-3585B8D654D3} 56 | {76828FDE-FAE9-46AB-B2EA-F44C86D39A96} = {2E4BE413-58A9-40C4-AEA0-3585B8D654D3} 57 | {DC39E601-77EE-4AA3-B910-6FD8835F0DE5} = {5C0DBDCA-7C19-4337-AD0A-870D2A3EC563} 58 | {86478148-88AB-4C71-A0DE-42BAB0BF2BB8} = {8ACAF84B-4D93-407A-9B9F-65467B8B5572} 59 | {86A3638C-CB35-4EA5-A21D-DC7A6A17E8F6} = {8ACAF84B-4D93-407A-9B9F-65467B8B5572} 60 | {2431ABF5-9196-4327-9C5C-19E045F21221} = {040C3298-4FE7-4941-9926-1758E61110E3} 61 | EndGlobalSection 62 | EndGlobal 63 | --------------------------------------------------------------------------------