├── .gitattributes
├── .gitignore
├── CONTRIBUTING.md
├── DemoApps
└── QuizGame
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── Assets
│ ├── LockScreenLogo.scale-200.png
│ ├── Logo.scale-100.png
│ ├── SmallLogo.scale-100.png
│ ├── SplashScreen.scale-100.png
│ ├── SplashScreen.scale-200.png
│ ├── Square150x150Logo.scale-200.png
│ ├── Square44x44Logo.scale-200.png
│ ├── Square44x44Logo.targetsize-24_altform-unplated.png
│ ├── StoreLogo.png
│ ├── StoreLogo.scale-100.png
│ ├── Wide310x150Logo.scale-200.png
│ └── WideLogo.scale-100.png
│ ├── Common
│ ├── BindableBase.cs
│ └── DelegateCommand.cs
│ ├── MainPage.xaml
│ ├── MainPage.xaml.cs
│ ├── Model
│ ├── GameHost.cs
│ ├── PlayerMessage.cs
│ └── Question.cs
│ ├── Package.appxmanifest
│ ├── Properties
│ ├── AssemblyInfo.cs
│ └── Default.rd.xml
│ ├── QuizGame.csproj
│ ├── View
│ ├── GamePage.xaml
│ ├── GamePage.xaml.cs
│ ├── PlayerPage.xaml
│ └── PlayerPage.xaml.cs
│ └── ViewModel
│ ├── GameViewModel.cs
│ └── PlayerViewModel.cs
├── LICENSE
├── NetworkHelper.sln
├── NetworkHelper
├── DnssdManager.cs
├── DnssdParticipant.cs
├── ICommunicationChannel.cs
├── ISessionManager.cs
├── ISessionParticipant.cs
├── NetworkHelper.csproj
├── Properties
│ ├── AssemblyInfo.cs
│ └── NetworkHelper.rd.xml
├── SessionManager.cs
├── SessionParticipant.cs
├── TcpCommunicationChannel.cs
├── UdpManager.cs
├── UdpParticipant.cs
└── project.json
├── README.md
├── SECURITY.md
├── Screenshots
├── QuizGame_CreateGame_Lobby_scaled.png
├── QuizGame_GameInProgress_scaled.png
├── QuizGame_JoinGame_scaled.png
├── QuizGame_QuestionAnswered_scaled.png
└── StartUpProject_scaled.png
└── architecture.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 | *.vspx
87 | *.sap
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 |
154 | # Windows Azure Build Output
155 | csx/
156 | *.build.csdef
157 |
158 | # Microsoft Store app package directory
159 | AppPackages/
160 |
161 | # Visual Studio cache files
162 | # files ending in .cache can be ignored
163 | *.[Cc]ache
164 | # but keep track of directories ending in .cache
165 | !*.[Cc]ache/
166 |
167 | # Others
168 | ClientBin/
169 | [Ss]tyle[Cc]op.*
170 | ~$*
171 | *~
172 | *.dbmdl
173 | *.dbproj.schemaview
174 | *.pfx
175 | *.publishsettings
176 | node_modules/
177 | orleans.codegen.cs
178 |
179 | # RIA/Silverlight projects
180 | Generated_Code/
181 |
182 | # Backup & report files from converting an old project file
183 | # to a newer Visual Studio version. Backup files are not needed,
184 | # because we have git ;-)
185 | _UpgradeReport_Files/
186 | Backup*/
187 | UpgradeLog*.XML
188 | UpgradeLog*.htm
189 |
190 | # SQL Server files
191 | *.mdf
192 | *.ldf
193 |
194 | # Business Intelligence projects
195 | *.rdl.data
196 | *.bim.layout
197 | *.bim_*.settings
198 |
199 | # Microsoft Fakes
200 | FakesAssemblies/
201 |
202 | # Node.js Tools for Visual Studio
203 | .ntvs_analysis.dat
204 |
205 | # Visual Studio 6 build log
206 | *.plg
207 |
208 | # Visual Studio 6 workspace options file
209 | *.opt
210 |
211 | # Visual Studio LightSwitch build output
212 | **/*.HTMLClient/GeneratedArtifacts
213 | **/*.DesktopClient/GeneratedArtifacts
214 | **/*.DesktopClient/ModelManifest.xml
215 | **/*.Server/GeneratedArtifacts
216 | **/*.Server/ModelManifest.xml
217 | _Pvt_Extensions
218 |
219 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
2 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/App.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
31 |
32 |
33 | -25
34 |
37 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/App.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using Windows.ApplicationModel;
27 | using Windows.ApplicationModel.Activation;
28 | using Windows.UI.Xaml;
29 | using Windows.UI.Xaml.Controls;
30 | using Windows.UI.Xaml.Navigation;
31 |
32 | namespace QuizGameClient
33 | {
34 | ///
35 | /// Provides application-specific behavior to supplement the default Application class.
36 | ///
37 | sealed partial class App : Application
38 | {
39 | ///
40 | /// Initializes the singleton application object. This is the first line of authored code
41 | /// executed, and as such is the logical equivalent of main() or WinMain().
42 | ///
43 | public App()
44 | {
45 | this.InitializeComponent();
46 | this.Suspending += OnSuspending;
47 | }
48 |
49 | ///
50 | /// Invoked when the application is launched normally by the end user. Other entry points
51 | /// will be used such as when the application is launched to open a specific file.
52 | ///
53 | /// Details about the launch request and process.
54 | protected override void OnLaunched(LaunchActivatedEventArgs e)
55 | {
56 | //#if DEBUG
57 | // if (System.Diagnostics.Debugger.IsAttached)
58 | // {
59 | // this.DebugSettings.EnableFrameRateCounter = true;
60 | // }
61 | //#endif
62 |
63 | Frame rootFrame = Window.Current.Content as Frame;
64 |
65 | // Do not repeat app initialization when the Window already has content,
66 | // just ensure that the window is active
67 | if (rootFrame == null)
68 | {
69 | // Create a Frame to act as the navigation context and navigate to the first page
70 | rootFrame = new Frame();
71 | // Set the default language
72 | rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];
73 |
74 | rootFrame.NavigationFailed += OnNavigationFailed;
75 |
76 | if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
77 | {
78 | //TODO: Load state from previously suspended application
79 | }
80 |
81 | // Place the frame in the current Window
82 | Window.Current.Content = rootFrame;
83 | }
84 |
85 | if (rootFrame.Content == null)
86 | {
87 | // When the navigation stack isn't restored navigate to the first page,
88 | // configuring the new page by passing required information as a navigation
89 | // parameter
90 | rootFrame.Navigate(typeof(MainPage), e.Arguments);
91 | }
92 |
93 | // Enable back navigation.
94 | Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested += (s, ea) =>
95 | {
96 | Frame frame = Window.Current.Content as Frame;
97 |
98 | if (frame != null && frame.CanGoBack && ea.Handled == false)
99 | {
100 | ea.Handled = true;
101 | frame.GoBack();
102 | }
103 | };
104 |
105 | // Ensure the current window is active
106 | Window.Current.Activate();
107 | }
108 |
109 | ///
110 | /// Invoked when Navigation to a certain page fails
111 | ///
112 | /// The Frame which failed navigation
113 | /// Details about the navigation failure
114 | void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
115 | {
116 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
117 | }
118 |
119 | ///
120 | /// Invoked when application execution is being suspended. Application state is saved
121 | /// without knowing whether the application will be terminated or resumed with the contents
122 | /// of memory still intact.
123 | ///
124 | /// The source of the suspend request.
125 | /// Details about the suspend request.
126 | private void OnSuspending(object sender, SuspendingEventArgs e)
127 | {
128 | var deferral = e.SuspendingOperation.GetDeferral();
129 | //TODO: Save application state and stop any background activity
130 | deferral.Complete();
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/LockScreenLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/LockScreenLogo.scale-200.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/Logo.scale-100.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/SmallLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/SmallLogo.scale-100.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/Square44x44Logo.targetsize-24_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/StoreLogo.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Assets/WideLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/DemoApps/QuizGame/Assets/WideLogo.scale-100.png
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Common/BindableBase.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.ComponentModel;
27 | using System.Linq.Expressions;
28 | using System.Runtime.CompilerServices;
29 | using Windows.UI.Xaml.Data;
30 |
31 | namespace QuizGame.Common
32 | {
33 | ///
34 | /// Implementation of to simplify models.
35 | ///
36 | [Windows.Foundation.Metadata.WebHostHidden]
37 | public abstract class BindableBase : INotifyPropertyChanged
38 | {
39 | ///
40 | /// Multicast event for property change notifications.
41 | ///
42 | public event PropertyChangedEventHandler PropertyChanged;
43 |
44 | ///
45 | /// Checks if a property already matches a desired value. Sets the property and
46 | /// notifies listeners only when necessary.
47 | ///
48 | /// Type of the property.
49 | /// Reference to a property with both getter and setter.
50 | /// Desired value for the property.
51 | /// Name of the property used to notify listeners. This
52 | /// value is optional and can be provided automatically when invoked from compilers that
53 | /// support CallerMemberName.
54 | /// True if the value was changed, false if the existing value matched the
55 | /// desired value.
56 | protected bool SetProperty(ref T storage, T value, [CallerMemberName] String propertyName = null)
57 | {
58 | if (object.Equals(storage, value)) return false;
59 |
60 | storage = value;
61 | OnPropertyChanged(propertyName);
62 | return true;
63 | }
64 |
65 | ///
66 | /// Notifies listeners that a property value has changed.
67 | ///
68 | /// Name of the property used to notify listeners. This
69 | /// value is optional and can be provided automatically when invoked from compilers
70 | /// that support .
71 | protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
72 | {
73 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Common/DelegateCommand.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Threading.Tasks;
27 | using System.Windows.Input;
28 |
29 | namespace QuizGame.Common
30 | {
31 | ///
32 | /// An whose delegates can be attached for and .
33 | ///
34 | /// Parameter type.
35 | ///
36 | /// The constructor deliberately prevents the use of value types.
37 | /// Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings.
38 | /// Using default(T) was considered and rejected as a solution because the implementor would not be able to distinguish between a valid and defaulted values.
39 | ///
40 | /// Instead, callers should support a value type by using a nullable value type and checking the HasValue property before using the Value property.
41 | ///
42 | ///
43 | /// public MyClass()
44 | /// {
45 | /// this.submitCommand = new DelegateCommand<int?>(this.Submit, this.CanSubmit);
46 | /// }
47 | ///
48 | /// private bool CanSubmit(int? customerId)
49 | /// {
50 | /// return (customerId.HasValue && customers.Contains(customerId.Value));
51 | /// }
52 | ///
53 | ///
54 | ///
55 | public class DelegateCommand : DelegateCommandBase
56 | {
57 | ///
58 | /// Initializes a new instance of .
59 | ///
60 | /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.
61 | /// will always return true.
62 | public DelegateCommand(Action executeMethod)
63 | : this(executeMethod, (o) => true)
64 | {
65 | }
66 |
67 | ///
68 | /// Initializes a new instance of .
69 | ///
70 | /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.
71 | /// Delegate to execute when CanExecute is called on the command. This can be null.
72 | /// When both and ar .
73 | public DelegateCommand(Action executeMethod, Func canExecuteMethod)
74 | : base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
75 | {
76 | if (executeMethod == null || canExecuteMethod == null)
77 | throw new ArgumentNullException("executeMethod");
78 | }
79 |
80 | ///
81 | /// Factory method to create a new instance of from an awaitable handler method.
82 | ///
83 | /// Delegate to execute when Execute is called on the command.
84 | /// Constructed instance of
85 | public static DelegateCommand FromAsyncHandler(Func executeMethod)
86 | {
87 | return new DelegateCommand(executeMethod);
88 | }
89 |
90 | ///
91 | /// Factory method to create a new instance of from an awaitable handler method.
92 | ///
93 | /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.
94 | /// Delegate to execute when CanExecute is called on the command. This can be null.
95 | /// Constructed instance of
96 | public static DelegateCommand FromAsyncHandler(Func executeMethod, Func canExecuteMethod)
97 | {
98 | return new DelegateCommand(executeMethod, canExecuteMethod);
99 | }
100 |
101 | ///
102 | ///Determines if the command can execute by invoked the provided during construction.
103 | ///
104 | /// Data used by the command to determine if it can execute.
105 | ///
106 | /// if this command can be executed; otherwise, .
107 | ///
108 | public bool CanExecute(T parameter)
109 | {
110 | return base.CanExecute(parameter);
111 | }
112 |
113 | ///
114 | ///Executes the command and invokes the provided during construction.
115 | ///
116 | /// Data used by the command.
117 | public async Task Execute(T parameter)
118 | {
119 | await base.Execute(parameter);
120 | }
121 |
122 |
123 | private DelegateCommand(Func executeMethod)
124 | : this(executeMethod, (o) => true)
125 | {
126 | }
127 |
128 | private DelegateCommand(Func executeMethod, Func canExecuteMethod)
129 | : base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
130 | {
131 | if (executeMethod == null || canExecuteMethod == null)
132 | throw new ArgumentNullException("executeMethod");
133 | }
134 |
135 | }
136 |
137 | ///
138 | /// An whose delegates do not take any parameters for and .
139 | ///
140 | ///
141 | ///
142 | public class DelegateCommand : DelegateCommandBase
143 | {
144 | ///
145 | /// Creates a new instance of with the to invoke on execution.
146 | ///
147 | /// The to invoke when is called.
148 | public DelegateCommand(Action executeMethod)
149 | : this(executeMethod, () => true)
150 | {
151 | }
152 |
153 | ///
154 | /// Creates a new instance of with the to invoke on execution
155 | /// and a to query for determining if the command can execute.
156 | ///
157 | /// The to invoke when is called.
158 | /// The to invoke when is called
159 | public DelegateCommand(Action executeMethod, Func canExecuteMethod)
160 | : base((o) => executeMethod(), (o) => canExecuteMethod())
161 | {
162 | if (executeMethod == null || canExecuteMethod == null)
163 | throw new ArgumentNullException("executeMethod");
164 | }
165 |
166 | ///
167 | /// Factory method to create a new instance of from an awaitable handler method.
168 | ///
169 | /// Delegate to execute when Execute is called on the command.
170 | /// Constructed instance of
171 | public static DelegateCommand FromAsyncHandler(Func executeMethod)
172 | {
173 | return new DelegateCommand(executeMethod);
174 | }
175 |
176 | ///
177 | /// Factory method to create a new instance of from an awaitable handler method.
178 | ///
179 | /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.
180 | /// Delegate to execute when CanExecute is called on the command. This can be null.
181 | /// Constructed instance of
182 | public static DelegateCommand FromAsyncHandler(Func executeMethod, Func canExecuteMethod)
183 | {
184 | return new DelegateCommand(executeMethod, canExecuteMethod);
185 | }
186 |
187 | ///
188 | /// Executes the command.
189 | ///
190 | public async Task Execute()
191 | {
192 | await Execute(null);
193 | }
194 |
195 | ///
196 | /// Determines if the command can be executed.
197 | ///
198 | /// Returns if the command can execute, otherwise returns .
199 | public bool CanExecute()
200 | {
201 | return CanExecute(null);
202 | }
203 |
204 | private DelegateCommand(Func executeMethod)
205 | : this(executeMethod, () => true)
206 | {
207 | }
208 |
209 | private DelegateCommand(Func executeMethod, Func canExecuteMethod)
210 | : base((o) => executeMethod(), (o) => canExecuteMethod())
211 | {
212 | if (executeMethod == null || canExecuteMethod == null)
213 | throw new ArgumentNullException("executeMethod");
214 | }
215 | }
216 |
217 | ///
218 | /// An whose delegates can be attached for and .
219 | ///
220 | public abstract class DelegateCommandBase : ICommand
221 | {
222 | private readonly Func _executeMethod;
223 | private readonly Func _canExecuteMethod;
224 |
225 | ///
226 | /// Creates a new instance of a , specifying both the execute action and the can execute function.
227 | ///
228 | /// The to execute when is invoked.
229 | /// The to invoked when is invoked.
230 | protected DelegateCommandBase(Action executeMethod, Func canExecuteMethod)
231 | {
232 | if (executeMethod == null || canExecuteMethod == null)
233 | throw new ArgumentNullException("executeMethod");
234 |
235 | _executeMethod = (arg) => { executeMethod(arg); return Task.Delay(0); };
236 | _canExecuteMethod = canExecuteMethod;
237 | }
238 |
239 | ///
240 | /// Creates a new instance of a , specifying both the Execute action as an awaitable Task and the CanExecute function.
241 | ///
242 | /// The to execute when is invoked.
243 | /// The to invoked when is invoked.
244 | protected DelegateCommandBase(Func executeMethod, Func canExecuteMethod)
245 | {
246 | if (executeMethod == null || canExecuteMethod == null)
247 | throw new ArgumentNullException("executeMethod");
248 |
249 | _executeMethod = executeMethod;
250 | _canExecuteMethod = canExecuteMethod;
251 | }
252 |
253 | ///
254 | /// Raises on the UI thread so every
255 | /// command invoker can requery .
256 | ///
257 | protected virtual void OnCanExecuteChanged()
258 | {
259 | var handlers = CanExecuteChanged;
260 | if (handlers != null)
261 | {
262 | handlers(this, EventArgs.Empty);
263 | }
264 | }
265 |
266 |
267 | ///
268 | /// Raises on the UI thread so every command invoker
269 | /// can requery to check if the command can execute.
270 | /// Note that this will trigger the execution of once for each invoker.
271 | ///
272 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
273 | public void RaiseCanExecuteChanged()
274 | {
275 | OnCanExecuteChanged();
276 | }
277 |
278 | async void ICommand.Execute(object parameter)
279 | {
280 | await Execute(parameter);
281 | }
282 |
283 | bool ICommand.CanExecute(object parameter)
284 | {
285 | return CanExecute(parameter);
286 | }
287 |
288 | ///
289 | /// Executes the command with the provided parameter by invoking the supplied during construction.
290 | ///
291 | ///
292 | protected async Task Execute(object parameter)
293 | {
294 | await _executeMethod(parameter);
295 | }
296 |
297 | ///
298 | /// Determines if the command can execute with the provided parameter by invoking the supplied during construction.
299 | ///
300 | /// The parameter to use when determining if this command can execute.
301 | /// Returns if the command can execute. otherwise.
302 | protected bool CanExecute(object parameter)
303 | {
304 | return _canExecuteMethod == null || _canExecuteMethod(parameter);
305 | }
306 |
307 | ///
308 | /// Occurs when changes happen that affect whether or not the command should execute.
309 | ///
310 | public event EventHandler CanExecuteChanged;
311 |
312 | }
313 |
314 | }
--------------------------------------------------------------------------------
/DemoApps/QuizGame/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using Windows.UI.Xaml.Controls;
26 |
27 | namespace QuizGameClient
28 | {
29 | public sealed partial class MainPage : Page
30 | {
31 | public MainPage()
32 | {
33 | this.InitializeComponent();
34 | }
35 |
36 | private void OnCreateGameClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
37 | {
38 | this.Frame.Navigate(typeof(QuizGame.GamePage));
39 | }
40 |
41 | private void OnJoinGameClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
42 | {
43 | this.Frame.Navigate(typeof(QuizGame.PlayerPage));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Model/GameHost.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using NetworkHelper;
26 | using System;
27 |
28 | namespace QuizGame.Model
29 | {
30 | public class GameHost
31 | {
32 | public string Name { get; set; }
33 | public Guid Id { get; set; }
34 | public ICommunicationChannel CommChannel { get; set; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Model/PlayerMessage.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | namespace QuizGame.Model
26 | {
27 | ///
28 | /// A message that is from a player to the game host.
29 | ///
30 | public class PlayerMessage
31 | {
32 | ///
33 | /// The command requested by the player.
34 | ///
35 | public PlayerMessageType Command { get; set; }
36 | ///
37 | /// The name of the player that sent the command.
38 | ///
39 | public string PlayerName { get; set; }
40 | ///
41 | /// The index of the Question.Options list that the player chose.
42 | ///
43 | public int QuestionAnswer { get; set; }
44 | }
45 |
46 | ///
47 | /// An enumeration representing the available commands that players can send to the game host.
48 | ///
49 | public enum PlayerMessageType
50 | {
51 | Join = 0,
52 | Leave,
53 | Answer
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Model/Question.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System.Collections.Generic;
26 |
27 | namespace QuizGame.Model
28 | {
29 | ///
30 | /// A quiz question that is displayed in the game host UI and sent to players.
31 | ///
32 | public class Question
33 | {
34 | ///
35 | /// The question text.
36 | ///
37 | public string Text { get; set; }
38 | ///
39 | /// List of available answers.
40 | ///
41 | public List Options { get; set; }
42 | ///
43 | /// The index of the correct answer in Options.
44 | ///
45 | public int CorrectAnswerIndex { get; set; }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | QuizGame
7 | Microsoft Corporation
8 | Assets\StoreLogo.png
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System.Reflection;
26 | using System.Runtime.CompilerServices;
27 | using System.Runtime.InteropServices;
28 |
29 | // General Information about an assembly is controlled through the following
30 | // set of attributes. Change these attribute values to modify the information
31 | // associated with an assembly.
32 | [assembly: AssemblyTitle("QuizGame")]
33 | [assembly: AssemblyDescription("")]
34 | [assembly: AssemblyConfiguration("")]
35 | [assembly: AssemblyCompany("Microsoft Corporation")]
36 | [assembly: AssemblyProduct("QuizGame")]
37 | [assembly: AssemblyCopyright("Copyright (c) 2015 Microsoft Corporation. All rights reserved.")]
38 | [assembly: AssemblyTrademark("")]
39 | [assembly: AssemblyCulture("")]
40 |
41 | // Version information for an assembly consists of the following four values:
42 | //
43 | // Major Version
44 | // Minor Version
45 | // Build Number
46 | // Revision
47 | //
48 | // You can specify all the values or you can default the Build and Revision Numbers
49 | // by using the '*' as shown below:
50 | // [assembly: AssemblyVersion("1.0.*")]
51 | [assembly: AssemblyVersion("1.0.0.0")]
52 | [assembly: AssemblyFileVersion("1.0.0.0")]
53 | [assembly: ComVisible(false)]
--------------------------------------------------------------------------------
/DemoApps/QuizGame/Properties/Default.rd.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/QuizGame.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | x86
7 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}
8 | AppContainerExe
9 | Properties
10 | QuizGame
11 | QuizGame
12 | en-US
13 | UAP
14 | 10.0.17763.0
15 | 10.0.17763.0
16 | 14
17 | true
18 | 512
19 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
20 | win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot
21 |
22 |
23 | true
24 | bin\ARM\Debug\
25 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
26 | ;2008
27 | full
28 | ARM
29 | false
30 | prompt
31 | true
32 |
33 |
34 | bin\ARM\Release\
35 | TRACE;NETFX_CORE;WINDOWS_UAP
36 | true
37 | ;2008
38 | pdbonly
39 | ARM
40 | false
41 | prompt
42 | true
43 | true
44 |
45 |
46 | true
47 | bin\x64\Debug\
48 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
49 | ;2008
50 | full
51 | x64
52 | false
53 | prompt
54 | true
55 |
56 |
57 | bin\x64\Release\
58 | TRACE;NETFX_CORE;WINDOWS_UAP
59 | true
60 | ;2008
61 | pdbonly
62 | x64
63 | false
64 | prompt
65 | true
66 | true
67 |
68 |
69 | true
70 | bin\x86\Debug\
71 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
72 | ;2008
73 | full
74 | x86
75 | false
76 | prompt
77 | true
78 |
79 |
80 | bin\x86\Release\
81 | TRACE;NETFX_CORE;WINDOWS_UAP
82 | true
83 | ;2008
84 | pdbonly
85 | x86
86 | false
87 | prompt
88 | true
89 | true
90 |
91 |
92 |
93 |
94 |
95 |
96 | App.xaml
97 |
98 |
99 |
100 |
101 |
102 |
103 | GamePage.xaml
104 |
105 |
106 | MainPage.xaml
107 |
108 |
109 |
110 | PlayerPage.xaml
111 |
112 |
113 |
114 |
115 | Designer
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | MSBuild:Compile
131 | Designer
132 |
133 |
134 | Designer
135 | MSBuild:Compile
136 |
137 |
138 | MSBuild:Compile
139 | Designer
140 |
141 |
142 | Designer
143 | MSBuild:Compile
144 |
145 |
146 |
147 |
148 | {737229ae-d189-4765-b37b-31a326996a70}
149 | NetworkHelper
150 |
151 |
152 |
153 |
154 | 5.0.0
155 |
156 |
157 |
158 | 14.0
159 |
160 |
161 |
168 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/View/GamePage.xaml:
--------------------------------------------------------------------------------
1 |
24 |
32 |
33 |
34 |
37 |
38 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
69 |
70 |
73 |
74 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/View/GamePage.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using Windows.UI.Core;
26 | using Windows.UI.Xaml;
27 | using Windows.UI.Xaml.Controls;
28 | using Windows.UI.Xaml.Navigation;
29 | using QuizGame.ViewModel;
30 |
31 | namespace QuizGame
32 | {
33 | ///
34 | /// An empty page that can be used on its own or navigated to within a Frame.
35 | ///
36 | public sealed partial class GamePage : Page
37 | {
38 | public GameViewModel ViewModel { get; set; }
39 |
40 | public GamePage()
41 | {
42 | InitializeComponent();
43 | }
44 |
45 | protected async override void OnNavigatedFrom(NavigationEventArgs e)
46 | {
47 | base.OnNavigatedFrom(e);
48 |
49 | await ViewModel.StopListeningAsync();
50 | }
51 |
52 | protected override void OnNavigatedTo(NavigationEventArgs e)
53 | {
54 | ViewModel = new GameViewModel();
55 |
56 | Frame rootFrame = Window.Current.Content as Frame;
57 |
58 | if (rootFrame != null && rootFrame.CanGoBack)
59 | {
60 | // Show UI in title bar if opted-in and in-app backstack is not empty.
61 | SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
62 | AppViewBackButtonVisibility.Visible;
63 | }
64 | else
65 | {
66 | // Remove the UI from the title bar if in-app back stack is empty.
67 | SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
68 | AppViewBackButtonVisibility.Collapsed;
69 | }
70 | }
71 | }
72 |
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/View/PlayerPage.xaml:
--------------------------------------------------------------------------------
1 |
24 |
34 |
35 |
36 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/View/PlayerPage.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using Windows.UI.Core;
26 | using Windows.UI.Xaml;
27 | using Windows.UI.Xaml.Controls;
28 | using Windows.UI.Xaml.Navigation;
29 | using QuizGame.ViewModel;
30 |
31 | namespace QuizGame
32 | {
33 | ///
34 | /// An empty page that can be used on its own or navigated to within a Frame.
35 | ///
36 | public sealed partial class PlayerPage : Page
37 | {
38 | public PlayerViewModel ViewModel { get; set; }
39 |
40 | public PlayerPage()
41 | {
42 | this.InitializeComponent();
43 | }
44 |
45 | protected async override void OnNavigatedFrom(NavigationEventArgs e)
46 | {
47 | base.OnNavigatedFrom(e);
48 |
49 | await ViewModel.StopListeningAsync();
50 | }
51 |
52 | protected async override void OnNavigatedTo(NavigationEventArgs e)
53 | {
54 | ViewModel = new PlayerViewModel();
55 |
56 | await ViewModel.StartListeningAsync();
57 |
58 | Frame rootFrame = Window.Current.Content as Frame;
59 |
60 | if (rootFrame.CanGoBack)
61 | {
62 | // Show UI in title bar if opted-in and in-app backstack is not empty.
63 | SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
64 | AppViewBackButtonVisibility.Visible;
65 | }
66 | else
67 | {
68 | // Remove the UI from the title bar if in-app back stack is empty.
69 | SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
70 | AppViewBackButtonVisibility.Collapsed;
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/ViewModel/GameViewModel.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using NetworkHelper;
26 | using QuizGame.Common;
27 | using QuizGame.Model;
28 | using System;
29 | using System.Collections.Generic;
30 | using System.Collections.ObjectModel;
31 | using System.Linq;
32 | using System.Threading.Tasks;
33 | using Windows.ApplicationModel.Core;
34 | using Windows.UI;
35 | using Windows.UI.Core;
36 | using Windows.UI.Text;
37 | using Windows.UI.Xaml;
38 | using Windows.UI.Xaml.Media;
39 |
40 | namespace QuizGame.ViewModel
41 | {
42 | public class PlayerProgress
43 | {
44 | public SolidColorBrush AnsweredCurrentQuestionBrush { get; internal set; }
45 | public FontWeight AnsweredCurrentQuestionFontWeight { get; internal set; }
46 | public string Name { get; internal set; }
47 | }
48 |
49 | public class PlayerResult
50 | {
51 | public string Name { get; internal set; }
52 | public Int32 Score { get; internal set; }
53 | }
54 |
55 | public enum GameState { PreGame, Lobby, GameUnderway, Results }
56 |
57 | ///
58 | /// This is the ViewModel for the HostPage, it ties all the UI logic with the game logic.
59 | ///
60 | public class GameViewModel : BindableBase
61 | {
62 | ///
63 | /// A static helper function to run code on the UI thread. This is used in callbacks from the NetworkHelper library.
64 | ///
65 | private static Func callOnUiThread = async (handler) => await
66 | CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
67 | CoreDispatcherPriority.Normal, handler);
68 |
69 | ///
70 | /// A dictionary of submitted answers from all the players.
71 | ///
72 | private Dictionary> SubmittedAnswers { get; set; }
73 |
74 | ///
75 | /// The player names stored for the UI.
76 | ///
77 | public ObservableCollection PlayerNames { get; set; }
78 |
79 | ///
80 | /// The list of questions that will be sent to players.
81 | ///
82 | private List Questions { get; set; } = new List()
83 | {
84 | new Question
85 | {
86 | Text = "In which year was Microsoft founded?",
87 | Options = new List { "1971", "1973", "1975", "1977" },
88 | CorrectAnswerIndex = 2
89 | },
90 | new Question
91 | {
92 | Text = "What was the Microsoft slogan in 2005?",
93 | Options = new List
94 | {
95 | "A computer on every desktop.",
96 | "Where do you want to go today?",
97 | "Your Potential. Our Passion.",
98 | "Be what's next."
99 | },
100 | CorrectAnswerIndex = 1
101 | },
102 | new Question
103 | {
104 | Text = "Including Clippy, how many Office Assistants were in Office 97?",
105 | Options = new List { "5", "7", "9", "Wait...there were others?" },
106 | CorrectAnswerIndex = 2
107 | },
108 | new Question
109 | {
110 | Text = "The dog Rover, in what 1995 Microsoft product, could be considered a precursor to Cortana?",
111 | Options = new List
112 | {
113 | "Microsoft Encarta",
114 | "Microsoft Bob",
115 | "Microsoft Live One Care",
116 | "Microsoft Live Mesh"
117 | },
118 | CorrectAnswerIndex = 1
119 | }
120 | };
121 |
122 | ///
123 | /// The position of the CurrentQuestion in the Questions list.
124 | ///
125 | private int currentQuestionIndex = -1;
126 |
127 | ///
128 | /// Maps the player names to their guids.
129 | ///
130 | private Dictionary _playerToParticipantMap = new Dictionary();
131 |
132 | ///
133 | /// The manager that sends UDP advertisement messages and manages a list of participants.
134 | ///
135 | private UdpManager _manager = new UdpManager();
136 |
137 | ///
138 | /// The list of communication channels to send messages to participants.
139 | ///
140 | private List _participantCommunicationChannels = new List();
141 |
142 | ///
143 | /// The communication channel to listen for messages from participants.
144 | ///
145 | private ICommunicationChannel _managerCommunicationChannel = new TcpCommunicationChannel();
146 |
147 | public GameViewModel()
148 | {
149 | // When a player connects to the host.
150 | _manager.ParticipantConnected += OnParticipantConnected;
151 |
152 | // When a message is received from a player.
153 | _managerCommunicationChannel.MessageReceived += OnMessageReceived;
154 |
155 | // Attach an event handler to the PropertyChanged event so that
156 | // PlayerProgress and NextButtonText are updated correctly.
157 | PropertyChanged += (s, e) =>
158 | {
159 | if (e.PropertyName.Equals("SubmittedAnswers"))
160 | {
161 | OnPropertyChanged(nameof(PlayerProgress));
162 | }
163 | if (e.PropertyName.Equals("CurrentQuestion") &&
164 | Questions.Last() == CurrentQuestion)
165 | {
166 | NextButtonText = "Show results";
167 | }
168 | };
169 |
170 | NextButtonText = "Next question";
171 |
172 | // Set up game information.
173 | PlayerNames = new ObservableCollection();
174 | SubmittedAnswers = new Dictionary>();
175 | }
176 |
177 | ///
178 | /// Command for creating a game. After this command is executed,
179 | /// the host will enter the lobby screen and start broadcasting itself
180 | /// to listening players.
181 | ///
182 | public DelegateCommand CreateGameCommand
183 | {
184 | get
185 | {
186 | return _createGameCommand ?? (_createGameCommand = new DelegateCommand(
187 | async () =>
188 | {
189 | GameState = GameState.Lobby;
190 | await StartAdvertisingGameAsync(GameName);
191 | }));
192 | }
193 | }
194 | private DelegateCommand _createGameCommand;
195 |
196 | ///
197 | /// Command for starting a game. After this command is executed,
198 | /// the host will send the first question to all the players.
199 | ///
200 | public DelegateCommand StartGameCommand
201 | {
202 | get
203 | {
204 | return _startGameCommand ?? (_startGameCommand = new DelegateCommand(
205 | async () =>
206 | {
207 | // Start game.
208 | currentQuestionIndex = 0;
209 | await SendCurrentQuestion();
210 |
211 | // On Question changed.
212 | OnPropertyChanged(nameof(CurrentQuestion));
213 | OnPropertyChanged(nameof(PlayerProgress));
214 |
215 | GameState = GameState.GameUnderway;
216 | StopAdvertisingGame();
217 | },
218 | () => GameState == GameState.Lobby));
219 | }
220 | }
221 | private DelegateCommand _startGameCommand;
222 |
223 | ///
224 | /// Command for either sending the next question to all players, or displaying the results
225 | /// if the last question was already sent.
226 | ///
227 | public DelegateCommand NextQuestionCommand
228 | {
229 | get
230 | {
231 | return _nextQuestionCommand ?? (_nextQuestionCommand = new DelegateCommand(
232 | async () =>
233 | {
234 | currentQuestionIndex++;
235 | await SendCurrentQuestion();
236 |
237 | // Notify the UI that the current question has changed.
238 | OnPropertyChanged(nameof(CurrentQuestion));
239 | OnPropertyChanged(nameof(PlayerProgress));
240 |
241 | // If the game is over, then change the game state.
242 | if (currentQuestionIndex >= Questions.Count())
243 | {
244 | GameState = GameState.Results;
245 | }
246 | },
247 | // Can execute if the game is underway and the current question is not null.
248 | () => GameState == GameState.GameUnderway && CurrentQuestion != null));
249 | }
250 | }
251 | private DelegateCommand _nextQuestionCommand;
252 |
253 | ///
254 | /// Command for ending the game and going back to the lobby. When returning to the lobby,
255 | /// the host will start broadcasting itself to other players.
256 | ///
257 | public DelegateCommand EndGameCommand
258 | {
259 | get
260 | {
261 | return _endGameCommand ?? (_endGameCommand = new DelegateCommand(async () =>
262 | {
263 | await StartAdvertisingGameAsync(GameName);
264 | GameState = GameState.Lobby;
265 | NextButtonText = "Next question";
266 | },
267 | // This command can only execute if the state of the game is showing the results.
268 | () => GameState == GameState.Results));
269 | }
270 | }
271 | private DelegateCommand _endGameCommand;
272 |
273 | ///
274 | /// The current state of the game.
275 | ///
276 | public GameState GameState
277 | {
278 | get
279 | {
280 | return _gameState;
281 | }
282 |
283 | set
284 | {
285 | if (SetProperty(ref _gameState, value))
286 | {
287 | // Alert the UI that all the visiblity properties may have changed, because the game state changed.
288 | OnPropertyChanged(nameof(PreGameVisibility));
289 | OnPropertyChanged(nameof(StartVisibility));
290 | OnPropertyChanged(nameof(GameUnderwayVisibility));
291 | OnPropertyChanged(nameof(ResultsVisibility));
292 | OnPropertyChanged(nameof(PlayerResults));
293 |
294 | StartGameCommand.RaiseCanExecuteChanged();
295 | NextQuestionCommand.RaiseCanExecuteChanged();
296 | EndGameCommand.RaiseCanExecuteChanged();
297 | }
298 | }
299 | }
300 | private GameState _gameState;
301 |
302 | ///
303 | /// The name of the game that players will see when selecting the game they want to join.
304 | ///
305 | public string GameName
306 | {
307 | get
308 | {
309 | return _gameName;
310 | }
311 | set
312 | {
313 | if (SetProperty(ref _gameName, value))
314 | {
315 | OnPropertyChanged(nameof(GameName));
316 | StartGameCommand.RaiseCanExecuteChanged();
317 | }
318 | }
319 | }
320 | private string _gameName;
321 |
322 | ///
323 | /// The text for the "next" button. This allows text to change based on the current
324 | /// game state.
325 | ///
326 | public string NextButtonText
327 | {
328 | get
329 | {
330 | return _nextButtonText;
331 | }
332 | set
333 | {
334 | SetProperty(ref _nextButtonText, value);
335 | }
336 | }
337 | private string _nextButtonText;
338 |
339 | ///
340 | /// Gets the progress for each player.
341 | ///
342 | public List PlayerProgress
343 | {
344 | get
345 | {
346 | var players = SubmittedAnswers.AsEnumerable().Select(kvp => new PlayerProgress
347 | {
348 | Name = kvp.Key,
349 | AnsweredCurrentQuestionFontWeight =
350 | CurrentQuestion != null &&
351 | kvp.Value.ContainsKey(CurrentQuestion) &&
352 | kvp.Value[CurrentQuestion].HasValue ?
353 | FontWeights.ExtraBold : FontWeights.Normal,
354 | AnsweredCurrentQuestionBrush =
355 | CurrentQuestion != null &&
356 | kvp.Value.ContainsKey(CurrentQuestion) &&
357 | kvp.Value[CurrentQuestion].HasValue ?
358 | new SolidColorBrush(Colors.Green) :
359 | new SolidColorBrush(Colors.LightGray)
360 | });
361 | return players.ToList();
362 | }
363 | }
364 |
365 | ///
366 | /// Gets the results for each player.
367 | ///
368 | public List PlayerResults
369 | {
370 | get
371 | {
372 | var correctAnswers = Questions.Select(question => question.CorrectAnswerIndex);
373 | var results =
374 | from playerResults in SubmittedAnswers.AsEnumerable()
375 | let score = playerResults.Value.AsEnumerable()
376 | .Select(kvp => kvp.Value)
377 | .Zip(correctAnswers, (playerAnswer, actualAnswer) =>
378 | playerAnswer.HasValue && playerAnswer.Value == actualAnswer)
379 | .Count(isCorrect => isCorrect)
380 | select new { PlayerName = playerResults.Key, Score = score };
381 |
382 | return results.ToDictionary(result => result.PlayerName, result => result.Score).Select(
383 | kvp => new PlayerResult { Name = kvp.Key, Score = kvp.Value }).ToList();
384 | }
385 | }
386 |
387 | ///
388 | /// Gets the current question that players see.
389 | ///
390 | public Question CurrentQuestion => currentQuestionIndex > -1 &&
391 | currentQuestionIndex < Questions.Count ? Questions[currentQuestionIndex] : null;
392 |
393 | ///
394 | /// Used to determine if the PreGame screen is visible or not.
395 | ///
396 | public Visibility PreGameVisibility =>
397 | GameState == GameState.PreGame ? Visibility.Visible : Visibility.Collapsed;
398 |
399 | ///
400 | /// Used to determine if the Lobby screen is visible or not.
401 | ///
402 | public Visibility StartVisibility =>
403 | GameState == GameState.Lobby ? Visibility.Visible : Visibility.Collapsed;
404 |
405 | ///
406 | /// Used to determine if the Questions screen is visible or not.
407 | ///
408 | public Visibility GameUnderwayVisibility =>
409 | GameState == GameState.GameUnderway ? Visibility.Visible : Visibility.Collapsed;
410 |
411 | ///
412 | /// Used to determine if the scores screen is visible or not.
413 | ///
414 | public Visibility ResultsVisibility =>
415 | GameState == GameState.Results ? Visibility.Visible : Visibility.Collapsed;
416 |
417 | ///
418 | /// Stops advertising and listening for answer
419 | ///
420 | public async Task StopListeningAsync()
421 | {
422 | _manager.StopAdvertising();
423 | await _managerCommunicationChannel.StopListening();
424 | }
425 |
426 | ///
427 | /// Updates the UI when a player has left.
428 | ///
429 | ///
430 | private void OnPlayerLeft(string playerName)
431 | {
432 | _manager.RemoveParticipant(_playerToParticipantMap[playerName]);
433 | _playerToParticipantMap.Remove(playerName);
434 |
435 | if (PlayerNames.Remove(playerName))
436 | {
437 | SubmittedAnswers.Remove(playerName);
438 | OnPropertyChanged(nameof(PlayerNames));
439 | OnPropertyChanged(nameof(SubmittedAnswers));
440 | }
441 | }
442 |
443 | ///
444 | /// Updates the UI when a player has joined.
445 | ///
446 | ///
447 | private void OnPlayerJoined(PlayerMessage message, Guid guid)
448 | {
449 | Guid duplicatePlayerID;
450 |
451 | if (!_playerToParticipantMap.TryGetValue(message.PlayerName, out duplicatePlayerID))
452 | {
453 | _playerToParticipantMap.Add(message.PlayerName, guid);
454 |
455 | if (PlayerNames.Contains(message.PlayerName))
456 | {
457 | message.PlayerName += ".";
458 | }
459 |
460 | PlayerNames.Add(message.PlayerName);
461 |
462 | SubmittedAnswers.Add(message.PlayerName,
463 | new Dictionary(Questions.Count));
464 |
465 | OnPropertyChanged(nameof(PlayerNames));
466 | OnPropertyChanged(nameof(SubmittedAnswers));
467 | }
468 | }
469 |
470 | ///
471 | /// When a message is received from a player.
472 | ///
473 | private async void OnMessageReceived(object sender, IMessageReceivedEventArgs e)
474 | {
475 | // Deserialize the message
476 | object data = new PlayerMessage();
477 | e.GetDeserializedMessage(ref data);
478 | var message = data as PlayerMessage;
479 |
480 | switch (message.Command)
481 | {
482 | case PlayerMessageType.Answer:
483 | await callOnUiThread(() => OnAnswerReceived(message.PlayerName, message.QuestionAnswer));
484 | break;
485 | case PlayerMessageType.Leave:
486 | await callOnUiThread(() => OnPlayerLeft(message.PlayerName));
487 | break;
488 | }
489 | }
490 |
491 | ///
492 | /// When a player has been connected to the host.
493 | ///
494 | private async void OnParticipantConnected(object sender, ParticipantConnectedEventArgs e)
495 | {
496 | // A player has joined the game.
497 | _participantCommunicationChannels.Add(_manager.CreateCommunicationChannel(e.Id));
498 |
499 | PlayerMessage data = new PlayerMessage
500 | {
501 | PlayerName = e.Message.ToString(),
502 | QuestionAnswer = 0,
503 | Command = PlayerMessageType.Join
504 | };
505 |
506 | await callOnUiThread(() => OnPlayerJoined(data, e.Id));
507 | }
508 |
509 | ///
510 | /// Updates the UI when an answer has been received from a player.
511 | ///
512 | private bool OnAnswerReceived(string playerName, int answerIndex)
513 | {
514 | if (playerName != null && CurrentQuestion != null)
515 | {
516 | SubmittedAnswers[playerName][CurrentQuestion] = answerIndex;
517 | OnPropertyChanged(nameof(SubmittedAnswers));
518 | return true;
519 | }
520 |
521 | return false;
522 | }
523 |
524 | ///
525 | /// Sends the current question to all players that have joined.
526 | ///
527 | ///
528 | private async Task SendCurrentQuestion()
529 | {
530 | // Do this even if currentQuestionIndex < Questions.Count, because the client needs to know
531 | // when the current question goes to null so it can update its UI state.
532 | var clientList = new List();
533 | clientList.AddRange(_playerToParticipantMap.Values);
534 |
535 | var messageTasks = clientList.Select(client =>
536 | _manager.CreateCommunicationChannel(client).SendRemoteMessageAsync(CurrentQuestion ?? new Question()));
537 |
538 | await Task.WhenAll(messageTasks);
539 |
540 | OnPropertyChanged(nameof(CurrentQuestion));
541 | }
542 |
543 | ///
544 | /// Begins broadcasting the game to listening players.
545 | ///
546 | private async Task StartAdvertisingGameAsync(string name)
547 | {
548 | _manager.AdvertiserMessage = name;
549 | await _manager.StartAdvertisingAsync();
550 | await _managerCommunicationChannel.StartListeningAsync();
551 | }
552 |
553 | ///
554 | /// Stops broadcasting the game to listening players.
555 | ///
556 | private void StopAdvertisingGame() => _manager.StopAdvertising();
557 | }
558 | }
559 |
--------------------------------------------------------------------------------
/DemoApps/QuizGame/ViewModel/PlayerViewModel.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using NetworkHelper;
26 | using QuizGame.Common;
27 | using QuizGame.Model;
28 | using System;
29 | using System.Collections.ObjectModel;
30 | using System.Threading.Tasks;
31 | using Windows.ApplicationModel.Core;
32 | using Windows.UI.Core;
33 | using Windows.UI.Xaml;
34 |
35 | namespace QuizGame.ViewModel
36 | {
37 | public class PlayerViewModel : BindableBase
38 | {
39 | ///
40 | /// A static helper function to run code on the UI thread.
41 | ///
42 | private static Func callOnUiThread = async (handler)
43 | => await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, handler);
44 |
45 | ///
46 | /// Represents the participant.
47 | ///
48 | private UdpParticipant _participant = new UdpParticipant();
49 |
50 | ///
51 | /// The communication channel to listen for messages from the game host.
52 | ///
53 | private ICommunicationChannel _participantCommunicationChannel;
54 |
55 | ///
56 | /// The communication channel at which messages are sent
57 | ///
58 | private ICommunicationChannel _managerCommunicationChannel;
59 |
60 | private bool _isQuestionAnswered { get; set; }
61 |
62 | public PlayerViewModel()
63 | {
64 | // When a game host is found.
65 | _participant.ManagerFound += (async (sender, e) =>
66 | {
67 | var host = new GameHost() { Name = e.Message, Id = e.Id, CommChannel = _participant.CreateCommunicationChannel(e.Id) };
68 | await callOnUiThread(() => AvailableGames.Add(host));
69 | });
70 |
71 | _participantCommunicationChannel = new TcpCommunicationChannel();
72 |
73 | // When a new question is received from the game host.
74 | _participantCommunicationChannel.MessageReceived += (async (sender, e) =>
75 | {
76 | object message = new Question();
77 | e.GetDeserializedMessage(ref message);
78 | await callOnUiThread(() => CurrentQuestion = message as Question);
79 | });
80 | }
81 |
82 | ///
83 | /// The text that indicates the current state of the app displayed in the UI.
84 | ///
85 | public string StateName
86 | {
87 | get
88 | {
89 | return _stateName;
90 | }
91 | set
92 | {
93 | SetProperty(ref _stateName, value);
94 | }
95 | }
96 | private string _stateName;
97 |
98 | ///
99 | /// The name of the player.
100 | ///
101 | public string PlayerName
102 | {
103 | get
104 | {
105 | return _playerName ?? string.Empty;
106 | }
107 | set
108 | {
109 | if (SetProperty(ref _playerName, value))
110 | {
111 | JoinGameCommand.RaiseCanExecuteChanged();
112 | }
113 | }
114 | }
115 | private string _playerName;
116 |
117 | ///
118 | /// Indicates whether the player can join a game. Preconditions are that an available game has been
119 | /// selected and the player has entered a name.
120 | ///
121 | public bool CanJoin
122 | {
123 | get
124 | {
125 | return _canJoin && PlayerName != null && PlayerName.Length != 0 && SelectedGame != null;
126 | }
127 | set
128 | {
129 | if (SetProperty(ref _canJoin, value))
130 | {
131 | OnPropertyChanged(nameof(CanJoin));
132 | JoinGameCommand.RaiseCanExecuteChanged();
133 | }
134 | }
135 | }
136 | private bool _canJoin;
137 |
138 | ///
139 | /// Indicates whether a player has joined a game.
140 | ///
141 | public bool IsJoined
142 | {
143 | get
144 | {
145 | return _isJoined;
146 | }
147 | set
148 | {
149 | if (SetProperty(ref _isJoined, value))
150 | {
151 | OnPropertyChanged(nameof(JoinVisibility));
152 | OnPropertyChanged(nameof(GameUnderwayVisibility));
153 | OnPropertyChanged(nameof(QuestionAvailableVisibility));
154 | }
155 | StateName = _isJoined ? "stand by..." : "lobby";
156 | }
157 | }
158 | private bool _isJoined;
159 |
160 | ///
161 | /// The current question that the game host has sent.
162 | ///
163 | public Question CurrentQuestion
164 | {
165 | get
166 | {
167 | return _currentQuestion;
168 | }
169 | set
170 | {
171 | if (IsJoined && SetProperty(ref _currentQuestion, value))
172 | {
173 | OnPropertyChanged(nameof(QuestionAvailableVisibility));
174 | StateName = _currentQuestion == null ? "lobby" : "make a selection...";
175 | _isQuestionAnswered = false;
176 | AnswerQuestionCommand.RaiseCanExecuteChanged();
177 | }
178 | }
179 | }
180 | private Question _currentQuestion;
181 |
182 | ///
183 | /// The list of available game hosts.
184 | ///
185 | public ObservableCollection AvailableGames { get; } = new ObservableCollection();
186 |
187 | ///
188 | /// The game host that has been selected in the UI.
189 | ///
190 | public object SelectedGame
191 | {
192 | get
193 | {
194 | return _selectedGame;
195 | }
196 | set
197 | {
198 | if (SetProperty(ref _selectedGame, value))
199 | {
200 | OnPropertyChanged(nameof(SelectedGame));
201 | CanJoin = true;
202 | JoinGameCommand.RaiseCanExecuteChanged();
203 | }
204 | }
205 | }
206 | private object _selectedGame;
207 |
208 | ///
209 | /// The command associated with joining a game.
210 | ///
211 | public DelegateCommand JoinGameCommand
212 | {
213 | get
214 | {
215 | return _joinGameCommand ?? (_joinGameCommand = new DelegateCommand(
216 | async () => await JoinGameAsync(PlayerName, ((GameHost)SelectedGame).Id),
217 | () => CanJoin));
218 | }
219 | }
220 | private DelegateCommand _joinGameCommand;
221 |
222 | ///
223 | /// The command associated with leaving a game.
224 | ///
225 | public DelegateCommand LeaveGameCommand
226 | {
227 | get
228 | {
229 | return _leaveGameCommand ?? (_leaveGameCommand = new DelegateCommand(
230 | async () =>
231 | {
232 | await LeaveGameAsync(PlayerName);
233 | IsJoined = false;
234 | CurrentQuestion = null;
235 | }));
236 | }
237 | }
238 | private DelegateCommand _leaveGameCommand;
239 |
240 | ///
241 | /// The command associated with sending an answer to the game host.
242 | ///
243 | public DelegateCommand AnswerQuestionCommand
244 | {
245 | get
246 | {
247 | return _answerQuestionCommand ?? (_answerQuestionCommand = new DelegateCommand(
248 | async option =>
249 | {
250 | await AnswerQuestionAsync(PlayerName, Int32.Parse(option));
251 | _isQuestionAnswered = true;
252 | AnswerQuestionCommand.RaiseCanExecuteChanged();
253 | StateName = "thank you";
254 | },
255 | option => !_isQuestionAnswered));
256 | }
257 | }
258 | private DelegateCommand _answerQuestionCommand;
259 |
260 | ///
261 | /// Indicates whether the join game screen is visible in the UI.
262 | ///
263 | public Visibility JoinVisibility => !IsJoined ? Visibility.Visible : Visibility.Collapsed;
264 |
265 | ///
266 | /// Indicates whether the game underway screen in visible in the UI.
267 | ///
268 | public Visibility GameUnderwayVisibility => IsJoined ? Visibility.Visible : Visibility.Collapsed;
269 |
270 | ///
271 | /// Indicates whether the question screen is visible in the UI.
272 | ///
273 | public Visibility QuestionAvailableVisibility => IsJoined && CurrentQuestion != null ? Visibility.Visible : Visibility.Collapsed;
274 |
275 | ///
276 | /// Start listening for available games and messages from a game host.
277 | ///
278 | public async Task StartListeningAsync()
279 | {
280 | // Start listening for UDP advertisers.
281 | await _participant.StartListeningAsync();
282 |
283 | // Start listening for TCP messages.
284 | await _participantCommunicationChannel.StartListeningAsync();
285 | }
286 |
287 | ///
288 | /// Stop listening for available games and messages from a game host.
289 | ///
290 | public async Task StopListeningAsync()
291 | {
292 | _participant.StopListening();
293 | await _participantCommunicationChannel.StopListening();
294 | }
295 |
296 | ///
297 | /// Helper method to join a host game.
298 | ///
299 | private async Task JoinGameAsync(string playerName, Guid host)
300 | {
301 | _participant.ListenerMessage = playerName;
302 | await _participant.ConnectToManagerAsync(host);
303 | _managerCommunicationChannel = _participant.CreateCommunicationChannel(host);
304 |
305 | // Alert the ViewModel that the player has joined the game successfully.
306 | await callOnUiThread(() =>
307 | {
308 | IsJoined = true;
309 | });
310 | }
311 |
312 | ///
313 | /// Helper method to leave a game by sending a leave message to the joined game host.
314 | ///
315 | private async Task LeaveGameAsync(string playerName)
316 | {
317 | PlayerMessage command = new PlayerMessage()
318 | {
319 | Command = PlayerMessageType.Leave,
320 | PlayerName = playerName
321 | };
322 |
323 | await _managerCommunicationChannel
324 | .SendRemoteMessageAsync(command);
325 | }
326 |
327 | ///
328 | /// Helper method to send an answer to the joined game host for the current question.
329 | ///
330 | private async Task AnswerQuestionAsync(string playerName, int option)
331 | {
332 | PlayerMessage command = new PlayerMessage()
333 | {
334 | Command = PlayerMessageType.Answer,
335 | PlayerName = playerName,
336 | QuestionAnswer = option
337 | };
338 |
339 | await _managerCommunicationChannel
340 | .SendRemoteMessageAsync(command);
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Microsoft
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
23 |
24 |
--------------------------------------------------------------------------------
/NetworkHelper.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25123.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkHelper", "NetworkHelper\NetworkHelper.csproj", "{737229AE-D189-4765-B37B-31A326996A70}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DemoApps", "DemoApps", "{22973E4F-6A4C-4195-A64D-075136EF3704}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B38AD68C-E743-42A7-87D8-DD45AA490C17}"
11 | ProjectSection(SolutionItems) = preProject
12 | architecture.md = architecture.md
13 | CONTRIBUTING.md = CONTRIBUTING.md
14 | LICENSE = LICENSE
15 | README.md = README.md
16 | EndProjectSection
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuizGame", "DemoApps\QuizGame\QuizGame.csproj", "{E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}"
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|ARM = Debug|ARM
23 | Debug|x64 = Debug|x64
24 | Debug|x86 = Debug|x86
25 | Release|ARM = Release|ARM
26 | Release|x64 = Release|x64
27 | Release|x86 = Release|x86
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {737229AE-D189-4765-B37B-31A326996A70}.Debug|ARM.ActiveCfg = Debug|ARM
31 | {737229AE-D189-4765-B37B-31A326996A70}.Debug|ARM.Build.0 = Debug|ARM
32 | {737229AE-D189-4765-B37B-31A326996A70}.Debug|x64.ActiveCfg = Debug|x64
33 | {737229AE-D189-4765-B37B-31A326996A70}.Debug|x64.Build.0 = Debug|x64
34 | {737229AE-D189-4765-B37B-31A326996A70}.Debug|x86.ActiveCfg = Debug|x86
35 | {737229AE-D189-4765-B37B-31A326996A70}.Debug|x86.Build.0 = Debug|x86
36 | {737229AE-D189-4765-B37B-31A326996A70}.Release|ARM.ActiveCfg = Release|ARM
37 | {737229AE-D189-4765-B37B-31A326996A70}.Release|ARM.Build.0 = Release|ARM
38 | {737229AE-D189-4765-B37B-31A326996A70}.Release|x64.ActiveCfg = Release|x64
39 | {737229AE-D189-4765-B37B-31A326996A70}.Release|x64.Build.0 = Release|x64
40 | {737229AE-D189-4765-B37B-31A326996A70}.Release|x86.ActiveCfg = Release|x86
41 | {737229AE-D189-4765-B37B-31A326996A70}.Release|x86.Build.0 = Release|x86
42 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|ARM.ActiveCfg = Debug|ARM
43 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|ARM.Build.0 = Debug|ARM
44 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|ARM.Deploy.0 = Debug|ARM
45 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|x64.ActiveCfg = Debug|x64
46 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|x64.Build.0 = Debug|x64
47 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|x64.Deploy.0 = Debug|x64
48 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|x86.ActiveCfg = Debug|x86
49 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|x86.Build.0 = Debug|x86
50 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Debug|x86.Deploy.0 = Debug|x86
51 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|ARM.ActiveCfg = Release|ARM
52 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|ARM.Build.0 = Release|ARM
53 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|ARM.Deploy.0 = Release|ARM
54 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|x64.ActiveCfg = Release|x64
55 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|x64.Build.0 = Release|x64
56 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|x64.Deploy.0 = Release|x64
57 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|x86.ActiveCfg = Release|x86
58 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|x86.Build.0 = Release|x86
59 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F}.Release|x86.Deploy.0 = Release|x86
60 | EndGlobalSection
61 | GlobalSection(SolutionProperties) = preSolution
62 | HideSolutionNode = FALSE
63 | EndGlobalSection
64 | GlobalSection(NestedProjects) = preSolution
65 | {E6EEB9AE-9164-4C96-AEAE-BF87E30C1F3F} = {22973E4F-6A4C-4195-A64D-075136EF3704}
66 | EndGlobalSection
67 | EndGlobal
68 |
--------------------------------------------------------------------------------
/NetworkHelper/DnssdManager.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Linq;
27 | using System.Threading.Tasks;
28 | using Windows.Networking.ServiceDiscovery.Dnssd;
29 | using Windows.Networking;
30 | using Windows.Networking.Sockets;
31 | using Windows.Networking.Connectivity;
32 | using System.IO;
33 |
34 | namespace NetworkHelper
35 | {
36 | public class DnssdManager : SessionManager
37 | {
38 | ///
39 | /// The message that is received indicating a test.
40 | ///
41 | private const string TEST_MESSAGE = "connection_test";
42 |
43 | ///
44 | /// The default port to use when connecting back to the manager.
45 | ///
46 | private const string DEFAULT_PORT = "56788";
47 |
48 | ///
49 | /// The default instance name when registering the DNS-SD service.
50 | ///
51 | private const string INSTANCE_NAME = "DnssdManager";
52 |
53 | ///
54 | /// The network protocol that will be accepting connections for responses.
55 | ///
56 | private const string NETWORK_PROTOCOL = "_tcp";
57 |
58 | ///
59 | /// The domain of the DNS-SD registration.
60 | ///
61 | private const string DOMAIN = "local";
62 |
63 | ///
64 | /// The service type of the DNS-SD registration.
65 | ///
66 | private const string SERVICE_TYPE = "_p2phelper";
67 |
68 | ///
69 | /// The name of the DNS-SD service that is being registered. You can provide any name that you want.
70 | ///
71 | public string InstanceName { get; set; } = INSTANCE_NAME;
72 |
73 | ///
74 | /// The DNS-SD service object.
75 | ///
76 | private DnssdServiceInstance _service;
77 |
78 | ///
79 | /// The TCP socket that will be accepting connections for the DSN-SD service responses.
80 | ///
81 | private StreamSocketListener _socket;
82 |
83 | ///
84 | /// The port to use when connecting back to the host.
85 | ///
86 | public string Port { get; set; } = DEFAULT_PORT;
87 |
88 | ///
89 | /// Registers the DNS-SD service.
90 | ///
91 | public override async Task StartAdvertisingAsync()
92 | {
93 | if (_socket == null && _service == null)
94 | {
95 | _socket = new StreamSocketListener();
96 | _socket.ConnectionReceived += MessageToConnectReceivedFromParticipantAsync;
97 | await _socket.BindServiceNameAsync(Port);
98 |
99 | _service = new DnssdServiceInstance(
100 | $"{InstanceName}.{SERVICE_TYPE}.{NETWORK_PROTOCOL}.{DOMAIN}.",
101 | NetworkInformation.GetHostNames().FirstOrDefault(x => x.Type == HostNameType.DomainName && x.RawName.Contains("local")),
102 | UInt16.Parse(_socket.Information.LocalPort)
103 | );
104 |
105 | var operationStatus = await _service.RegisterStreamSocketListenerAsync(_socket);
106 |
107 | return true;
108 | }
109 |
110 | return false;
111 | }
112 |
113 | ///
114 | /// Unregisters and removes the DNS-SD service. If this method is not called,
115 | /// the registration will remain discoverable, even if the app is not running.
116 | ///
117 | public override bool StopAdvertising()
118 | {
119 | if (_socket != null && _service != null)
120 | {
121 | _socket.ConnectionReceived -= MessageToConnectReceivedFromParticipantAsync;
122 | _socket.Dispose();
123 | _socket = null;
124 | _service = null;
125 |
126 | return true;
127 | }
128 |
129 | return false;
130 | }
131 |
132 | ///
133 | /// Creates a TcpCommunicationChannel object and returns it so that app developers can send custom TCP messages to the manager.
134 | /// Returns a null remote host name in TcpCommunicationChannel object if the manager didn't exist.
135 | ///
136 | public override ICommunicationChannel CreateCommunicationChannel(Guid participant, int flags = 0)
137 | {
138 | TcpCommunicationChannel channel = new TcpCommunicationChannel();
139 | channel.RemoteHostname = ((DnssdParticipantInformation)Participants[participant]).Host;
140 | return channel;
141 | }
142 |
143 | ///
144 | /// When a new message is received, the participant is added to the list of Participants.
145 | ///
146 | private async void MessageToConnectReceivedFromParticipantAsync(
147 | StreamSocketListener sender,
148 | StreamSocketListenerConnectionReceivedEventArgs args)
149 | {
150 | var participant = new DnssdParticipantInformation { Host = args.Socket.Information.RemoteAddress };
151 |
152 | // Read the subscriber's message.
153 | using (var reader = new StreamReader(args.Socket.InputStream.AsStreamForRead()))
154 | {
155 | string message = await reader.ReadLineAsync();
156 |
157 | if (message != TEST_MESSAGE)
158 | {
159 | // Add the participant.
160 | base.AddParticipant(participant, message);
161 | }
162 | }
163 | }
164 | }
165 |
166 | public class DnssdParticipantInformation
167 | {
168 | public HostName Host { get; set; }
169 |
170 | public override bool Equals(object obj)
171 | {
172 | var objCast = obj as DnssdParticipantInformation;
173 | return objCast != null ? Host.IsEqual(objCast.Host) : false;
174 | }
175 |
176 | public override int GetHashCode() => Host.GetHashCode();
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/NetworkHelper/DnssdParticipant.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Collections.Generic;
27 | using System.IO;
28 | using System.Threading.Tasks;
29 | using Windows.Devices.Enumeration;
30 | using Windows.Networking;
31 | using Windows.Networking.Sockets;
32 |
33 | namespace NetworkHelper
34 | {
35 | public class DnssdParticipant : SessionParticipant
36 | {
37 | ///
38 | /// The message to send in TestConnectionAsync
39 | ///
40 | private const string TEST_MESSAGE = "connection_test";
41 |
42 | ///
43 | /// The network protocol that will be accepting connections for responses.
44 | ///
45 | private const string NETWORK_PROTOCOL = "_tcp";
46 |
47 | ///
48 | /// The domain of the DNS-SD registration.
49 | ///
50 | private const string DOMAIN = "local";
51 |
52 | ///
53 | /// The service type of the DNS-SD registration.
54 | ///
55 | private const string SERVICE_TYPE = "_p2phelper";
56 |
57 | ///
58 | /// The protocol ID that identifies DNS-SD.
59 | ///
60 | private const string PROTOCOL_GUID = "{4526e8c1-8aac-4153-9b16-55e86ada0e54}";
61 |
62 | ///
63 | /// The host name property.
64 | ///
65 | private const string HOSTNAME_PROPERTY = "System.Devices.Dnssd.HostName";
66 |
67 | ///
68 | /// The service name property.
69 | ///
70 | private const string SERVICENAME_PROPERTY = "System.Devices.Dnssd.ServiceName";
71 |
72 | ///
73 | /// The instance name property.
74 | ///
75 | private const string INSTANCENAME_PROPERTY = "System.Devices.Dnssd.InstanceName";
76 |
77 | ///
78 | /// The IP address property.
79 | ///
80 | private const string IPADDRESS_PROPERTY = "System.Devices.IpAddress";
81 |
82 | ///
83 | /// The port number property.
84 | ///
85 | private const string PORTNUMBER_PROPERTY = "System.Devices.Dnssd.PortNumber";
86 |
87 | ///
88 | /// All of the properties that will be returned when a DNS-SD instance has been found.
89 | ///
90 | private string[] _propertyKeys = new String[] {
91 | HOSTNAME_PROPERTY,
92 | SERVICENAME_PROPERTY,
93 | INSTANCENAME_PROPERTY,
94 | IPADDRESS_PROPERTY,
95 | PORTNUMBER_PROPERTY
96 | };
97 |
98 | ///
99 | /// The AQS filter string that is used to filter services to DNS-SD only.
100 | ///
101 | private string _aqsQueryString = $"System.Devices.AepService.ProtocolId:={PROTOCOL_GUID} AND " +
102 | $"System.Devices.Dnssd.Domain:=\"{DOMAIN}\" AND System.Devices.Dnssd.ServiceName:=\"{SERVICE_TYPE}.{NETWORK_PROTOCOL}\"";
103 |
104 | ///
105 | /// The device watcher that will be watching for DNS-SD connections.
106 | ///
107 | private DeviceWatcher _watcher;
108 |
109 | ///
110 | /// The message that will be sent when connecting to a manager.
111 | ///
112 | public string ListenerMessage { get; set; }
113 |
114 | ///
115 | /// Establishes a connection to a DNS-SD instance by connecting to it's TCP socket and sending a message.
116 | ///
117 | public override async Task ConnectToManagerAsync(Guid manager)
118 | {
119 | var subscription = base.Managers[manager] as DnssdManagerInformation;
120 |
121 | using (var socket = new StreamSocket())
122 | {
123 | await socket.ConnectAsync(subscription.Host, subscription.Port);
124 |
125 | using (var writer = new StreamWriter(socket.OutputStream.AsStreamForWrite()))
126 | {
127 | await writer.WriteLineAsync(ListenerMessage.ToString());
128 | await writer.FlushAsync();
129 | }
130 | }
131 | }
132 |
133 | ///
134 | /// Creates a TcpCommunicationChannel object for communication with the DNS-SD instance.
135 | ///
136 | public override ICommunicationChannel CreateCommunicationChannel(Guid manager, int flags = 0)
137 | {
138 | var managerCast = Managers[manager] as DnssdManagerInformation;
139 | return new TcpCommunicationChannel { RemoteHostname = managerCast == null ? null : managerCast.Host };
140 | }
141 |
142 | ///
143 | /// Uses a device watcher to monitor for available DSN-SD instances.
144 | ///
145 | public override async Task StartListeningAsync()
146 | {
147 | bool status = false;
148 |
149 | if (_watcher == null)
150 | {
151 | _watcher = DeviceInformation.CreateWatcher(
152 | _aqsQueryString,
153 | _propertyKeys,
154 | DeviceInformationKind.AssociationEndpointService);
155 |
156 | _watcher.Added += async (s, a) => await OnFoundDnssdServiceAsync(a.Properties);
157 | _watcher.Updated += async (s, a) => await OnFoundDnssdServiceAsync(a.Properties);
158 | _watcher.Start();
159 |
160 | status = true;
161 | }
162 |
163 | await Task.CompletedTask;
164 | return status;
165 | }
166 |
167 | ///
168 | /// Stops watching for available DNS-SD instances.
169 | ///
170 | public override bool StopListening()
171 | {
172 | if (_watcher != null)
173 | {
174 | _watcher.Stop();
175 | _watcher = null;
176 |
177 | return true;
178 | }
179 |
180 | return false;
181 | }
182 |
183 | ///
184 | /// Adds the manager to the list of Managers, when a DNS-SD instance is found.
185 | ///
186 | private async Task OnFoundDnssdServiceAsync(IReadOnlyDictionary properties)
187 | {
188 | var host = new HostName((properties[IPADDRESS_PROPERTY] as String[])[0]);
189 | var port = properties[PORTNUMBER_PROPERTY].ToString();
190 |
191 | bool isValidConnection = await TestConnectionAsync(host, port);
192 |
193 | if (isValidConnection)
194 | {
195 | base.AddManager(
196 | new DnssdManagerInformation { Host = host, Port = port },
197 | properties[INSTANCENAME_PROPERTY].ToString()
198 | );
199 | }
200 | }
201 |
202 | ///
203 | /// Attempts to connect and send a test message to a TCP host for verification the host exists.
204 | ///
205 | private async Task TestConnectionAsync(HostName host, string port)
206 | {
207 | using (var socket = new StreamSocket())
208 | {
209 | try
210 | {
211 | await socket.ConnectAsync(host, port);
212 |
213 | using (var writer = new StreamWriter(socket.OutputStream.AsStreamForWrite()))
214 | {
215 | await writer.WriteLineAsync(TEST_MESSAGE);
216 | await writer.FlushAsync();
217 | }
218 | }
219 | catch (Exception)
220 | {
221 | return false;
222 | }
223 | }
224 |
225 | return true;
226 | }
227 | }
228 |
229 | public class DnssdManagerInformation
230 | {
231 | public HostName Host { get; set; }
232 | public string Port { get; set; }
233 |
234 | public override bool Equals(object obj)
235 | {
236 | var objCast = obj as DnssdManagerInformation;
237 | return objCast != null ? Host.IsEqual(objCast.Host) && Port == objCast.Port : false;
238 | }
239 |
240 | public override int GetHashCode() => (Host + Port).GetHashCode();
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/NetworkHelper/ICommunicationChannel.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Collections.Generic;
27 | using System.IO;
28 | using System.Linq;
29 | using System.Runtime.Serialization.Json;
30 | using System.Text;
31 | using System.Threading.Tasks;
32 |
33 | namespace NetworkHelper
34 | {
35 | public interface ICommunicationChannel
36 | {
37 | ///
38 | /// An event handler that indicates a message was received.
39 | ///
40 | event EventHandler MessageReceived;
41 |
42 | ///
43 | /// Sends a serialized object to the remote.
44 | ///
45 | Task SendRemoteMessageAsync(object message);
46 |
47 | ///
48 | /// Starts listening for messages.
49 | ///
50 | Task StartListeningAsync();
51 |
52 | ///
53 | /// Stops listening for messages.
54 | ///
55 | Task StopListening();
56 | }
57 |
58 | public interface IMessageReceivedEventArgs
59 | {
60 | void GetDeserializedMessage(ref object message);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/NetworkHelper/ISessionManager.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Threading.Tasks;
27 |
28 | namespace NetworkHelper
29 | {
30 | public interface ISessionManager
31 | {
32 | ///
33 | /// An event that indicates that a participant has been connected.
34 | ///
35 | event EventHandler ParticipantConnected;
36 |
37 | ///
38 | /// Start advertising.
39 | ///
40 | Task StartAdvertisingAsync();
41 |
42 | ///
43 | /// Stop advertising.
44 | ///
45 | bool StopAdvertising();
46 |
47 | ///
48 | /// Creates an ICommunicationChannel object and returns it so that app developers can send custom messages to the participant.
49 | ///
50 | ICommunicationChannel CreateCommunicationChannel(Guid participant, int flags = 0);
51 |
52 | ///
53 | /// Removes a participant from a participants list.
54 | ///
55 | bool RemoveParticipant(Guid participant);
56 | }
57 |
58 | public class ParticipantConnectedEventArgs : EventArgs
59 | {
60 | public Guid Id { get; set; }
61 |
62 | public object Message { get; set; }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/NetworkHelper/ISessionParticipant.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Collections.Generic;
27 | using System.Linq;
28 | using System.Text;
29 | using System.Threading.Tasks;
30 |
31 | namespace NetworkHelper
32 | {
33 | public interface ISessionParticipant
34 | {
35 | ///
36 | /// An event indicating that a manager has been found.
37 | ///
38 | event EventHandler ManagerFound;
39 |
40 | ///
41 | /// Start listening for managers.
42 | ///
43 | Task StartListeningAsync();
44 |
45 | ///
46 | /// Stop listening for managers.
47 | ///
48 | bool StopListening();
49 |
50 | ///
51 | /// Connect to a manager that has already been found.
52 | ///
53 | Task ConnectToManagerAsync(Guid manager);
54 |
55 | ///
56 | /// Creates and returns an ICommunicationChannel object so that app developers can have direct communication with the manager and send custom messages.
57 | ///
58 | ICommunicationChannel CreateCommunicationChannel(Guid manager, int flags = 0);
59 |
60 | ///
61 | /// Removes a manager from the list of available managers.
62 | ///
63 | bool RemoveManager(Guid manager);
64 | }
65 |
66 | public class ManagerFoundEventArgs
67 | {
68 | public Guid Id { get; set; }
69 | public string Message { get; set; }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/NetworkHelper/NetworkHelper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {737229AE-D189-4765-B37B-31A326996A70}
8 | Library
9 | Properties
10 | NetworkHelper
11 | NetworkHelper
12 | en-US
13 | UAP
14 | 10.0.17763.0
15 | 10.0.17763.0
16 | 14
17 | 512
18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
19 |
20 |
21 | ARM
22 | true
23 | bin\ARM\Debug\
24 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
25 | ;2008
26 | full
27 | ARM
28 | false
29 | prompt
30 | true
31 |
32 |
33 | ARM
34 | bin\ARM\Release\
35 | TRACE;NETFX_CORE;WINDOWS_UAP
36 | true
37 | ;2008
38 | pdbonly
39 | ARM
40 | false
41 | prompt
42 | true
43 |
44 |
45 | x64
46 | true
47 | bin\x64\Debug\
48 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
49 | ;2008
50 | full
51 | x64
52 | false
53 | prompt
54 | true
55 |
56 |
57 | x64
58 | bin\x64\Release\
59 | TRACE;NETFX_CORE;WINDOWS_UAP
60 | true
61 | ;2008
62 | pdbonly
63 | x64
64 | false
65 | prompt
66 | true
67 |
68 |
69 | x86
70 | true
71 | bin\x86\Debug\
72 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
73 | ;2008
74 | full
75 | x86
76 | false
77 | prompt
78 | true
79 |
80 |
81 | x86
82 | bin\x86\Release\
83 | TRACE;NETFX_CORE;WINDOWS_UAP
84 | true
85 | ;2008
86 | pdbonly
87 | x86
88 | false
89 | prompt
90 | true
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | 14.0
110 |
111 |
112 |
119 |
--------------------------------------------------------------------------------
/NetworkHelper/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("NetworkHelper")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("NetworkHelper")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Version information for an assembly consists of the following four values:
18 | //
19 | // Major Version
20 | // Minor Version
21 | // Build Number
22 | // Revision
23 | //
24 | // You can specify all the values or you can default the Build and Revision Numbers
25 | // by using the '*' as shown below:
26 | // [assembly: AssemblyVersion("1.0.*")]
27 | [assembly: AssemblyVersion("1.0.0.0")]
28 | [assembly: AssemblyFileVersion("1.0.0.0")]
29 | [assembly: ComVisible(false)]
--------------------------------------------------------------------------------
/NetworkHelper/Properties/NetworkHelper.rd.xml:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/NetworkHelper/SessionManager.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Collections.Generic;
27 | using System.Linq;
28 | using System.Text;
29 | using System.Threading.Tasks;
30 |
31 | namespace NetworkHelper
32 | {
33 | public abstract class SessionManager : ISessionManager
34 | {
35 | public Dictionary Participants { get; set; } = new Dictionary();
36 |
37 | public event EventHandler ParticipantConnected = delegate { };
38 |
39 | public abstract Task StartAdvertisingAsync();
40 |
41 | public abstract bool StopAdvertising();
42 |
43 | public abstract ICommunicationChannel CreateCommunicationChannel(Guid participant, int flags);
44 |
45 | public bool RemoveParticipant(Guid subscriber) => Participants.Remove(subscriber);
46 |
47 | ///
48 | /// Adds a participant.
49 | ///
50 | protected void AddParticipant(object participant, string participantMessage)
51 | {
52 | // Add the participant if it isn't already in the list of Participants.
53 | if (!Participants.Values.Contains(participant))
54 | {
55 | // Generate a new GUID so that app developers can reference the participant.
56 | var guid = Guid.NewGuid();
57 | Participants.Add(guid, participant);
58 |
59 | // Notify that ParticipantConnected event handlers so app developers are aware when a new participant has been connected.
60 | ParticipantConnected(this, new ParticipantConnectedEventArgs { Id = guid, Message = participantMessage});
61 | }
62 | }
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/NetworkHelper/SessionParticipant.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Collections.Concurrent;
27 | using System.Collections.Generic;
28 | using System.Threading.Tasks;
29 |
30 | namespace NetworkHelper
31 | {
32 | public abstract class SessionParticipant : ISessionParticipant
33 | {
34 | ///
35 | /// The managers that are available.
36 | ///
37 | public ConcurrentDictionary Managers { get; set; } = new ConcurrentDictionary();
38 |
39 | public event EventHandler ManagerFound = delegate { };
40 |
41 | public abstract Task StartListeningAsync();
42 |
43 | public abstract bool StopListening();
44 |
45 | public abstract Task ConnectToManagerAsync(Guid manager);
46 |
47 | public abstract ICommunicationChannel CreateCommunicationChannel(Guid manager, int flags);
48 |
49 | public bool RemoveManager(Guid manager)
50 | {
51 | object obj;
52 | return Managers.TryRemove(manager, out obj);
53 | }
54 |
55 | ///
56 | /// Adds a manager to the AvailableManagers list.
57 | ///
58 | protected void AddManager(object manager, string managerMessage)
59 | {
60 | // Add the manager to the list of Managers if it's not already in the list.
61 | if (!Managers.Values.Contains(manager))
62 | {
63 | // Generate a new GUID, so that app developers can reference this particular manager.
64 | var guid = Guid.NewGuid();
65 | bool isAdded = Managers.TryAdd(guid, manager);
66 |
67 | if (isAdded)
68 | {
69 | // Notify that ManagerFound handlers so the app developers are aware of a new Manager.
70 | ManagerFound(this, new ManagerFoundEventArgs { Id = guid, Message = managerMessage });
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/NetworkHelper/TcpCommunicationChannel.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.IO;
27 | using System.Runtime.Serialization.Json;
28 | using System.Threading.Tasks;
29 | using Windows.Networking.Sockets;
30 | using Windows.Storage.Streams;
31 |
32 | namespace NetworkHelper
33 | {
34 | public class TcpCommunicationChannel : ICommunicationChannel
35 | {
36 | ///
37 | /// The default port.
38 | /// This port was chosen randomly in the ephemeral port range.
39 | ///
40 | private const string TCP_COMMUNICATION_PORT = "56789";
41 |
42 | ///
43 | /// The socket connection to the remote TCP server.
44 | ///
45 | private StreamSocket _remoteSocket;
46 |
47 | ///
48 | /// The local socket connection that will be listening for TCP messages.
49 | ///
50 | private StreamSocketListener _localSocket;
51 |
52 | ///
53 | /// The port number that the remote TCP server and local TCP server is listening to.
54 | ///
55 | public string CommunicationPort { get; set; } = TCP_COMMUNICATION_PORT;
56 |
57 | ///
58 | /// The hostname of the remote TCP server that is listeneing for TCP connections.
59 | ///
60 | public Windows.Networking.HostName RemoteHostname { get; set; }
61 |
62 | ///
63 | /// An event indicating that a message was received from the remote TCP server.
64 | ///
65 | public event EventHandler MessageReceived = delegate { };
66 |
67 | ///
68 | /// Serializes the data object and sends it to the connected RemoteSocket.
69 | /// For more information on making an object serializable, see DataContractJsonSerializer.
70 | ///
71 | public async Task SendRemoteMessageAsync(object data)
72 | {
73 | // Connect to the remote host to ensure that the connection exists.
74 | await ConnectToRemoteAsync();
75 |
76 | using (var writer = new DataWriter(_remoteSocket.OutputStream))
77 | {
78 | byte[] serializedData = SerializeData(data);
79 | byte[] serializedDataLength = BitConverter.GetBytes(serializedData.Length);
80 |
81 | writer.WriteBytes(serializedDataLength);
82 | writer.WriteBytes(serializedData);
83 |
84 | await writer.StoreAsync();
85 | await writer.FlushAsync();
86 | }
87 |
88 | // Disconnect from the remote host.
89 | DisconnectFromRemote();
90 | }
91 |
92 | ///
93 | /// Creates a TCP socket and binds to the CommunicationPort.
94 | /// Use the default JsonSerializer if null is passed into the serializer parameter.
95 | /// Returns false if you are already listening.
96 | ///
97 | public async Task StartListeningAsync()
98 | {
99 | if (_localSocket == null)
100 | {
101 | _localSocket = new StreamSocketListener();
102 | _localSocket.ConnectionReceived += LocalSocketConnectionReceived;
103 | await _localSocket.BindServiceNameAsync(CommunicationPort);
104 | return true;
105 | }
106 |
107 | return false;
108 | }
109 |
110 | ///
111 | /// Disposes of the TCP socket and sets it to null. Returns false if
112 | /// you weren't listening.
113 | ///
114 | public async Task StopListening()
115 | {
116 | if (_localSocket != null)
117 | {
118 | await _localSocket.CancelIOAsync();
119 | _localSocket.ConnectionReceived -= LocalSocketConnectionReceived;
120 | _localSocket.Dispose();
121 | _localSocket = null;
122 | return true;
123 | }
124 |
125 | return false;
126 | }
127 |
128 | ///
129 | /// The event handler for when a TCP connection has been received.
130 | ///
131 | private async void LocalSocketConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
132 | {
133 | using (var reader = new DataReader(args.Socket.InputStream))
134 | {
135 | reader.InputStreamOptions = InputStreamOptions.None;
136 |
137 | // Read the length of the payload that will be received.
138 | byte[] payloadSize = new byte[(uint)BitConverter.GetBytes(0).Length];
139 | await reader.LoadAsync((uint)payloadSize.Length);
140 | reader.ReadBytes(payloadSize);
141 |
142 | // Read the payload.
143 | int size = BitConverter.ToInt32(payloadSize, 0);
144 | byte[] payload = new byte[size];
145 | await reader.LoadAsync((uint)size);
146 | reader.ReadBytes(payload);
147 |
148 | // Notify subscribers that a message was received.
149 | MessageReceived(this, new TcpMessageReceivedEventArgs { Message = payload });
150 | }
151 | }
152 |
153 | ///
154 | /// Creates a RemoteSocket and establishes a connection to the RemoteHostname on CommunicationPort.
155 | ///
156 | private async Task ConnectToRemoteAsync()
157 | {
158 | _remoteSocket = _remoteSocket ?? new StreamSocket();
159 | await _remoteSocket.ConnectAsync(RemoteHostname, CommunicationPort);
160 | }
161 |
162 | ///
163 | /// Serializes an object by using DataContractJsonSerializer.
164 | ///
165 | private byte[] SerializeData(object data)
166 | {
167 | using (var stream = new MemoryStream())
168 | {
169 | new DataContractJsonSerializer(data.GetType()).WriteObject(stream, data);
170 | return stream.ToArray();
171 | }
172 | }
173 |
174 | ///
175 | /// Disposes the RemoteSocket.
176 | ///
177 | private void DisconnectFromRemote()
178 | {
179 | _remoteSocket.Dispose();
180 | _remoteSocket = null;
181 | }
182 | }
183 |
184 | ///
185 | /// The event args that contain the message.
186 | ///
187 | public class TcpMessageReceivedEventArgs : EventArgs, IMessageReceivedEventArgs
188 | {
189 | public byte[] Message { get; set; }
190 |
191 | ///
192 | /// Deserializes Message by using the DataContractJsonSerializer.
193 | ///
194 | void IMessageReceivedEventArgs.GetDeserializedMessage(ref object message)
195 | {
196 | using (var stream = new MemoryStream(Message))
197 | {
198 | message = new DataContractJsonSerializer(message.GetType()).ReadObject(stream);
199 | }
200 | }
201 |
202 | ///
203 | /// Converts the Message to a string.
204 | ///
205 | public override string ToString()
206 | {
207 | return System.Text.Encoding.ASCII.GetString(Message);
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/NetworkHelper/UdpManager.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.IO;
27 | using System.Threading;
28 | using System.Threading.Tasks;
29 | using Windows.Networking;
30 | using Windows.Networking.Sockets;
31 |
32 | namespace NetworkHelper
33 | {
34 | public class UdpManager : SessionManager
35 | {
36 | ///
37 | /// The default timing interval (in milliseconds) to send an advertising message.
38 | ///
39 | private const int ADVERTISING_INTERVAL = 500;
40 |
41 | ///
42 | /// The default message to send when advertising.
43 | ///
44 | private const string ADVERTISING_MESSAGE = "Advertiser";
45 |
46 | ///
47 | /// The default port to use when advertising for UDP multicast messages.
48 | /// This port was chosen randomly in the ephemeral port range.
49 | ///
50 | private const string UDP_COMMUNICATION_PORT = "56788";
51 |
52 | ///
53 | /// The default IP to use when advertising UDP multicast messages.
54 | /// This IP was chosen randomly as part of the multicast IP range.
55 | ///
56 | private const string UDP_MULTICAST_IP = "237.1.3.37";
57 |
58 | ///
59 | /// The timer that will cause the advertiser to send a UDP multicast message every AdvertisingInterval milliseconds.
60 | ///
61 | private Timer _timer;
62 |
63 | ///
64 | /// The socket of the Advertiser.
65 | ///
66 | public DatagramSocket AdvertiserSocket { get; set; }
67 |
68 | ///
69 | /// The port that the Advertiser will be sending UDP messages to and accept message from.
70 | ///
71 | public string AdvertiserPort { get; set; } = UDP_COMMUNICATION_PORT;
72 |
73 | ///
74 | /// The multicast group that the Advertiser will be sending UDP messages to.
75 | ///
76 | public HostName AdvertiserGroupHost { get; set; } = new HostName(UDP_MULTICAST_IP);
77 |
78 | ///
79 | /// The message that will be sent when advertising.
80 | ///
81 | public string AdvertiserMessage { get; set; } = ADVERTISING_MESSAGE;
82 |
83 | ///
84 | /// The timing interval (in milliseconds) to send an advertising message.
85 | ///
86 | public int AdvertiserInterval { get; set; } = ADVERTISING_INTERVAL;
87 |
88 | ///
89 | /// Creates a new UDP socket and starts advertising the AdvertisingMessage to the AdvertiserPort and AdvertiserGroupHost
90 | ///
91 | public override async Task StartAdvertisingAsync()
92 | {
93 | if (AdvertiserSocket == null)
94 | {
95 | AdvertiserSocket = new DatagramSocket();
96 |
97 | // Attach event handler and start listening on given port.
98 | AdvertiserSocket.MessageReceived += MessageToConnectReceivedFromParticipantAsync;
99 | await AdvertiserSocket.BindServiceNameAsync(AdvertiserPort);
100 |
101 | // Start the timer, to send a message every X milliseconds.
102 | _timer = new Timer(async state => await SendMessageAsync(), null, 0, AdvertiserInterval);
103 |
104 | return true;
105 | }
106 |
107 | return false;
108 | }
109 |
110 | public override bool StopAdvertising()
111 | {
112 | if (AdvertiserSocket != null)
113 | {
114 | AdvertiserSocket.MessageReceived -= MessageToConnectReceivedFromParticipantAsync;
115 | AdvertiserSocket.Dispose();
116 | AdvertiserSocket = null;
117 | _timer.Dispose();
118 | return true;
119 | }
120 |
121 | return false;
122 | }
123 |
124 | ///
125 | /// Creates a TcpCommunicationChannel object and returns it so that app developers can send custom TCP messages to the manager.
126 | /// Returns a null remote host name in TcpCommunicationChannel object if the manager didn't exist.
127 | ///
128 | public override ICommunicationChannel CreateCommunicationChannel(Guid participant, int flags = 0)
129 | {
130 | TcpCommunicationChannel channel = new TcpCommunicationChannel();
131 | channel.RemoteHostname = ((UdpParticipantInformation)Participants[participant]).Host;
132 | return channel;
133 | }
134 |
135 | ///
136 | /// The private method that sends an "advertising" message to the multicast group.
137 | ///
138 | private async Task SendMessageAsync()
139 | {
140 | // Connect to a multicast group IP and send a message to the group.
141 | Stream outStream = (await AdvertiserSocket.GetOutputStreamAsync(AdvertiserGroupHost, AdvertiserPort)).AsStreamForWrite();
142 |
143 | using (var writer = new StreamWriter(outStream))
144 | {
145 | await writer.WriteLineAsync(AdvertiserMessage);
146 | await writer.FlushAsync();
147 | }
148 | }
149 |
150 | ///
151 | /// When a new message is received, the participant is added to the list of Participants.
152 | ///
153 | private async void MessageToConnectReceivedFromParticipantAsync(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
154 | {
155 | var participant = new UdpParticipantInformation { Host = args.RemoteAddress };
156 |
157 | // Read the subscriber's message.
158 | using (var reader = new StreamReader(args.GetDataStream().AsStreamForRead()))
159 | {
160 | string message = await reader.ReadLineAsync();
161 |
162 | // Add the participant.
163 | base.AddParticipant(participant, message);
164 | }
165 | }
166 |
167 | }
168 |
169 | public class UdpParticipantInformation
170 | {
171 | public HostName Host { get; set; }
172 |
173 | public override bool Equals(object obj)
174 | {
175 | var objCast = obj as UdpParticipantInformation;
176 | return objCast != null ? Host.IsEqual(objCast.Host) : false;
177 | }
178 |
179 | public override int GetHashCode() => Host.GetHashCode();
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/NetworkHelper/UdpParticipant.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Collections.Generic;
27 | using System.IO;
28 | using System.Linq;
29 | using System.Text;
30 | using System.Threading.Tasks;
31 | using Windows.Networking;
32 | using Windows.Networking.Sockets;
33 |
34 | namespace NetworkHelper
35 | {
36 | public class UdpParticipant : SessionParticipant
37 | {
38 | ///
39 | /// The default port to use when listening for UDP messages.
40 | /// This port was chosen randomly in the ephemeral port range.
41 | ///
42 | private const string UDP_COMMUNICATION_PORT = "56788";
43 |
44 | ///
45 | /// The default IP to use when listening for multicast messages.
46 | /// This IP was chosen randomly as part of the multicast IP range.
47 | ///
48 | private const string UDP_MULTICAST_IP = "237.1.3.37";
49 |
50 | ///
51 | /// The datagram socket that will be listening for incoming advertiser messages
52 | ///
53 | private DatagramSocket _listenerSocket;
54 |
55 | ///
56 | /// The port that the listener will be listening for UDP messages to and accept message from.
57 | ///
58 | public string ListenerPort { get; set; } = UDP_COMMUNICATION_PORT;
59 |
60 | ///
61 | /// The multicast group that the listener will be sending UDP messages to.
62 | ///
63 | public HostName ListenerGroupHost { get; set; } = new HostName(UDP_MULTICAST_IP);
64 |
65 | ///
66 | /// The message that will be sent when connecting to a manager.
67 | ///
68 | public String ListenerMessage { get; set; }
69 |
70 | ///
71 | /// Start listening.
72 | ///
73 | public override async Task StartListeningAsync()
74 | {
75 | bool status = false;
76 |
77 | if (_listenerSocket == null)
78 | {
79 | _listenerSocket = new DatagramSocket();
80 | _listenerSocket.MessageReceived += AdvertisementMessageReceivedFromManagerAsync;
81 | await _listenerSocket.BindServiceNameAsync(ListenerPort);
82 | _listenerSocket.JoinMulticastGroup(ListenerGroupHost);
83 |
84 | status = true;
85 | }
86 |
87 | return status;
88 | }
89 |
90 | ///
91 | /// Stop listening for subscriptions.
92 | ///
93 | public override bool StopListening()
94 | {
95 | bool status = false;
96 |
97 | if (_listenerSocket != null)
98 | {
99 | _listenerSocket.Dispose();
100 | _listenerSocket = null;
101 |
102 | status = true;
103 | }
104 |
105 | return status;
106 | }
107 |
108 | ///
109 | /// Sends a UDP message to the advertiser which indicates a join.
110 | ///
111 | public async override Task ConnectToManagerAsync(Guid manager)
112 | {
113 | var subscription = base.Managers[manager] as UdpManagerInformation;
114 |
115 | var outStream = (await _listenerSocket.GetOutputStreamAsync(subscription.Host, ListenerPort)).AsStreamForWrite();
116 |
117 | using (var writer = new StreamWriter(outStream))
118 | {
119 | await writer.WriteLineAsync(ListenerMessage);
120 | await writer.FlushAsync();
121 | }
122 | }
123 |
124 | ///
125 | /// Creates a TcpCommunicationChannel object and returns it so that app developers can send custom TCP messages to the manager.
126 | /// Returns a null remote host name in TcpCommunicationChannel object if the manager didn't exist.
127 | ///
128 | public override ICommunicationChannel CreateCommunicationChannel(Guid manager, int flags = 0)
129 | {
130 | var managerCast = Managers[manager] as UdpManagerInformation;
131 | return new TcpCommunicationChannel { RemoteHostname = managerCast == null ? null : managerCast.Host };
132 | }
133 |
134 | ///
135 | /// Adds the manager to the list of Managers, when a UDP advertisement message is received..
136 | ///
137 | private async void AdvertisementMessageReceivedFromManagerAsync(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
138 | {
139 | // Read the advertisement message.
140 | using (var reader = new StreamReader(args.GetDataStream().AsStreamForRead()))
141 | {
142 | string message = await reader.ReadLineAsync();
143 |
144 | // Add the manager to the list of Managers.
145 | base.AddManager(new UdpManagerInformation { Host = args.RemoteAddress }, message);
146 | }
147 | }
148 | }
149 |
150 | public class UdpManagerInformation
151 | {
152 | public HostName Host { get; set; }
153 |
154 | public override bool Equals(object obj)
155 | {
156 | var objCast = obj as UdpManagerInformation;
157 | return objCast != null ? Host.IsEqual(objCast.Host) : false;
158 | }
159 |
160 | public override int GetHashCode() => Host.GetHashCode();
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/NetworkHelper/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0"
4 | },
5 | "frameworks": {
6 | "uap10.0": {}
7 | },
8 | "runtimes": {
9 | "win10-arm": {},
10 | "win10-arm-aot": {},
11 | "win10-x86": {},
12 | "win10-x86-aot": {},
13 | "win10-x64": {},
14 | "win10-x64-aot": {}
15 | }
16 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | languages:
4 | - csharp
5 | products:
6 | - windows
7 | - windows-uwp
8 | description: "A library and mini-app that show how to use UWP networking APIs to enable network discovery and communication in your games or apps."
9 | statusNotificationTargets:
10 | - codefirst@microsoft.com
11 | ---
12 |
13 |
16 |
17 | # NetworkHelper sample library
18 |
19 | A sample library and demo mini-app that shows how to use UWP networking APIs to enable network discovery and communication in your games or apps. This sample runs on the Universal Windows Platform (UWP). Specifically, the library provides the ability for:
20 |
21 | - Wi-Fi network discovery and management peer clients over a local Wi-Fi network.
22 | - Direct communication between discovered devices on the same Wi-Fi network with developer configurable messages.
23 |
24 | ## Running the sample
25 |
26 | To run the sample, you need the latest version of Windows 10 installed on your target and development machines. For more information about Windows 10 see the [Windows 10 Upgrade](https://go.microsoft.com/fwlink/p/?LinkId=619312) page.
27 |
28 | To build this sample, you need [Visual Studio 2017 and the latest version of the Windows 10 SDK](http://go.microsoft.com/fwlink/?LinkID=280676). You can use the free Visual Studio Community Edition to build and run Windows Universal Platform (UWP) apps. To get the latest updates to Windows and the development tools, and to help shape their development, join
29 | the [Windows Insider Program](https://insider.windows.com).
30 |
31 | After you have the latest version of Windows 10 and the development tools installed on your machine, you are ready to run the sample in Visual Studio. To do this you need to set the StartUp Project to one of the Demo Apps in the DemoApps folder in Visual Studio. Below are instructions for setting QuizGame as the start up project.
32 |
33 | 1. Open the NetworkHelper solution in Visual Studio
34 | 2. In the Solution Explorer, right-click the QuizGame project, then select *Set as StartUp Project*.
35 | 
36 | 3. You can now deploy to two different devices and Start Debugging (F5) or Start Without Debugging (Ctrl+F5) to try the sample out.
37 |
38 | **Important Note** This sample is supposed to run on two separate devices on the same local network. To get a full experience, deploy this sample to more than one device and make sure they are on the same Wi-Fi network. If you are running into any issues running this sample, make sure that you have started QuizGame on another device and started a new game. If this doesn't work, ensure that your Wi-Fi router or local network system allows UDP multicast packets to be sent and received. Some networks do not allow network discovery by blocking UDP multicast packets.
39 |
40 | ## Demo App
41 |
42 | **QuizGame** is a Universal Windows Platform (UWP) app sample that uses the NetworkHelper library to enable a pub-style trivia game. When running the sample, you are presented with an option to create a new game as a quiz master on your local network or join an existing game and answer questions from the quiz master. Questions appear on the quiz master's screen while players answer the questions on their own devices. A quiz master can advance the game to additional questions and display the scores at the end. Below are some features that QuizGame uses.
43 | - Simple MVVM (Model, View, ViewModel) coding pattern
44 | - Simple navigation using code-behind files.
45 | - NetworkHelper to enable discovery and broadcasting of available games on the local network.
46 | - NetworkHelper to enable communication between the game and players.
47 |
48 | 
49 | 
50 | 
51 | 
52 |
53 | ## Code at a glance
54 |
55 | If you’re just interested in code snippets for certain API and don’t want to browse or run the full sample, check out the following files for examples of some highlighted features:
56 |
57 | Below are the details of the NetworkHelper library interface.
58 | * [ICommunicationChannel.cs](NetworkHelper/ICommunicationChannel.cs#L33), [ISessionManager.cs](NetworkHelper/ISessionManager.cs#L33), [ISessionParticipant.cs](NetworkHelper/ISessionParticipant.cs#L33), [SessionManager.cs](NetworkHelper/SessionManager.cs#L33), and [SessionParticipant.cs](NetworkHelper/SessionParticipant.cs#L33):
59 | - These interfaces represent a generic peer-to-peer helper architecture that provides the framework for implementing various network protocols.
60 | - The ISessionManager interface and SessionManager abstract class define how a specific protocol can advertise itself to listening participants, manage connected participants, and provide direct communication to connected participants.
61 | - The ISessionParticipant interface and SessionParticipant abstract class define how a specific protocol can listen for managers, establish a connection to a manager, and communicate directly with managers.
62 | - The ICommunicationChannel interface defines how message are sent and received between managers and participants.
63 |
64 | Below are the details of the TCP/UDP and DNS-SD implementations of the NetworkHelper library interface.
65 | * [TcpCommunicationChannel.cs](NetworkHelper/TcpCommunicationChannel.cs#L35), [UdpManager.cs](NetworkHelper/UdpManager.cs#L37), [UdpParticipant.cs](NetworkHelper/UdpParticipant.cs#L37), [DnsSdManager.cs](NetworkHelper/DnssdManager.cs#L36), and [DnsSdParticipant.cs](NetworkHelper/DnssdParticipant.cs#L35):
66 | - These classes are a concrete implementation of the interfaces and abstract classes described above for the TCP/UDP network protocol.
67 | - The UdpManager class broadcasts UDP messages to listening participants, listens for participant connection requests, and generates TcpCommunicationChannel objects for sending messages to connected participants.
68 | - The DnsSdManager class registers a DNS-SD instance to listening participants, listens for participant connection requests, and generates TcpCommunicationChannel objects for sending messages to connected participants.
69 | - The UdpParticipant class listens for UDP messages broadcast by managers, sends UDP connection requests to managers, and generates TcpCommunicationChannel objects for sending messages to managers.
70 | - The DnsSdParticipant class listens for registered DNS-SD instances, sends UDP connection requests to managers, and generates TcpCommunicationChannel objects for sending messages to managers.
71 | - The TcpCommunicationChannel class listens for TCP connections and sends TCP messages.
72 | - You can use the NetworkHelper library as-is in your projects, either directly, or through an adapter to keep it decoupled. (See the Communicator types below for an example of an adapter.)
73 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Screenshots/QuizGame_CreateGame_Lobby_scaled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/Screenshots/QuizGame_CreateGame_Lobby_scaled.png
--------------------------------------------------------------------------------
/Screenshots/QuizGame_GameInProgress_scaled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/Screenshots/QuizGame_GameInProgress_scaled.png
--------------------------------------------------------------------------------
/Screenshots/QuizGame_JoinGame_scaled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/Screenshots/QuizGame_JoinGame_scaled.png
--------------------------------------------------------------------------------
/Screenshots/QuizGame_QuestionAnswered_scaled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/Screenshots/QuizGame_QuestionAnswered_scaled.png
--------------------------------------------------------------------------------
/Screenshots/StartUpProject_scaled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-networkhelper/03c096983370eda96ab6bd8695d0f8a0f2bd3996/Screenshots/StartUpProject_scaled.png
--------------------------------------------------------------------------------
/architecture.md:
--------------------------------------------------------------------------------
1 | # QuizGame architecture
2 |
3 | Although QuizGame is minimalistic, it has a realistic architecture that reflects best practices for larger projects. The networking, game logic, user interaction, and display functionality are kept loosely coupled, making it easier to modify, replace, or reuse individual components of the app, among other benefits.
4 |
5 | ## Separation of concerns
6 |
7 | The P2PHelper component is completely agnostic about the messages that it sends, but to keep it decoupled from other code, the rest of the app accesses it only through an adapter (The *Communicator types) that exposes a game-oriented interface.
8 |
9 | The app works with any valid implementations of this interface, and there are mock versions of the adapters that take advantage of this fact. These mock adapters communicate directly, bypassing the network to enable isolation testing of the UI. (You can enable test mode by setting the conditional compilation symbol LOCALTESTMODEON in the project's build properties.)
10 |
11 | ## MVVM
12 |
13 | The app uses the Model-View-ViewModel (MVVM) architecture to integrate the various components. The **HostViewModel** and **ClientViewModel** classes access one another through the **IHostCommunicator** and **IClientCommunicator** interfaces (implemented by the adapter described above). The HostViewModel also accesses the game logic (the model layer) through the **IGame** interface. Both view-models expose properties and commands for their respective UIs (the view layer) to bind to.
14 |
15 | ## Current state
16 |
17 | This app is a work in progress made available for early review. It started out as a Windows 8.1 Universal app and was converted to a Windows 10 UWP app. Forthcoming updates will take increasing advantage of UWP platform features to demonstrate best practices for UWP app development. Feel free to send us your feedback on any aspect of this sample.
18 |
19 | ## Next steps
20 |
21 | This sample is intentionally simplistic within the architectural requirements just described. The goal is not to build a complete, full-featured, well-polished trivia game app, but rather to demonstrate some basic possibilities that can go in several different directions. The decoupled architecture makes it easier to borrow pieces, integrate other code, or use as a template for a different app. Let us know if you have any ideas on where it might go next!
22 |
23 |
--------------------------------------------------------------------------------