Skip to content

Commit b85cffc

Browse files
committed
2 parents 8588caf + eb9f35e commit b85cffc

File tree

21 files changed

+585
-1
lines changed

21 files changed

+585
-1
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": ".NET Core Launch (web)",
6+
"type": "coreclr",
7+
"request": "launch",
8+
"preLaunchTask": "build",
9+
"program": "${workspaceFolder}/bin/Debug/net8.0/dotnetcore_quiz.dll",
10+
"args": [],
11+
"cwd": "${workspaceFolder}",
12+
"stopAtEntry": false,
13+
"serverReadyAction": {
14+
"action": "openExternally",
15+
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
16+
},
17+
"env": {
18+
"ASPNETCORE_ENVIRONMENT": "Production"
19+
},
20+
"sourceFileMap": {
21+
"/Views": "${workspaceFolder}/Views"
22+
}
23+
},
24+
{
25+
"name": ".NET Core Launch (web) Debug",
26+
"type": "coreclr",
27+
"request": "launch",
28+
"preLaunchTask": "build",
29+
"program": "${workspaceFolder}/bin/Debug/net8.0/dotnetcore_quiz.dll",
30+
"args": [],
31+
"cwd": "${workspaceFolder}",
32+
"stopAtEntry": false,
33+
"serverReadyAction": {
34+
"action": "openExternally",
35+
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
36+
},
37+
"env": {
38+
"ASPNETCORE_ENVIRONMENT": "Development"
39+
},
40+
"sourceFileMap": {
41+
"/Views": "${workspaceFolder}/Views"
42+
}
43+
}
44+
]
45+
}
46+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/dotnetcore_quiz.csproj"
11+
],
12+
"problemMatcher": "$msCompile"
13+
}
14+
]
15+
}
16+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Serilog;
2+
using Serilog.Events;
3+
4+
namespace SecureFromScratch.Quiz.Config
5+
{
6+
public static class LoggerConfig
7+
{
8+
public static void Configure()
9+
{
10+
Log.Logger = new LoggerConfiguration()
11+
.MinimumLevel.Information()
12+
.Enrich.FromLogContext()
13+
.WriteTo.Console()
14+
.WriteTo.File("Logs/log.txt", rollingInterval: RollingInterval.Day)
15+
.CreateLogger();
16+
}
17+
}
18+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
public class LoggerEx
2+
{
3+
private readonly ILogger _logger;
4+
private static readonly string VALID_CHARS = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()-";
5+
6+
public LoggerEx(ILogger logger)
7+
{
8+
_logger = logger;
9+
}
10+
public void LogInformation(string message, params object[] args)
11+
{
12+
checkMessage(args);
13+
_logger.LogInformation(message, args);
14+
}
15+
public void LogWarning(string message, params object[] args)
16+
{
17+
checkMessage(args);
18+
_logger.LogWarning(message, args);
19+
}
20+
public void checkMessage(params object[] args)
21+
{
22+
foreach (string item in args)
23+
{
24+
if (!hasOnlyValidChars(item, VALID_CHARS))
25+
{
26+
Console.Error.WriteLine("Log contains invlaid characters");
27+
throw new ArgumentException("Log is not valid.");
28+
}
29+
}
30+
}
31+
private static bool hasOnlyValidChars(string input, string validChars)
32+
{
33+
foreach (char c in input)
34+
{
35+
if (!validChars.Contains(c))
36+
{
37+
return false;
38+
}
39+
}
40+
41+
return true;
42+
}
43+
44+
45+
46+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.Extensions.Logging;
3+
using SecureFromScratch.Quiz.Model;
4+
using SecureFromScratch.Quiz.Services;
5+
using SecureFromScratch.Quiz.Safety;
6+
//using SecureFromScratch.Quiz.Safety.Spoilers;
7+
using SecureFromScratch.Quiz.Config;
8+
9+
namespace SecureFromScratch.Quiz.Controllers
10+
{
11+
[ApiController]
12+
[Route("/")]
13+
public class QuizController : ControllerBase
14+
{
15+
private readonly ILogger<QuizController> _logger;
16+
//private readonly LoggerEx _answersLogger;
17+
private readonly ILogger _answersLogger;
18+
private readonly MultipleChoiceQuestion _quizService;
19+
20+
public QuizController(
21+
ILogger<QuizController> logger,
22+
ILoggerFactory loggerFactory,
23+
MultipleChoiceQuestion quizService)
24+
{
25+
_logger = logger;
26+
//_answersLogger = new LoggerEx(loggerFactory.CreateLogger("Answers"));
27+
_answersLogger = logger;
28+
_quizService = quizService;
29+
}
30+
31+
32+
33+
34+
private static string GenerateAnswerOption(string username, string optionText, bool isCorrect)
35+
{
36+
return $"<a href='/answer?username={username}&isCorrect={isCorrect}'>{optionText}</a><br>\n";
37+
}
38+
39+
public record QuestionDetails(string Question, string[] Answers);
40+
41+
[HttpGet("question")]
42+
public ContentResult GetQuestionDetails([FromQuery(Name = "username")] string rawUsername)
43+
{
44+
_logger.LogInformation("/question with name={Username}", rawUsername);
45+
46+
var question = _quizService.GetQuestion();
47+
_logger.LogInformation("/question returning question: {Question}", question);
48+
49+
var username = new FullName(rawUsername);
50+
var shuffledAnswers = _quizService.GenerateShuffledAnswers();
51+
var options = shuffledAnswers.Options;
52+
var correctAnswerIdx = shuffledAnswers.CorrectAnswerIdx;
53+
54+
var html = "<html>" +
55+
"<head><title>Answer The Daily Question</title></head>" +
56+
"<body>" +
57+
$"<h2>{question}</h2><br>\n" +
58+
"Here are the possible answers, choose the appropriate link:<br>\n" +
59+
GenerateAnswerOption(username.Username, options[0], correctAnswerIdx == 0) +
60+
GenerateAnswerOption(username.Username, options[1], correctAnswerIdx == 1) +
61+
GenerateAnswerOption(username.Username, options[2], correctAnswerIdx == 2) +
62+
GenerateAnswerOption(username.Username, options[3], correctAnswerIdx == 3) +
63+
"</body></html>";
64+
65+
return Content(html, "text/html");
66+
}
67+
68+
69+
private string GenerateAnswerOption(object value, string v1, bool v2)
70+
{
71+
throw new NotImplementedException();
72+
}
73+
74+
[HttpGet("answer")]
75+
public ContentResult SubmitAnswer([FromQuery(Name = "username")] string rawUsername, [FromQuery] bool isCorrect)
76+
{
77+
_logger.LogInformation("/answer with name={Username} and answer={Answer}", rawUsername, isCorrect);
78+
79+
var username = new FullName(rawUsername);
80+
81+
if (isCorrect)
82+
_answersLogger.LogWarning("Right: {Username}", username.Username);
83+
else
84+
_answersLogger.LogInformation("Wrong: {Username}", username.Username);
85+
86+
var html = "<html>" +
87+
"<head><title>Done!</title></head>" +
88+
"<body>" +
89+
$"<h2>Thank you for submitting, {username.Username}</h2>" +
90+
"<a href='/index.html'>Start Again</a>" +
91+
"</body></html>";
92+
93+
return Content(html, "text/html");
94+
}
95+
96+
}
97+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace SecureFromScratch.Quiz.Model
2+
{
3+
public class ShuffledAnswers
4+
{
5+
public string[] Options { get; set; }
6+
public int CorrectAnswerIdx { get; set; }
7+
}
8+
}

csharp/dotnetcore_quiz/Program.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Serilog;
2+
using SecureFromScratch.Quiz.Config;
3+
4+
LoggerConfig.Configure(); // ✅ Call your custom method
5+
var builder = WebApplication.CreateBuilder(args);
6+
7+
builder.Host.UseSerilog(); // ✅ Plug Serilog into ASP.NET
8+
9+
builder.Services.AddControllers();
10+
builder.Services.AddSingleton<SecureFromScratch.Quiz.Services.MultipleChoiceQuestion>();
11+
12+
var app = builder.Build();
13+
14+
if (app.Environment.IsDevelopment())
15+
{
16+
app.UseDeveloperExceptionPage(); // shows detailed errors
17+
}
18+
else
19+
{
20+
app.UseExceptionHandler("/error"); // generic error endpoint
21+
app.UseHsts();
22+
}
23+
24+
app.Map("/error", () => Results.Problem("An unexpected error occurred."));
25+
app.UseDefaultFiles();
26+
app.UseStaticFiles();
27+
28+
app.MapControllers();
29+
30+
31+
32+
33+
app.Run();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:38362",
8+
"sslPort": 44303
9+
}
10+
},
11+
"profiles": {
12+
"http": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"launchUrl": "swagger",
17+
"applicationUrl": "http://localhost:5223",
18+
"environmentVariables": {
19+
"ASPNETCORE_ENVIRONMENT": "Development"
20+
}
21+
},
22+
"https": {
23+
"commandName": "Project",
24+
"dotnetRunMessages": true,
25+
"launchBrowser": true,
26+
"launchUrl": "swagger",
27+
"applicationUrl": "https://localhost:7180;http://localhost:5223",
28+
"environmentVariables": {
29+
"ASPNETCORE_ENVIRONMENT": "Development"
30+
}
31+
},
32+
"IIS Express": {
33+
"commandName": "IISExpress",
34+
"launchBrowser": true,
35+
"launchUrl": "swagger",
36+
"environmentVariables": {
37+
"ASPNETCORE_ENVIRONMENT": "Development"
38+
}
39+
}
40+
}
41+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace SecureFromScratch.Quiz.Safety
2+
{
3+
public class FullName
4+
{
5+
6+
public string Username { get; }
7+
8+
public FullName(string username)
9+
{
10+
if (string.IsNullOrWhiteSpace(username))
11+
throw new ArgumentException("Username cannot be empty.");
12+
Username = username;
13+
}
14+
15+
public override string ToString() => Username;
16+
}
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using SecureFromScratch.Quiz.Model;
2+
3+
namespace SecureFromScratch.Quiz.Services
4+
{
5+
public class MultipleChoiceQuestion
6+
{
7+
public string GetQuestion() => "What is the capital of France?";
8+
9+
public ShuffledAnswers GenerateShuffledAnswers() => new ShuffledAnswers
10+
{
11+
Options = new[] { "Paris", "Berlin", "Rome", "Madrid" },
12+
CorrectAnswerIdx = 0
13+
};
14+
}
15+
}

0 commit comments

Comments
 (0)