Skip to content

Commit f12afca

Browse files
authored
Merge pull request #1083 from Pietervanhove/ledgerdemosql2022
Add Ledger Demo for SQL Server 2022
2 parents c35fcce + 0e260c9 commit f12afca

54 files changed

Lines changed: 1519 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
5.13 MB
Loading
3.72 KB
Loading
7.15 KB
Loading

media/features/ledger/Michael.jpg

292 KB
Loading

media/features/ledger/Pieter.jpg

2.54 KB
Loading
22.9 KB
Loading

samples/features/security/ledger/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
This set of samples/demos showcases [Ledger](https://docs.microsoft.com/en-us/azure/azure-sql/database/ledger-overview).
66

77
- [Demo of Ledger in Azure SQL Database](./azure-sql-database/README.md)
8+
- [Demo of Ledger in SQL Server](./sql-server/README.md)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.dll
2+
*.obj
3+
*.sig.txt
4+
*.json
5+
bin/
6+
obj/
7+
properties/
8+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="System.Data.SqlClient" Version="4.8.3" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
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>&nbsp;";
63+
var imgVisitFlag = @"<img src='img/" + g.VisitCountry.Replace(" ", "") + @".png' width=14>&nbsp;";
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

Comments
 (0)