1+ // This code is designed to be as simple as possible, not pulling in lots of libraries and frameworks such as EF and MVC.
2+ using System . Data ;
3+ using System . Data . SqlClient ;
4+ using System . Text ;
5+ using System . Text . Json ;
6+ using System . Security . Cryptography ;
7+
8+ var builder = WebApplication . CreateBuilder ( new WebApplicationOptions {
9+ Args = args ,
10+ ApplicationName = typeof ( Program ) . Assembly . FullName ,
11+ ContentRootPath = Directory . GetCurrentDirectory ( ) ,
12+ WebRootPath = Directory . GetCurrentDirectory ( )
13+ } ) ;
14+
15+ var app = builder . Build ( ) ;
16+ app . UseRouting ( ) ;
17+ app . UseDefaultFiles ( ) ;
18+ app . UseStaticFiles ( ) ;
19+
20+ string htmlIndexStart = File . ReadAllText ( "indexStart.html" ) ;
21+ string htmlError = File . ReadAllText ( "error.html" ) ;
22+ string htmlSuccess = File . ReadAllText ( "success.html" ) ;
23+
24+ // this code connects to SQL as the user of the web app
25+ const string connString = @"Server=.\SQL2022;Database=WorldCup;Trusted_Connection=True;" ;
26+
27+ // root folder
28+ app . MapGet ( "/" , async context => {
29+ Console . WriteLine ( $ "Connection from { context . Connection . RemoteIpAddress } ") ;
30+
31+ // get list of moneylines
32+ using var con = new SqlConnection ( connString ) ;
33+ con . Open ( ) ;
34+
35+ using var cmd = new SqlCommand ( "SELECT * from [dbo].[MoneyLine]" , con ) ;
36+ var rows = cmd . ExecuteReader ( ) ;
37+
38+ // create array of games from the moneyline data
39+ List < Game > games = new List < Game > ( ) ;
40+ while ( rows . Read ( ) ) {
41+ var game = new Game ( ( int ) rows . GetValue ( 0 ) ,
42+ ( string ) rows . GetValue ( 1 ) ,
43+ ( int ) rows . GetValue ( 2 ) ,
44+ ( int ) rows . GetValue ( 3 ) ,
45+ ( string ) rows . GetValue ( 4 ) ,
46+ ( int ) rows . GetValue ( 5 ) ,
47+ ( DateTime ) rows . GetValue ( 6 ) ) ;
48+ games . Add ( game ) ;
49+ }
50+
51+ // Build the resulting HTML on the fly!
52+ // One <TR> per game, multiple <TD>s
53+ var sbHtml = new StringBuilder ( htmlIndexStart ) ;
54+ const string TR = @"<TR>" , TD = @"<TD>" , SpanTD = @"<TD colspan=3>" ;
55+ const string EndTR = @"</TR>" , EndTD = @"</TD>" ;
56+ const string Radio = @"<input type='radio' name='bet' value='{0}|{1}|{2}|{3}'>" ;
57+ foreach ( var g in games ) {
58+
59+ // Country vs Country heading
60+ sbHtml . Append ( TR ) ;
61+ sbHtml . Append ( SpanTD ) ;
62+ var imgHomeFlag = @"<img src='img/" + g . HomeCountry . Replace ( " " , "" ) + @".png' width=14> " ;
63+ var imgVisitFlag = @"<img src='img/" + g . VisitCountry . Replace ( " " , "" ) + @".png' width=14> " ;
64+ sbHtml . Append ( imgHomeFlag + g . HomeCountry + " vs " + imgVisitFlag + g . VisitCountry ) ; // + " on " + g.GameDateTime);
65+ sbHtml . Append ( EndTD ) ;
66+ sbHtml . Append ( EndTR ) ;
67+
68+ // The three sets of odds
69+ sbHtml . Append ( TR ) ;
70+ // Win
71+ sbHtml . Append ( TD ) ;
72+ sbHtml . Append ( string . Format ( Radio , g . MoneyLineID , g . HomeCountry , "W" , g . HomeCountryOdds ) ) ;
73+ sbHtml . Append ( g . HomeCountry + " " + g . HomeCountryOdds ) ;
74+ sbHtml . Append ( EndTD ) ;
75+
76+ // Draw
77+ sbHtml . Append ( TD ) ;
78+ sbHtml . Append ( string . Format ( Radio , g . MoneyLineID , g . HomeCountry , "D" , g . DrawOdds ) ) ;
79+ sbHtml . Append ( "Draw " + g . DrawOdds ) ;
80+ sbHtml . Append ( EndTD ) ;
81+
82+ // Loss
83+ sbHtml . Append ( TD ) ;
84+ sbHtml . Append ( string . Format ( Radio , g . MoneyLineID , g . HomeCountry , "L" , g . VisitCountryOdds ) ) ;
85+ sbHtml . Append ( g . VisitCountry + " " + g . VisitCountryOdds ) ;
86+ sbHtml . Append ( EndTD ) ;
87+ sbHtml . Append ( EndTR ) ;
88+
89+ sbHtml . Append ( "\n " ) ;
90+ }
91+
92+ // close off the HTML page
93+ sbHtml . Append ( "</table><p></p><input type='submit' value='Place Bet' style='height:70px; width:200px; font-size:1.5em; color:#FFFFFF; background-color:#808080'></form></body></html>" ) ;
94+
95+
96+ await context . Response . WriteAsync ( sbHtml . ToString ( ) ) ;
97+ } ) ;
98+
99+ // place a bet and insert into SQL
100+
101+ app . MapGet ( "/placebet" , async context => {
102+ //string name = context.Request.Query["flname"];
103+ string amount = context . Request . Query [ "betamount" ] ;
104+ string moneyline = context . Request . Query [ "bet" ] ;
105+ string name = "Pieter Vanhove" ;
106+ Console . WriteLine ( $ "Request for bet from { name } for { amount } on { moneyline } ") ;
107+
108+ string fName = "" , lName = "" ;
109+ string Country = "" , result = "" ;
110+ var odds = 0 ;
111+ var moneylineId = 0 ;
112+ var amount2 = 0 ;
113+
114+ // this validates the three input args and if there're no errors, returns them in the ref args
115+ // moneyline is a string of four values separated by a '|'
116+ if ( ! ValidateRequest ( name ,
117+ amount ,
118+ moneyline ,
119+ ref fName ,
120+ ref lName ,
121+ ref moneylineId ,
122+ ref Country ,
123+ ref result ,
124+ ref odds ,
125+ ref amount2 ) ) {
126+ await context . Response . WriteAsync ( htmlError ) ;
127+ } else {
128+ var cs = connString ;
129+
130+ using var con = new SqlConnection ( cs ) ;
131+ con . Open ( ) ;
132+
133+ using ( var cmd = new SqlCommand ( "usp_PlaceBet" , con ) ) {
134+ cmd . Parameters . AddWithValue ( "@MoneylineID" , moneylineId ) ;
135+ cmd . Parameters . AddWithValue ( "@FirstName" , fName ) ;
136+ cmd . Parameters . AddWithValue ( "@LastName" , lName ) ;
137+ cmd . Parameters . AddWithValue ( "@Country" , Country ) ;
138+ cmd . Parameters . AddWithValue ( "@Bet" , amount2 ) ;
139+ cmd . Parameters . AddWithValue ( "@Odds" , odds ) ;
140+
141+ cmd . CommandType = CommandType . StoredProcedure ;
142+ cmd . ExecuteNonQuery ( ) ;
143+ }
144+
145+ // add the receipt tp the resulting confirmation page
146+ string ? digest = GetLedgerDigest ( con ) ;
147+ if ( ! string . IsNullOrEmpty ( digest ) ) {
148+ string sigFilename = CreateDownloadableSigBlock ( digest ) ;
149+ htmlSuccess = htmlSuccess . Replace ( "%F%" , sigFilename ) ;
150+
151+ await context . Response . WriteAsync ( htmlSuccess ) ;
152+ } else {
153+ await context . Response . WriteAsync ( "Unable to download receipt." ) ;
154+ }
155+ }
156+ } ) ;
157+
158+ // Get SQL Server version
159+ app . MapGet ( "/version" , async context => {
160+ string ? v = GetSqlVersion ( ) ;
161+ await context . Response . WriteAsync ( string . IsNullOrEmpty ( v ) ? "Unable to get SQL Server version" : v ) ;
162+ } ) ;
163+
164+ bool ValidateRequest ( string name ,
165+ string amount ,
166+ string moneyline ,
167+ ref string fName ,
168+ ref string lName ,
169+ ref int moneylineId ,
170+ ref string Country ,
171+ ref string result ,
172+ ref int odds ,
173+ ref int amount2 ) {
174+
175+ if ( ! string . IsNullOrEmpty ( name ) && ! string . IsNullOrEmpty ( amount ) && ! string . IsNullOrEmpty ( moneyline ) ) {
176+ // Get user name
177+ string [ ] flname = name . Split ( ' ' , 2 ) ;
178+ if ( flname . Count ( ) != 2 )
179+ return false ;
180+
181+ // get bet amount
182+ UInt32 intAmount = 0 ;
183+ if ( ! UInt32 . TryParse ( amount , out intAmount ) )
184+ return false ;
185+
186+ // moneyline is four fields:
187+ // FirstName|LastName|Wager|Odds
188+ const int NUM_FIELDS = 4 ;
189+ string [ ] moneylineItems = moneyline . Split ( '|' , NUM_FIELDS ) ;
190+ if ( moneylineItems . Count ( ) != NUM_FIELDS )
191+ return false ;
192+
193+ if ( ! Int32 . TryParse ( amount , out amount2 ) )
194+ return false ;
195+
196+ // get user's name
197+ fName = flname [ 0 ] ;
198+ lName = flname [ 1 ] ;
199+
200+ // get bet details
201+ if ( ! Int32 . TryParse ( moneylineItems [ 0 ] , out moneylineId ) )
202+ return false ;
203+
204+ // Home country and Win, Draw or Loss
205+ Country = moneylineItems [ 1 ] ;
206+ result = moneylineItems [ 2 ] ;
207+
208+ // odds
209+ if ( ! Int32 . TryParse ( moneylineItems [ 3 ] , out odds ) )
210+ return false ;
211+
212+ return true ;
213+ }
214+
215+ return false ;
216+ }
217+
218+ // SQL query to get SQL Server version
219+ string ? GetSqlVersion ( ) {
220+ using var con = new SqlConnection ( connString ) ;
221+ con . Open ( ) ;
222+
223+ using var cmd = new SqlCommand ( "SELECT @@VERSION" , con ) ;
224+ return cmd . ExecuteScalar ( ) ? . ToString ( ) ;
225+ }
226+
227+ // Get the last tx digest
228+ string ? GetLedgerDigest ( SqlConnection con ) {
229+ using var cmd = new SqlCommand ( "sp_generate_database_ledger_digest" , con ) ;
230+ return cmd . ExecuteScalar ( ) ? . ToString ( ) ;
231+ }
232+
233+ /* NOT USED
234+ // sign some data, this creates real signatures,
235+ // but the keys are ephemeral for demo purposes only
236+ string SignBlob(string text) {
237+ const string CRYPTO_VERSION = "01";
238+
239+ using SHA256 alg = SHA256.Create(); // in future add cryptoagility
240+ byte[] data = Encoding.ASCII.GetBytes(text);
241+ byte[] hash = alg.ComputeHash(data);
242+
243+ // for demo only, we should really load a cert/key from the cert store
244+ using (RSA rsa = RSA.Create()) {
245+ RSAPKCS1SignatureFormatter rsaForm = new(rsa);
246+ rsaForm.SetHashAlgorithm(nameof(SHA256));
247+ byte[] sig = rsaForm.CreateSignature(hash);
248+ var sigBase64 = Convert.ToBase64String(sig);
249+ return CRYPTO_VERSION + "|" + sigBase64;
250+ }
251+ }
252+ */
253+ // creates a file with a random name
254+ string CreateDownloadableSigBlock ( string sig ) {
255+ var filename = Guid . NewGuid ( ) + ".json" ;
256+ using ( var fs = File . Create ( filename ) ) {
257+ byte [ ] b = new UTF8Encoding ( true ) . GetBytes ( sig ) ;
258+ fs . Write ( b , 0 , b . Length ) ;
259+ }
260+
261+ return filename ;
262+ }
263+
264+ // Start the web app
265+ app . Run ( ) ;
266+
267+ // struct to hold game details
268+ public struct Game {
269+ public Game ( int MoneyLineID , string HomeCountry , int HomeCountryOdds , int DrawOdds , string VisitCountry , int VisitCountryOdds , DateTime GameDateTime ) {
270+ this . MoneyLineID = MoneyLineID ;
271+ this . HomeCountry = HomeCountry ;
272+ this . HomeCountryOdds = HomeCountryOdds ;
273+ this . DrawOdds = DrawOdds ;
274+ this . VisitCountry = VisitCountry ;
275+ this . VisitCountryOdds = VisitCountryOdds ;
276+ this . GameDateTime = GameDateTime ;
277+ }
278+ public int MoneyLineID ;
279+ public string HomeCountry ;
280+ public int HomeCountryOdds ;
281+ public int DrawOdds ;
282+ public string VisitCountry ;
283+ public int VisitCountryOdds ;
284+ public DateTime GameDateTime ;
285+ }
0 commit comments