├── .gitattributes
├── .gitignore
├── Chat
├── ChatClient
│ ├── ChatClient.csproj
│ └── Program.cs
├── ChatProtocol
│ ├── ChatProtocol.csproj
│ └── Constants.cs
└── ChatServer
│ ├── ChatServer.csproj
│ └── Program.cs
├── ConfigurationSample
├── BindingConfig.cs
├── ConfigFiles
│ ├── KeyPerFile
│ │ └── KeyPerFileConfigSample__StringValue
│ ├── mysettings.json
│ ├── mysettings.optional.json
│ └── mysettings.xml
├── ConfigurationSample.csproj
└── Program.cs
├── ConfigureAwaitSample
├── ConfigureAwaitSample.csproj
├── MyThreadPool.cs
└── Program.cs
├── DI-Test
├── DI-Test.csproj
├── IMyScopedService.cs
├── IMySingletonService.cs
├── IMyTransientService.cs
├── MyScopedService.cs
├── MySingletonService.cs
├── MyTransientService.cs
├── Program.cs
└── links.txt
├── LearnDotNet-Samples.sln
├── LoggingSample
├── LoggingSample.csproj
├── Program.cs
└── appsettings.json
├── NFind
├── ConsoleLineSource.cs
├── FileLineSource.cs
├── FindOptions.cs
├── ILineSource.cs
├── IStringFinder.cs
├── Line.cs
├── LineSourceStringFinder.cs
├── NFind.csproj
├── Program.cs
└── Properties
│ └── launchSettings.json
├── README.md
├── RepositorySample
├── DB
│ └── ShopDB.sql
├── Entities
│ ├── Order.cs
│ ├── OrderItem.cs
│ └── Product.cs
├── IOrderReferenceGenerator.cs
├── OrderReferenceGenerators
│ ├── FixedStringOrderReferenceGenerator.cs
│ └── RandomStringOrderReferenceGenerator.cs
├── Program.cs
├── Repository
│ ├── IOrderRepository.cs
│ ├── IProductRepository.cs
│ ├── InMemory
│ │ ├── InMemoryOrderRepository.cs
│ │ └── InMemoryProductRepository.cs
│ ├── OrderFindCreterias.cs
│ ├── OrderSortBy.cs
│ ├── PagingCreterias.cs
│ ├── ProductFindCreterias.cs
│ ├── ProductSortBy.cs
│ └── SqlServer
│ │ ├── SqlServerOrderRepository.cs
│ │ └── SqlServerProductRepository.cs
├── RepositorySample.csproj
├── UnitOfWork
│ ├── ICheckoutUnitOfWork.cs
│ └── SqlServer
│ │ └── SqlServerCheckoutUnitOfWork.cs
└── appsettings.json
├── Task-Sample
├── Program.cs
└── Task-Sample.csproj
├── ThreadPoolSample
├── MyThreadPool.cs
├── Program.cs
└── ThreadPoolSample.csproj
├── WebServer.Middleware.StaticContent
├── StaticContentMiddleware.cs
└── WebServer.Middleware.StaticContent.csproj
├── WebServer
├── WebServer.SDK
│ ├── HttpReasonPhrases.cs
│ ├── HttpResponseCodes.cs
│ ├── ICallable.cs
│ ├── IMiddleware.cs
│ ├── IRequestReader.cs
│ ├── IRequestReaderFactory.cs
│ ├── IResponseBodyWriter.cs
│ ├── IResponseWriter.cs
│ ├── IResponseWriterFactory.cs
│ ├── MiddlewareContext.cs
│ ├── NullResponseBodyWriter.cs
│ ├── ResponseBodyWriters
│ │ └── StringResponseBodyWriter.cs
│ ├── WHeader.cs
│ ├── WMethods.cs
│ ├── WRequest.cs
│ ├── WResponse.cs
│ └── WebServer.SDK.csproj
└── WebServer.Server
│ ├── Callable.cs
│ ├── ClientConnection.cs
│ ├── DefaultMiddlewares
│ ├── NotFoundMiddleware.cs
│ └── NullMiddleware.cs
│ ├── HeaderLine.cs
│ ├── NullCallable.cs
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── RequestLine.cs
│ ├── RequestLineParser.cs
│ ├── RequestReaderFactory.cs
│ ├── RequestReaders
│ └── DefaultRequestReader.cs
│ ├── ResponseWriterFactory.cs
│ ├── ResponseWriters
│ └── DefaultResponseWriter.cs
│ ├── WRequestBuilder.cs
│ ├── WebServer.Server.csproj
│ ├── WebServerOptions.cs
│ ├── Worker.cs
│ ├── appsettings.Development.json
│ └── appsettings.json
└── WhyWeNeedAsync
├── Controllers
└── HomeController.cs
├── Models
└── Product.cs
├── Program.cs
├── Properties
└── launchSettings.json
├── README.md
├── WhyWeNeedAsync.csproj
├── appsettings.Development.json
├── appsettings.json
└── wwwroot
└── favicon.ico
/.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 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
364 | /RepositorySample/connectionStrings.json
365 |
--------------------------------------------------------------------------------
/Chat/ChatClient/ChatClient.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Chat/ChatClient/Program.cs:
--------------------------------------------------------------------------------
1 | using ChatProtocol;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 |
6 | namespace ChatClient
7 | {
8 | internal class Program
9 | {
10 | static async Task Main(string[] args)
11 | {
12 | var endPoint = new IPEndPoint(IPAddress.Loopback, ChatProtocol.Constants.DefaultChatPort);
13 |
14 | var clientSocket = new Socket(
15 | endPoint.AddressFamily,
16 | SocketType.Stream,
17 | ProtocolType.Tcp
18 | );
19 |
20 | await clientSocket.ConnectAsync(endPoint);
21 | var buffer = new byte[1024];
22 | var r = await clientSocket.ReceiveAsync(buffer);
23 | if (r == 0)
24 | {
25 | showConnectionError();
26 | return;
27 | }
28 |
29 | var welcomeText = Encoding.UTF8.GetString(buffer, 0, r);
30 | if (!Constants.WelcomeText.Equals(welcomeText))
31 | {
32 | showConnectionError();
33 | return;
34 | }
35 |
36 | Console.WriteLine(welcomeText);
37 |
38 | while (true)
39 | {
40 | Console.Write("Enter your message: ");
41 | var msg = Console.ReadLine();
42 |
43 | if (string.IsNullOrEmpty(msg)) {
44 | await closeConnectionAsync(clientSocket);
45 | return;
46 | }
47 | else
48 | {
49 | var bytes = Encoding.UTF8.GetBytes(msg);
50 | await clientSocket.SendAsync(bytes);
51 | }
52 | }
53 | }
54 |
55 | private static async Task closeConnectionAsync(Socket clientSocket)
56 | {
57 | var bytes = Encoding.UTF8.GetBytes(Constants.CommandShutdown);
58 | await clientSocket.SendAsync(bytes);
59 |
60 | clientSocket.Close();
61 | }
62 |
63 | private static void showConnectionError()
64 | {
65 | Console.WriteLine("Invalid protocol!");
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Chat/ChatProtocol/ChatProtocol.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Chat/ChatProtocol/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace ChatProtocol
2 | {
3 | public class Constants
4 | {
5 | public const int DefaultChatPort = 8899;
6 | public const string WelcomeText = "Welcome!";
7 |
8 | public const string CommandShutdown = "";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Chat/ChatServer/ChatServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Chat/ChatServer/Program.cs:
--------------------------------------------------------------------------------
1 | using ChatProtocol;
2 | using System;
3 | using System.Net;
4 | using System.Net.Sockets;
5 | using System.Text;
6 |
7 | namespace ChatServer
8 | {
9 | internal class Program
10 | {
11 | // đây là phiên bản đã được chỉnh sửa, nếu bạn muốn xem code trình bày trên video Youtube (bài học lập trình Socket), hãy
12 | // tham khảo: https://github.com/daohainam/LearnDotNet-Samples/tree/af72bd21659bbc164ff85284ce654e8a2f1339f4
13 |
14 | static async Task Main(string[] args)
15 | {
16 | var endPoint = new IPEndPoint(IPAddress.Loopback, ChatProtocol.Constants.DefaultChatPort);
17 | var serverSocket = new Socket(
18 | endPoint.AddressFamily,
19 | SocketType.Stream,
20 | ProtocolType.Tcp
21 | );
22 |
23 | serverSocket.Bind(endPoint);
24 |
25 | Console.WriteLine($"Listening... (port {endPoint.Port})");
26 |
27 | serverSocket.Listen();
28 |
29 |
30 | CancellationTokenSource cancellationTokenSource = new();
31 | var cancellationToken = cancellationTokenSource.Token;
32 |
33 | var acceptTask = AcceptConnectionsAsync(serverSocket, cancellationToken);
34 |
35 | Console.WriteLine("Press Enter to shutdown the server...");
36 | Console.ReadLine();
37 |
38 | cancellationTokenSource.Cancel();
39 | await acceptTask;
40 | }
41 |
42 | private static async Task HandleClientRequestAsync(Socket clientSocket, int clientId, CancellationToken cancellationToken)
43 | {
44 | Console.WriteLine($"[Client {clientId}] connected!");
45 |
46 | var welcomeBytes = Encoding.UTF8.GetBytes(Constants.WelcomeText);
47 | await clientSocket.SendAsync(welcomeBytes, cancellationToken);
48 |
49 | var buffer = new byte[1024];
50 |
51 | while (!cancellationToken.IsCancellationRequested)
52 | {
53 | try
54 | {
55 | var r = await clientSocket.ReceiveAsync(buffer, cancellationToken: cancellationToken);
56 | var msg = Encoding.UTF8.GetString(buffer, 0, r);
57 |
58 | if (msg.Equals(Constants.CommandShutdown))
59 | {
60 | CloseConnection(clientSocket);
61 | Console.WriteLine($"[Client {clientId}] disconnected!");
62 | break;
63 | }
64 |
65 | Console.WriteLine($"[Client {clientId}]: {msg}");
66 | } catch (OperationCanceledException)
67 | { }
68 | }
69 | }
70 |
71 | private static void CloseConnection(Socket clientSocket)
72 | {
73 | clientSocket.Close();
74 | }
75 |
76 | private static async Task AcceptConnectionsAsync(Socket serverSocket, CancellationToken cancellationToken)
77 | {
78 | var clientHandlers = new List();
79 | int clientId = 1;
80 |
81 | while (!cancellationToken.IsCancellationRequested)
82 | {
83 | try
84 | {
85 | var clientSocket = await serverSocket.AcceptAsync(cancellationToken);
86 | var t = HandleClientRequestAsync(clientSocket, clientId++, cancellationToken);
87 | clientHandlers.Add(t);
88 | }
89 | catch (OperationCanceledException)
90 | {
91 | break;
92 | }
93 | }
94 |
95 | await Task.WhenAll(clientHandlers);
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ConfigurationSample/BindingConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConfigurationSample
8 | {
9 | internal class BindingConfig
10 | {
11 | public string StringConfig { get; set; } = string.Empty;
12 | public int IntegerConfig { get; set; }
13 | public bool BoolConfig { get; set; }
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ConfigurationSample/ConfigFiles/KeyPerFile/KeyPerFileConfigSample__StringValue:
--------------------------------------------------------------------------------
1 | The KeyPerFileConfigurationProvider uses a directory's files as configuration key-value pairs. The key is the file name. The value is the file's contents. The Key-per-file configuration provider is used in Docker hosting scenarios.
--------------------------------------------------------------------------------
/ConfigurationSample/ConfigFiles/mysettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "JsonConfigSample": {
3 | "StringConfig": "Value1",
4 | "IntegerConfig": 9999,
5 | "BoolConfig": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/ConfigurationSample/ConfigFiles/mysettings.optional.json:
--------------------------------------------------------------------------------
1 | {
2 | "JsonConfigSample": {
3 | "StringConfig": "Value1-Changed"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/ConfigurationSample/ConfigFiles/mysettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Config from XML
5 | 1234
6 | true
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ConfigurationSample/ConfigurationSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 | PreserveNewest
28 |
29 |
30 | PreserveNewest
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/ConfigurationSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace ConfigurationSample
4 | {
5 | internal class Program
6 | {
7 | static void Main(string[] args)
8 | {
9 | var configuration = new ConfigurationBuilder()
10 | .AddJsonFile("ConfigFiles/mysettings.json", optional: false)
11 | .AddJsonFile("ConfigFiles/mysettings.optional.json", optional: true)
12 | .AddXmlFile("ConfigFiles/mysettings.xml")
13 | .AddKeyPerFile(@"C:\Users\namdo\source\repos\LearnDotNet-Samples\ConfigurationSample\ConfigFiles\KeyPerFile\", optional: true)
14 | .AddEnvironmentVariables()
15 | .AddCommandLine(args)
16 | .Build();
17 |
18 | PrintConfiguredProviders(configuration);
19 | Console.WriteLine();
20 |
21 | PrintConfigValues(configuration);
22 | Console.WriteLine();
23 |
24 | var config = configuration.GetRequiredSection("JsonConfigSample").Get();
25 | Print(config);
26 | }
27 |
28 | private static void Print(BindingConfig? config)
29 | {
30 | if (config == null)
31 | {
32 | Console.WriteLine("config == null");
33 | }
34 | else
35 | {
36 | Console.WriteLine($"StringConfig: {config.StringConfig}");
37 | Console.WriteLine($"IntegerConfig: {config.IntegerConfig}");
38 | Console.WriteLine($"BoolConfig: {config.BoolConfig}");
39 | }
40 | }
41 |
42 | private static void PrintConfiguredProviders(IConfigurationRoot configuration)
43 | {
44 | foreach (var p in configuration.Providers)
45 | {
46 | Console.WriteLine($"Provider: {p.GetType().Name}");
47 | }
48 | }
49 |
50 | private static void PrintConfigValues(IConfigurationRoot configuration)
51 | {
52 | foreach (var config in configuration.AsEnumerable())
53 | {
54 | Console.WriteLine($"{config.Key} = {config.Value}");
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ConfigureAwaitSample/ConfigureAwaitSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ConfigureAwaitSample/MyThreadPool.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ConfigureAwaitSample
9 | {
10 | internal class MyThreadPool
11 | {
12 | private static readonly BlockingCollection<(Action, ExecutionContext?)> actions = [];
13 |
14 | public static void QueueUserWorkItem(Action action) => actions.Add((action, ExecutionContext.Capture()));
15 |
16 | static MyThreadPool()
17 | {
18 | for (int i = 0; i < Environment.ProcessorCount; i++)
19 | {
20 | var t = new Thread(() =>
21 | {
22 | while (true)
23 | {
24 | (Action action, ExecutionContext? context) = actions.Take();
25 | if (context is null)
26 | {
27 | action();
28 | }
29 | else
30 | {
31 | ExecutionContext.Run(context, state => ((Action)state!).Invoke(), action);
32 | }
33 | }
34 | })
35 | {
36 | IsBackground = true
37 | };
38 | t.Start();
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ConfigureAwaitSample/Program.cs:
--------------------------------------------------------------------------------
1 | namespace ConfigureAwaitSample
2 | {
3 | internal class Program
4 | {
5 | static async Task Main(string[] args)
6 | {
7 | for (int i = 0; i < 100; i++)
8 | {
9 | // await Print(i).ConfigureAwait(false);
10 |
11 |
12 | Console.WriteLine(i);
13 | }
14 |
15 | Console.ReadLine();
16 | }
17 |
18 | static async Task Print(int i)
19 | {
20 | Console.WriteLine(i);
21 | await Task.Delay(1000);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/DI-Test/DI-Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | DI_Test
7 | enable
8 | enable
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/DI-Test/IMyScopedService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace DI_Test
8 | {
9 | internal interface IMyScopedService
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DI-Test/IMySingletonService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace DI_Test
8 | {
9 | internal interface IMySingletonService
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DI-Test/IMyTransientService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace DI_Test
8 | {
9 | internal interface IMyTransientService
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DI-Test/MyScopedService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace DI_Test
8 | {
9 | internal class MyScopedService: IMyScopedService
10 | {
11 | private static int Id = 0;
12 | public MyScopedService() {
13 | Console.WriteLine($"Scoped!: {++Id}");
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/DI-Test/MySingletonService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace DI_Test
8 | {
9 | internal class MySingletonService: IMySingletonService
10 | {
11 | private static int Id = 0;
12 | public MySingletonService()
13 | {
14 | Console.WriteLine($"Singleton!: {++Id}");
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/DI-Test/MyTransientService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace DI_Test
8 | {
9 | internal class MyTransientService: IMyTransientService
10 | {
11 | private static int Id = 0;
12 | public MyTransientService()
13 | {
14 | Console.WriteLine($"Transient!: {++Id}");
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/DI-Test/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace DI_Test
4 | {
5 | internal class Program
6 | {
7 | static void Main(string[] args)
8 | {
9 | var serviceCollection = new ServiceCollection();
10 |
11 | serviceCollection.AddSingleton(services => new MySingletonService());
12 | serviceCollection.AddScoped(services => new MyScopedService());
13 | serviceCollection.AddTransient(services => new MyTransientService());
14 |
15 | var service = serviceCollection.BuildServiceProvider();
16 |
17 | object? obj;
18 |
19 | Console.WriteLine("Get singleton service");
20 | obj = service.GetService();
21 | obj = service.GetService();
22 | obj = service.GetService();
23 |
24 | Console.WriteLine("Get scoped service");
25 | obj = service.GetService();
26 | obj = service.GetService();
27 | obj = service.GetService();
28 |
29 | Console.WriteLine("Get transient service");
30 | obj = service.GetService();
31 | obj = service.GetService();
32 | obj = service.GetService();
33 |
34 | Console.WriteLine();
35 | Console.WriteLine("--- Create new scope ---");
36 | Console.WriteLine();
37 |
38 | var scope = service.CreateScope();
39 |
40 | Console.WriteLine("Get singleton service");
41 | obj = scope.ServiceProvider.GetService();
42 | obj = scope.ServiceProvider.GetService();
43 | obj = scope.ServiceProvider.GetService();
44 |
45 | Console.WriteLine("Get scoped service");
46 | obj = scope.ServiceProvider.GetService();
47 | obj = scope.ServiceProvider.GetService();
48 | obj = scope.ServiceProvider.GetService();
49 |
50 | Console.WriteLine("Get transient service");
51 | obj = scope.ServiceProvider.GetService();
52 | obj = scope.ServiceProvider.GetService();
53 | obj = scope.ServiceProvider.GetService();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/DI-Test/links.txt:
--------------------------------------------------------------------------------
1 | Service Provider: https://github.com/dotnet/runtime/blob/9e6ba1f68c6a9c7206dacdf1e4cac67ea19931eb/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs#L235
2 | ASP.NET CreateScope: https://github.com/dotnet/aspnetcore/blob/ed90662610b5770c9b0952d9c2db740388171bf3/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs#L26
--------------------------------------------------------------------------------
/LearnDotNet-Samples.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34723.18
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI-Test", "DI-Test\DI-Test.csproj", "{05BCDBF0-B897-4D22-B651-ADD42C67E4AE}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NFind", "NFind\NFind.csproj", "{AD7B536F-9EB4-4C91-9BA9-D56203A9BFD5}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WhyWeNeedAsync", "WhyWeNeedAsync\WhyWeNeedAsync.csproj", "{48E67E6C-1015-4BE4-B10E-0D0F71E442F9}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThreadPoolSample", "ThreadPoolSample\ThreadPoolSample.csproj", "{CAC74623-226B-473D-9FB8-59972ADEE35E}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositorySample", "RepositorySample\RepositorySample.csproj", "{3461F3AF-634C-48B6-ABD1-CFF8DD8C7877}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoggingSample", "LoggingSample\LoggingSample.csproj", "{1846DF7D-2306-4A43-8FE3-F478351EB9C5}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationSample", "ConfigurationSample\ConfigurationSample.csproj", "{9686CFAD-C714-4815-BC88-497473A87D03}"
19 | EndProject
20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Task-Sample", "Task-Sample\Task-Sample.csproj", "{0AD865C9-4176-4661-84D7-2C75AF17DC7F}"
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chat", "Chat", "{2C00D117-1046-4AFF-90D9-28303B4EDB4F}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatServer", "Chat\ChatServer\ChatServer.csproj", "{AE13FC24-702B-4BFE-9145-A2772163F761}"
25 | EndProject
26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatProtocol", "Chat\ChatProtocol\ChatProtocol.csproj", "{246BE196-A2FE-4199-9479-224BE29E0C6B}"
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatClient", "Chat\ChatClient\ChatClient.csproj", "{3D44C90B-EB93-4CFB-AA80-A810F5C56B96}"
29 | EndProject
30 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebServer", "WebServer", "{FD72EB35-4029-4AD3-96CE-96C241FA3422}"
31 | EndProject
32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebServer.Server", "WebServer\WebServer.Server\WebServer.Server.csproj", "{2C6EDC3C-B676-462A-A8E1-7485E8D3DD90}"
33 | EndProject
34 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer.SDK", "WebServer\WebServer.SDK\WebServer.SDK.csproj", "{9465D50D-363A-41BD-86EF-D9E98743BB65}"
35 | EndProject
36 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3CAF2F7A-877E-453B-8381-E8184B03D7D6}"
37 | EndProject
38 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer.Middleware.StaticContent", "WebServer.Middleware.StaticContent\WebServer.Middleware.StaticContent.csproj", "{21344432-B5B9-4D7D-88D9-5F731C09CCC2}"
39 | EndProject
40 | Global
41 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
42 | Debug|Any CPU = Debug|Any CPU
43 | Release|Any CPU = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
46 | {05BCDBF0-B897-4D22-B651-ADD42C67E4AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {05BCDBF0-B897-4D22-B651-ADD42C67E4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {05BCDBF0-B897-4D22-B651-ADD42C67E4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {05BCDBF0-B897-4D22-B651-ADD42C67E4AE}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {AD7B536F-9EB4-4C91-9BA9-D56203A9BFD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {AD7B536F-9EB4-4C91-9BA9-D56203A9BFD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {AD7B536F-9EB4-4C91-9BA9-D56203A9BFD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {AD7B536F-9EB4-4C91-9BA9-D56203A9BFD5}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {48E67E6C-1015-4BE4-B10E-0D0F71E442F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {48E67E6C-1015-4BE4-B10E-0D0F71E442F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {48E67E6C-1015-4BE4-B10E-0D0F71E442F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 | {48E67E6C-1015-4BE4-B10E-0D0F71E442F9}.Release|Any CPU.Build.0 = Release|Any CPU
58 | {CAC74623-226B-473D-9FB8-59972ADEE35E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 | {CAC74623-226B-473D-9FB8-59972ADEE35E}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 | {CAC74623-226B-473D-9FB8-59972ADEE35E}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {CAC74623-226B-473D-9FB8-59972ADEE35E}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {3461F3AF-634C-48B6-ABD1-CFF8DD8C7877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
63 | {3461F3AF-634C-48B6-ABD1-CFF8DD8C7877}.Debug|Any CPU.Build.0 = Debug|Any CPU
64 | {3461F3AF-634C-48B6-ABD1-CFF8DD8C7877}.Release|Any CPU.ActiveCfg = Release|Any CPU
65 | {3461F3AF-634C-48B6-ABD1-CFF8DD8C7877}.Release|Any CPU.Build.0 = Release|Any CPU
66 | {1846DF7D-2306-4A43-8FE3-F478351EB9C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {1846DF7D-2306-4A43-8FE3-F478351EB9C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {1846DF7D-2306-4A43-8FE3-F478351EB9C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
69 | {1846DF7D-2306-4A43-8FE3-F478351EB9C5}.Release|Any CPU.Build.0 = Release|Any CPU
70 | {9686CFAD-C714-4815-BC88-497473A87D03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
71 | {9686CFAD-C714-4815-BC88-497473A87D03}.Debug|Any CPU.Build.0 = Debug|Any CPU
72 | {9686CFAD-C714-4815-BC88-497473A87D03}.Release|Any CPU.ActiveCfg = Release|Any CPU
73 | {9686CFAD-C714-4815-BC88-497473A87D03}.Release|Any CPU.Build.0 = Release|Any CPU
74 | {0AD865C9-4176-4661-84D7-2C75AF17DC7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
75 | {0AD865C9-4176-4661-84D7-2C75AF17DC7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
76 | {0AD865C9-4176-4661-84D7-2C75AF17DC7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
77 | {0AD865C9-4176-4661-84D7-2C75AF17DC7F}.Release|Any CPU.Build.0 = Release|Any CPU
78 | {AE13FC24-702B-4BFE-9145-A2772163F761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79 | {AE13FC24-702B-4BFE-9145-A2772163F761}.Debug|Any CPU.Build.0 = Debug|Any CPU
80 | {AE13FC24-702B-4BFE-9145-A2772163F761}.Release|Any CPU.ActiveCfg = Release|Any CPU
81 | {AE13FC24-702B-4BFE-9145-A2772163F761}.Release|Any CPU.Build.0 = Release|Any CPU
82 | {246BE196-A2FE-4199-9479-224BE29E0C6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
83 | {246BE196-A2FE-4199-9479-224BE29E0C6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
84 | {246BE196-A2FE-4199-9479-224BE29E0C6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
85 | {246BE196-A2FE-4199-9479-224BE29E0C6B}.Release|Any CPU.Build.0 = Release|Any CPU
86 | {3D44C90B-EB93-4CFB-AA80-A810F5C56B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
87 | {3D44C90B-EB93-4CFB-AA80-A810F5C56B96}.Debug|Any CPU.Build.0 = Debug|Any CPU
88 | {3D44C90B-EB93-4CFB-AA80-A810F5C56B96}.Release|Any CPU.ActiveCfg = Release|Any CPU
89 | {3D44C90B-EB93-4CFB-AA80-A810F5C56B96}.Release|Any CPU.Build.0 = Release|Any CPU
90 | {2C6EDC3C-B676-462A-A8E1-7485E8D3DD90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
91 | {2C6EDC3C-B676-462A-A8E1-7485E8D3DD90}.Debug|Any CPU.Build.0 = Debug|Any CPU
92 | {2C6EDC3C-B676-462A-A8E1-7485E8D3DD90}.Release|Any CPU.ActiveCfg = Release|Any CPU
93 | {2C6EDC3C-B676-462A-A8E1-7485E8D3DD90}.Release|Any CPU.Build.0 = Release|Any CPU
94 | {9465D50D-363A-41BD-86EF-D9E98743BB65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
95 | {9465D50D-363A-41BD-86EF-D9E98743BB65}.Debug|Any CPU.Build.0 = Debug|Any CPU
96 | {9465D50D-363A-41BD-86EF-D9E98743BB65}.Release|Any CPU.ActiveCfg = Release|Any CPU
97 | {9465D50D-363A-41BD-86EF-D9E98743BB65}.Release|Any CPU.Build.0 = Release|Any CPU
98 | {21344432-B5B9-4D7D-88D9-5F731C09CCC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
99 | {21344432-B5B9-4D7D-88D9-5F731C09CCC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
100 | {21344432-B5B9-4D7D-88D9-5F731C09CCC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
101 | {21344432-B5B9-4D7D-88D9-5F731C09CCC2}.Release|Any CPU.Build.0 = Release|Any CPU
102 | EndGlobalSection
103 | GlobalSection(SolutionProperties) = preSolution
104 | HideSolutionNode = FALSE
105 | EndGlobalSection
106 | GlobalSection(NestedProjects) = preSolution
107 | {AE13FC24-702B-4BFE-9145-A2772163F761} = {2C00D117-1046-4AFF-90D9-28303B4EDB4F}
108 | {246BE196-A2FE-4199-9479-224BE29E0C6B} = {2C00D117-1046-4AFF-90D9-28303B4EDB4F}
109 | {3D44C90B-EB93-4CFB-AA80-A810F5C56B96} = {2C00D117-1046-4AFF-90D9-28303B4EDB4F}
110 | {2C6EDC3C-B676-462A-A8E1-7485E8D3DD90} = {FD72EB35-4029-4AD3-96CE-96C241FA3422}
111 | {9465D50D-363A-41BD-86EF-D9E98743BB65} = {FD72EB35-4029-4AD3-96CE-96C241FA3422}
112 | {3CAF2F7A-877E-453B-8381-E8184B03D7D6} = {FD72EB35-4029-4AD3-96CE-96C241FA3422}
113 | {21344432-B5B9-4D7D-88D9-5F731C09CCC2} = {3CAF2F7A-877E-453B-8381-E8184B03D7D6}
114 | EndGlobalSection
115 | GlobalSection(ExtensibilityGlobals) = postSolution
116 | SolutionGuid = {3862E9A6-6113-417F-9CD5-015CA542F0D9}
117 | EndGlobalSection
118 | EndGlobal
119 |
--------------------------------------------------------------------------------
/LoggingSample/LoggingSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Always
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LoggingSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Extensions.Logging;
3 | using Serilog;
4 | using Serilog.Extensions.Logging;
5 |
6 |
7 | namespace LoggingSample
8 | {
9 | internal class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | IConfigurationRoot config = new ConfigurationBuilder()
14 | .AddJsonFile("appSettings.json", optional: true)
15 | .Build();
16 |
17 | Console.WriteLine("Hello from System.Console!");
18 |
19 | var providers = new LoggerProviderCollection();
20 | Log.Logger = new LoggerConfiguration()
21 | //.MinimumLevel.Debug()
22 | //.WriteTo.Console()
23 | .WriteTo.Providers(providers)
24 | .WriteTo.File("logs/logs.txt")
25 | .CreateLogger();
26 |
27 | using ILoggerFactory factory = LoggerFactory.Create(builder =>
28 | {
29 | //builder.SetMinimumLevel(LogLevel.Warning);
30 | builder.AddConfiguration(config);
31 | builder.AddConsole();
32 | builder.AddSerilog();
33 |
34 | // builder.AddFilter(level => true);
35 | }
36 | );
37 |
38 | var logger = factory.CreateLogger("Program");
39 |
40 | logger.LogTrace("Hello form {name}", "Trace");
41 | logger.LogDebug("Hello form {name}", "Debug");
42 | logger.LogInformation("Hello form {name}", "Information");
43 | logger.LogWarning("Hello form {name}", "Warning");
44 | logger.LogError("Hello form {name}", "Error");
45 | logger.LogCritical("Hello form {name}", "Critical");
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/LoggingSample/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Warning"
7 | },
8 | "Console": {
9 | "IncludeScopes": true,
10 | "LogLevel": {
11 | "Default": "Debug"
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/NFind/ConsoleLineSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApp1
8 | {
9 | internal class ConsoleLineSource : ILineSource
10 | {
11 | public void Close()
12 | {
13 | }
14 |
15 | public void Open()
16 | {
17 | }
18 |
19 | public string? ReadLine()
20 | {
21 | return Console.ReadLine();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/NFind/FileLineSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApp1
8 | {
9 | internal class FileLineSource(string fileName) : ILineSource
10 | {
11 | private StreamReader? reader;
12 |
13 | public void Close()
14 | {
15 | reader?.Close();
16 | }
17 |
18 | public void Open()
19 | {
20 | reader = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read));
21 | }
22 |
23 | public string? ReadLine()
24 | {
25 | if (reader == null)
26 | {
27 | throw new InvalidOperationException();
28 | }
29 |
30 | return reader.ReadLine();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/NFind/FindOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApp1
8 | {
9 | internal class FindOptions
10 | {
11 | public bool FindContainingLines { get; set; } = true;
12 | public bool CountMode { get; set; } = false;
13 | public bool ShowLineNumber { get; set; } = false;
14 | public bool CaseSensitive { get; set; } = true;
15 | public bool SkipOfflineFiles { get; set; } = true;
16 | public string StringToFind { get; set; } = string.Empty;
17 | public string Path { get; set; } = string.Empty;
18 | public bool HelpMode { get; set; } = false;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NFind/ILineSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApp1
8 | {
9 | internal interface ILineSource
10 | {
11 | void Open();
12 | void Close();
13 | string? ReadLine();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/NFind/IStringFinder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApp1
8 | {
9 | internal interface IStringFinder
10 | {
11 | Line? Next();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/NFind/Line.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApp1
8 | {
9 | internal class Line
10 | {
11 | public required string Text { get; set; }
12 | public required int LineNumber { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/NFind/LineSourceStringFinder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApp1
8 | {
9 | internal class LineSourceStringFinder : IStringFinder
10 | {
11 | private readonly ILineSource source;
12 | private readonly string stringToFind;
13 | private readonly bool findContainingLines;
14 | private readonly StringComparison stringComparision;
15 | private int line;
16 |
17 | public LineSourceStringFinder(ILineSource source, string stringToFind, bool caseSensitive, bool findContainingLines)
18 | {
19 | this.source = source;
20 | this.stringToFind = stringToFind;
21 | this.findContainingLines = findContainingLines;
22 |
23 | stringComparision = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
24 |
25 | line = 0;
26 | }
27 |
28 | public Line? Next()
29 | {
30 | var next = source.ReadLine();
31 | while (next != null)
32 | {
33 | line++;
34 |
35 | bool matched = findContainingLines ? next.Contains(stringToFind, stringComparision) : !next.Contains(stringToFind, stringComparision);
36 |
37 | if (matched)
38 | {
39 | return new Line()
40 | {
41 | LineNumber = line,
42 | Text = next
43 | };
44 | }
45 |
46 | next = source.ReadLine();
47 | }
48 |
49 | return null;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/NFind/NFind.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/NFind/Program.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | namespace ConsoleApp1
4 | {
5 | internal class Program
6 | {
7 | static void Main(string[] args)
8 | {
9 | var options = BuildOptions(args);
10 | if (options.HelpMode)
11 | {
12 | Console.WriteLine("");
13 | return;
14 | }
15 |
16 | var sources = FindSources(options.Path, options.SkipOfflineFiles);
17 | foreach (var source in sources )
18 | {
19 | var finder = GetStringFinder(source, options);
20 |
21 | var next = finder.Next();
22 | while (next != null) {
23 | Print(source, next, options.ShowLineNumber);
24 |
25 | next = finder.Next();
26 | }
27 | }
28 | }
29 |
30 | private static void Print(ILineSource source, Line next, bool showLineNumber)
31 | {
32 | if (showLineNumber)
33 | {
34 | Console.WriteLine($"[{next.LineNumber}] {next.Text}");
35 | }
36 | else
37 | {
38 | Console.WriteLine(next.Text);
39 | }
40 | }
41 |
42 | private static IStringFinder GetStringFinder(ILineSource source, FindOptions options)
43 | {
44 | return new LineSourceStringFinder(source, options.StringToFind, options.CaseSensitive, options.FindContainingLines);
45 | }
46 |
47 | private static ILineSource[] FindSources(string path, bool skipOfflineFiles)
48 | {
49 | if (string.IsNullOrEmpty(path))
50 | {
51 | return [new ConsoleLineSource()];
52 | }
53 | else
54 | {
55 | var files = Directory.GetFiles(path);
56 | if (files.Length > 0)
57 | {
58 | if (skipOfflineFiles)
59 | {
60 | files = files.Where(f => new FileInfo(f).Attributes.HasFlag(FileAttributes.Offline) == false).ToArray();
61 | }
62 |
63 | return files.Select(f => new FileLineSource(f)).ToArray();
64 | }
65 | else
66 | {
67 | return [];
68 | }
69 | }
70 | }
71 |
72 | private static FindOptions BuildOptions(string[] args)
73 | {
74 | FindOptions options = new FindOptions();
75 |
76 | foreach (string arg in args)
77 | {
78 | if ("/v".Equals(arg))
79 | {
80 | options.FindContainingLines = false;
81 | }
82 | else if ("/c".Equals(arg))
83 | {
84 | options.CountMode = true;
85 | }
86 | else if ("/n".Equals(arg))
87 | {
88 | options.ShowLineNumber = true;
89 | }
90 | else if ("/i".Equals(arg))
91 | {
92 | options.CaseSensitive = false;
93 | }
94 | else if ("/?".Equals(arg))
95 | {
96 | options.HelpMode = true;
97 | }
98 | else if ("/off".Equals(arg) || "/offline".Equals(arg))
99 | {
100 | options.SkipOfflineFiles = false;
101 | }
102 | else
103 | {
104 | if (string.IsNullOrEmpty(options.StringToFind))
105 | {
106 | options.StringToFind = arg;
107 | }
108 | else if (string.IsNullOrEmpty(options.Path))
109 | {
110 | options.Path = arg;
111 | }
112 | else
113 | {
114 | options.HelpMode = true;
115 | }
116 | }
117 | }
118 |
119 | if (string.IsNullOrEmpty(options.StringToFind))
120 | {
121 | options.HelpMode = true;
122 | }
123 |
124 | return options;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/NFind/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ConsoleApp1": {
4 | "commandName": "Project",
5 | "commandLineArgs": "\"ABC\""
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Khóa học .NET cơ bản
2 | (đã hoàn thành)
3 |
4 | Link đến khóa học: https://www.youtube.com/playlist?list=PLRLJQuuRRcFlaITD5F6XKQJxOt8QgCNAg
5 |
6 | ## Mục tiêu:
7 | - Xây dựng nền tảng cơ bản cho một .NET developer để có thể làm việc độc lập hoặc key member trong một nhóm.
8 | - Nội dung bao quát hầu như toàn bộ các thành phần nền tảng trong .NET, tạo tiền đề học tiếp lên các nhóm chủ đề chuyên biệt hơn (ASP.NET, writing API, Entity Framework, microservices...)
9 |
10 | ## Tổng quan chương trình:
11 | Khóa học sẽ bao gồm 4 nhóm nội dung cơ bản:
12 | - Các thành phần của ngôn ngữ C#: kiểu dữ liệu, cấu trúc điều khiển, lambda, sử dụng các thư viện cơ bản của .NET, hướng đối tượng...
13 | - Các thành phần/thư viện cơ bản quan trọng trong .NET: ADO.NET, Logging, Configuration, Reflection...
14 | - Các chủ đề nâng cao: Expression tree, socket programming, multi-threading programming...
15 | - Bài tập áp dụng kiến thức đã học kết hợp tìm hiểu các mẫu thiết kế (design pattern).
16 |
17 | ## Nội dung:
18 | - Cài đặt Visual Studio và viết chương trình .NET đầu tiên
19 | - Các kiểu dữ liệu trong .NET
20 | - Array
21 | - Sử dụng các cấu trúc điều khiển
22 | - Phương thức
23 | - Exception
24 | - Stream và File
25 | - Collection
26 | - Biểu thức Lambda
27 | - LINQ
28 | - Lập trình hướng đối tượng
29 | - Đa hình
30 | - Static
31 | - Interface
32 | - Bài tập 1 (thiết kế và viết lại lệnh find trong Windows)
33 | - Cài đặt SQL Server
34 | - Giới thiệu ADO.NET
35 | - CRUD với ADO.NET
36 | - Bài tập 2 (thiết kế và viết chương trình xuất dữ liệu từ database ra file)
37 | - Repository và Unit of Work design pattern
38 | - Preprocessing directives
39 | - Generic host
40 | - Logging
41 | - Using attributes
42 | - Configuration
43 | - Dependency injection
44 | - Caching
45 | - Reflection
46 | - Lập trình đa luồng (multithreading)
47 | - Lập trình bất đồng bộ với async/await
48 | - Socket programming
49 | - Bài tập: viết một HTTP 1.1 web server đơn giản hỗ trợ GET method, phục vụ được cả nội dung tĩnh và động.
50 | - Làm việc với HttpClient
51 | - Viết unit test và testable code
52 |
53 | Các video bài tập có thể được cập nhật thêm sau khi hoàn thành khóa học.
54 |
--------------------------------------------------------------------------------
/RepositorySample/DB/ShopDB.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE Shop
2 | GO
3 |
4 | USE Shop
5 | GO
6 |
7 | CREATE TABLE Products (
8 | ProductId UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
9 | ProductName NVARCHAR(255) NOT NULL,
10 | ProductPrice FLOAT NOT NULL,
11 | Quantity INT NOT NULL
12 | );
13 |
14 | CREATE TABLE Orders (
15 | OrderId UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
16 | CustomerId UNIQUEIDENTIFIER NOT NULL,
17 | OrderReference NVARCHAR (20) NOT NULL,
18 |
19 | CONSTRAINT unqOrderReference UNIQUE (OrderReference)
20 | )
21 |
22 | CREATE TABLE OrderItems (
23 | OrderItemId UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
24 | OrderId UNIQUEIDENTIFIER NOT NULL FOREIGN KEY REFERENCES Orders(OrderId),
25 | ProductId UNIQUEIDENTIFIER NOT NULL FOREIGN KEY REFERENCES Products(ProductId),
26 | Quantity INT NOT NULL,
27 | Price FLOAT NOT NULL
28 | );
29 |
--------------------------------------------------------------------------------
/RepositorySample/Entities/Order.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Entities
8 | {
9 | public class Order
10 | {
11 | public Guid Id { get; set; }
12 | public Guid CustomerId { get; set; }
13 | public required string OrderReference { get; set; }
14 | public List Items { get; set; } = [];
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RepositorySample/Entities/OrderItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Entities
8 | {
9 | public class OrderItem
10 | {
11 | public Guid Id { get; set; }
12 | public Guid OrderId { get; set; }
13 | public Guid ProductId { get; set; }
14 | public int Quantity { get; set; }
15 | public double Price { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/RepositorySample/Entities/Product.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Entities
8 | {
9 | public class Product
10 | {
11 | public required Guid Id { get; set; }
12 | public required string Name { get; set; }
13 | public required double Price { get; set; }
14 | public required int Quantity { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RepositorySample/IOrderReferenceGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample
8 | {
9 | internal interface IOrderReferenceGenerator
10 | {
11 | string Next(int length);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/RepositorySample/OrderReferenceGenerators/FixedStringOrderReferenceGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.OrderReferenceGenerators
8 | {
9 | internal class FixedStringOrderReferenceGenerator (string id) : IOrderReferenceGenerator // for testing only
10 | {
11 | public string Next(int length)
12 | {
13 | return id;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RepositorySample/OrderReferenceGenerators/RandomStringOrderReferenceGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.OrderReferenceGenerators
8 | {
9 | internal class RandomStringOrderReferenceGenerator : IOrderReferenceGenerator
10 | {
11 | private static readonly Random random = new();
12 | public string Next(int length)
13 | {
14 | if (length <= 0 || length > 20)
15 | {
16 | throw new ArgumentOutOfRangeException(nameof(length));
17 | }
18 |
19 | const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
20 | return new string(Enumerable.Repeat(chars, length)
21 | .Select(s => s[random.Next(s.Length)]).ToArray());
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/RepositorySample/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.SqlClient;
2 | using Microsoft.Extensions.Configuration;
3 | using RepositorySample.Entities;
4 | using RepositorySample.Repository;
5 | using RepositorySample.Repository.InMemory;
6 | using RepositorySample.Repository.SqlServer;
7 | using RepositorySample.UnitOfWork.SqlServer;
8 | using System.Diagnostics;
9 | using System.Runtime.InteropServices;
10 |
11 | namespace RepositorySample
12 | {
13 | internal class Program
14 | {
15 | static void Main(string[] args)
16 | {
17 | IConfigurationRoot config = new ConfigurationBuilder()
18 | .AddJsonFile("appSettings.json", optional: true)
19 | .AddJsonFile("connectionStrings.json", optional: true)
20 | .Build();
21 |
22 | string connectionString = config.GetConnectionString("ShopDatabase") ?? string.Empty; // get from ConnectionStrings
23 |
24 | if (connectionString.Length > 0 )
25 | {
26 | var sqlConnection = new SqlConnection(connectionString);
27 | sqlConnection.Open();
28 |
29 | var trans = sqlConnection.BeginTransaction();
30 |
31 | var orderRepository = new SqlServerOrderRepository(sqlConnection, trans);
32 | var productRepository = new SqlServerProductRepository(sqlConnection, trans);
33 |
34 | orderRepository.DeleteAll();
35 | productRepository.DeleteAll();
36 |
37 | InsertSampleProducts(productRepository);
38 | QueryProducts(productRepository);
39 | trans.Commit();
40 |
41 | CreateOrders(sqlConnection);
42 | QueryOrders(orderRepository);
43 |
44 | }
45 | else
46 | {
47 | var orderRepository = new InMemoryOrderRepository();
48 | var productRepository = new InMemoryProductRepository();
49 |
50 | InsertSampleProducts(productRepository);
51 | QueryProducts(productRepository);
52 | }
53 | }
54 |
55 | private static void CreateOrders(SqlConnection sqlConnection)
56 | {
57 | var uow = new SqlServerCheckoutUnitOfWork(sqlConnection);
58 | var orderId = Guid.NewGuid();
59 |
60 | uow.CreateOrder(new Order() {
61 | OrderReference = "00001",
62 | CustomerId = Guid.Empty,
63 | Id = orderId,
64 | Items =
65 | [
66 | new OrderItem()
67 | {
68 | Id = new Guid("00000000-0000-0000-0001-000000000001"),
69 | OrderId = orderId,
70 | Price = 999,
71 | ProductId = new Guid("00000000-0000-0000-0000-000000000001"),
72 | Quantity = 1
73 | },
74 | new OrderItem()
75 | {
76 | Id = new Guid("00000000-0000-0000-0001-000000000002"),
77 | OrderId = orderId,
78 | Price = 999,
79 | ProductId = new Guid("00000000-0000-0000-0000-000000000002"),
80 | Quantity = 1
81 | }
82 | ]
83 | });
84 |
85 | uow.SaveChanges();
86 | }
87 |
88 | private static void QueryOrders(IOrderRepository orderRepository)
89 | {
90 | }
91 |
92 | private static void QueryProducts(IProductRepository productRepository)
93 | {
94 | Console.WriteLine("Product.Price >= 1000");
95 | var products = productRepository.Find(new ProductFindCreterias()
96 | {
97 | MinPrice = 1000,
98 | });
99 | PrintProducts(products);
100 |
101 | Console.WriteLine("Product.Price <= 1000");
102 | products = productRepository.Find(new ProductFindCreterias()
103 | {
104 | MaxPrice = 1000,
105 | });
106 | PrintProducts(products);
107 |
108 | Console.WriteLine("Product.Name contains iPad");
109 | products = productRepository.Find(new ProductFindCreterias()
110 | {
111 | Name = "iPad",
112 | });
113 | PrintProducts(products);
114 | }
115 |
116 | private static void PrintProducts(IEnumerable products)
117 | {
118 | foreach (var product in products)
119 | {
120 | Console.WriteLine($"{product.Id, 20} {product.Name, 40} {product.Price, 10}");
121 | }
122 | }
123 |
124 | private static void InsertSampleProducts(IProductRepository productRepository)
125 | {
126 | productRepository.Add(new Product() {
127 | Id = new Guid("00000000-0000-0000-0000-000000000001"),
128 | Name = "Apple iPhone",
129 | Price = 999,
130 | Quantity = 70,
131 | });
132 |
133 | productRepository.Add(new Product()
134 | {
135 | Id = new Guid("00000000-0000-0000-0000-000000000002"),
136 | Name = "Apple iPad",
137 | Price = 799,
138 | Quantity = 10,
139 | });
140 |
141 | productRepository.Add(new Product()
142 | {
143 | Id = new Guid("00000000-0000-0000-0000-000000000003"),
144 | Name = "Apple Macbook",
145 | Price = 1399,
146 | Quantity = 20,
147 | });
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/IOrderRepository.cs:
--------------------------------------------------------------------------------
1 | using RepositorySample.Entities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RepositorySample.Repository
9 | {
10 | internal interface IOrderRepository
11 | {
12 | Order? FindById(Guid id);
13 | Order? FindByReference(string reference);
14 | IEnumerable Find(OrderFindCreterias creterias, OrderSortBy sortBy = OrderSortBy.ReferenceAscending);
15 | Order? Add(Order order);
16 | int DeleteAll();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/IProductRepository.cs:
--------------------------------------------------------------------------------
1 | using RepositorySample.Entities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RepositorySample.Repository
9 | {
10 | internal interface IProductRepository
11 | {
12 | Product? FindById(Guid id);
13 | IEnumerable Find(ProductFindCreterias creterias, ProductSortBy sortBy = ProductSortBy.NameAscending);
14 | Product? Add(Product product);
15 | int DeleteAll();
16 | int Update(Product product);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/InMemory/InMemoryOrderRepository.cs:
--------------------------------------------------------------------------------
1 | using RepositorySample.Entities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RepositorySample.Repository.InMemory
9 | {
10 | internal class InMemoryOrderRepository : IOrderRepository
11 | {
12 | private readonly List orders = [];
13 |
14 | public Order? Add(Order order)
15 | {
16 | ArgumentNullException.ThrowIfNull(order, nameof(order));
17 |
18 | if (orders.Any(o => o.OrderReference == order.OrderReference))
19 | {
20 | throw new ArgumentException("Duplicated order reference");
21 | }
22 |
23 | orders.Add(order);
24 |
25 | return order;
26 | }
27 |
28 | public int DeleteAll()
29 | {
30 | var c = orders.Count;
31 |
32 | orders.Clear();
33 | return c;
34 | }
35 |
36 | public IEnumerable Find(OrderFindCreterias creterias, OrderSortBy sortBy = OrderSortBy.ReferenceAscending)
37 | {
38 | var query = from o in orders select o;
39 |
40 | if (creterias.Ids.Any())
41 | {
42 | query = query.Where(o => creterias.Ids.Contains(o.Id));
43 | }
44 |
45 | if (creterias.CustomerIds.Any())
46 | {
47 | query = query.Where(o => creterias.CustomerIds.Contains(o.CustomerId));
48 | }
49 |
50 | if (creterias.Skip > 0)
51 | {
52 | query = query.Skip(creterias.Skip);
53 | }
54 |
55 | if (creterias.Take > 0 && creterias.Take != int.MaxValue)
56 | {
57 | query = query.Take(creterias.Take);
58 | }
59 |
60 | if (sortBy == OrderSortBy.ReferenceAscending)
61 | {
62 | query = query.OrderBy(o => o.OrderReference);
63 | }
64 | else
65 | {
66 | query = query.OrderByDescending(o => o.OrderReference);
67 | }
68 |
69 | return query;
70 | }
71 |
72 | public Order? FindById(Guid id)
73 | {
74 | return orders.Where(o => o.Id == id).FirstOrDefault();
75 | }
76 |
77 | public Order? FindByReference(string reference)
78 | {
79 | return orders.Where(o => o.OrderReference == reference).FirstOrDefault();
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/InMemory/InMemoryProductRepository.cs:
--------------------------------------------------------------------------------
1 | using RepositorySample.Entities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RepositorySample.Repository.InMemory
9 | {
10 | internal class InMemoryProductRepository : IProductRepository
11 | {
12 | private readonly List products = [];
13 |
14 | public Product? Add(Product product)
15 | {
16 | products.Add(product);
17 |
18 | return product;
19 | }
20 |
21 | public int DeleteAll()
22 | {
23 | int count = products.Count;
24 | products.Clear();
25 | return count;
26 | }
27 |
28 | public IEnumerable Find(ProductFindCreterias creterias, ProductSortBy sortBy = ProductSortBy.NameAscending)
29 | {
30 | var query = from o in products select o;
31 |
32 | if (creterias.Ids.Any())
33 | {
34 | query = query.Where(p => creterias.Ids.Contains(p.Id));
35 | }
36 |
37 | if (creterias.MinPrice != double.MinValue)
38 | {
39 | query = query.Where(p => p.Price >= creterias.MinPrice);
40 | }
41 |
42 | if (creterias.MaxPrice != double.MaxValue)
43 | {
44 | query = query.Where(p => p.Price <= creterias.MaxPrice);
45 | }
46 |
47 | if (!string.IsNullOrEmpty(creterias.Name))
48 | {
49 | query = query.Where(p => p.Name.Contains(creterias.Name, StringComparison.OrdinalIgnoreCase));
50 | }
51 |
52 | if (creterias.Skip > 0)
53 | {
54 | query = query.Skip(creterias.Skip);
55 | }
56 |
57 | if (creterias.Take > 0 && creterias.Take != int.MaxValue)
58 | {
59 | query = query.Take(creterias.Take);
60 | }
61 |
62 | return query;
63 | }
64 |
65 | public Product? FindById(Guid id)
66 | {
67 | return products.Where(p => p.Id == id).FirstOrDefault();
68 | }
69 |
70 | public int Update(Product product)
71 | {
72 | var p = products.Where(p => p.Id == product.Id).FirstOrDefault();
73 | if (p != null)
74 | {
75 | products.Remove(p);
76 | products.Add(product);
77 |
78 | return 1;
79 | }
80 |
81 | return 0;
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/OrderFindCreterias.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Repository
8 | {
9 | public class OrderFindCreterias: PagingCreterias
10 | {
11 | public IEnumerable Ids { get; set; } = Enumerable.Empty();
12 | public IEnumerable CustomerIds { get; set; } = Enumerable.Empty();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/OrderSortBy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Repository
8 | {
9 | public enum OrderSortBy
10 | {
11 | ReferenceAscending,
12 | ReferenceDescending,
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/PagingCreterias.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Repository
8 | {
9 | public class PagingCreterias
10 | {
11 | public int Skip { get; set; } = 0;
12 | public int Take { get; set; } = int.MaxValue;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/ProductFindCreterias.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Repository
8 | {
9 | public class ProductFindCreterias: PagingCreterias
10 | {
11 | public double MinPrice { get; set; } = double.MinValue;
12 | public double MaxPrice { get; set; } = double.MaxValue;
13 | public IEnumerable Ids { get; set; } = Enumerable.Empty();
14 | public string Name { get; set; } = string.Empty;
15 |
16 | public static ProductFindCreterias Empty => new()
17 | {
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/ProductSortBy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace RepositorySample.Repository
8 | {
9 | public enum ProductSortBy
10 | {
11 | NameAscending,
12 | NameDescending,
13 | PriceAscending,
14 | PriceDescending,
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/SqlServer/SqlServerOrderRepository.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.SqlClient;
2 | using RepositorySample.Entities;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace RepositorySample.Repository.SqlServer
11 | {
12 | internal class SqlServerOrderRepository : IOrderRepository
13 | {
14 | private const string INSERT_COMMAND = "INSERT INTO Orders VALUES (@OrderId, @CustomerId, @OrderReference)";
15 | private const string FIND_BY_ID_QUERY = "SELECT CustomerId, OrderReference FROM Orders WHERE OrderId = @OrderId";
16 | private const string FIND_BY_REFERENCE_QUERY = "SELECT OrderId, CustomerId FROM Orders WHERE OrderReference = @OrderReference";
17 | private const string SELECT = "SELECT ";
18 | private const string FIND_ALL = "OrderId, CustomerId, OrderReference FROM Orders WHERE (1 = 1)";
19 | private const string FIND_ITEMS = "SELECT OrderItemId, ProductId, Quantity, Price FROM OrderItems WHERE OrderId = @OrderId";
20 | private const string INSERT_ITEM_COMMAND = "INSERT INTO OrderItems VALUES (@OrderItemId, @OrderId, @ProductId, @Quantity, @Price)";
21 | private const string DELETE_ALL = "DELETE FROM OrderItems; DELETE FROM Orders;";
22 |
23 | private readonly SqlConnection connection;
24 | private readonly SqlTransaction? transaction;
25 |
26 | public SqlServerOrderRepository(SqlConnection connection, SqlTransaction? transaction)
27 | {
28 | this.connection = connection ?? throw new ArgumentNullException(nameof(connection));
29 | this.transaction = transaction;
30 | }
31 |
32 | public Order? Add(Order order)
33 | {
34 | var cmd = connection.CreateCommand();
35 | cmd.CommandText = INSERT_COMMAND;
36 | if (transaction != null)
37 | {
38 | cmd.Transaction = transaction;
39 | }
40 |
41 | cmd.Parameters.Add(new SqlParameter("@OrderId", SqlDbType.UniqueIdentifier)).Value = order.Id;
42 | cmd.Parameters.Add(new SqlParameter("@CustomerId", SqlDbType.UniqueIdentifier)).Value = order.CustomerId;
43 | cmd.Parameters.Add(new SqlParameter("@OrderReference", SqlDbType.NVarChar, 20)).Value = order.OrderReference;
44 |
45 | if (cmd.ExecuteNonQuery() > 0)
46 | {
47 | cmd.CommandText = INSERT_ITEM_COMMAND;
48 | cmd.Parameters.Clear();
49 |
50 | cmd.Parameters.Add(new SqlParameter("@OrderItemId", SqlDbType.UniqueIdentifier));
51 | cmd.Parameters.Add(new SqlParameter("@ProductId", SqlDbType.UniqueIdentifier));
52 | cmd.Parameters.Add(new SqlParameter("@Quantity", SqlDbType.Int));
53 | cmd.Parameters.Add(new SqlParameter("@Price", SqlDbType.Float));
54 |
55 | cmd.Parameters.Add(new SqlParameter("@OrderId", SqlDbType.UniqueIdentifier)).Value = order.Id;
56 |
57 | foreach (var item in order.Items)
58 | {
59 | cmd.Parameters["@OrderItemId"].Value = item.Id;
60 | cmd.Parameters["@ProductId"].Value = item.ProductId;
61 | cmd.Parameters["@Quantity"].Value = item.Quantity;
62 | cmd.Parameters["@Price"].Value = item.Price;
63 |
64 | if (cmd.ExecuteNonQuery() == 0)
65 | {
66 | throw new Exception("Error inserting OrderItems");
67 | }
68 | }
69 |
70 | return order;
71 | }
72 | else
73 | {
74 | return null;
75 | }
76 | }
77 |
78 | public IEnumerable Find(OrderFindCreterias creterias, OrderSortBy sortBy = OrderSortBy.ReferenceAscending)
79 | {
80 | var cmd = connection.CreateCommand();
81 | if (transaction != null)
82 | {
83 | cmd.Transaction = transaction;
84 | }
85 |
86 | var sql = new StringBuilder(SELECT);
87 | if (creterias.Take > 0)
88 | {
89 | sql.Append("TOP ");
90 | sql.Append(creterias.Take);
91 | sql.Append(' ');
92 | }
93 | sql.Append(FIND_ALL);
94 |
95 | if (creterias.Ids.Any())
96 | {
97 | sql.Append(" AND OrderId IN (");
98 | sql.Append(string.Join(',', creterias.Ids.Select(id => $"'{id}'")));
99 | sql.Append(')');
100 | }
101 |
102 | if (creterias.CustomerIds.Any())
103 | {
104 | sql.Append(" AND CustomerId IN (");
105 | sql.Append(string.Join(',', creterias.CustomerIds.Select(id => $"'{id}'")));
106 | sql.Append(')');
107 | }
108 |
109 | if (sortBy == OrderSortBy.ReferenceAscending)
110 | {
111 | sql.Append(" ORDER BY OrderReference");
112 | }
113 | else
114 | {
115 | sql.Append(" ORDER BY OrderReference DESC");
116 | }
117 |
118 | if (creterias.Skip > 0)
119 | {
120 | sql.Append(" OFFSET ");
121 | sql.Append(creterias.Skip);
122 | sql.Append(" ROWS");
123 | }
124 |
125 | cmd.CommandText = sql.ToString();
126 | using var reader = cmd.ExecuteReader();
127 | var orders = new List();
128 |
129 | if (reader != null)
130 | {
131 | while (reader.Read())
132 | {
133 | orders.Add(new Order()
134 | {
135 | Id = reader.GetGuid(0),
136 | CustomerId = reader.GetGuid(1),
137 | OrderReference = reader.GetString(2)
138 | });
139 | }
140 |
141 | reader.Close();
142 | }
143 |
144 | foreach (var order in orders)
145 | {
146 | LoadOrderItems(order);
147 | }
148 |
149 | return orders;
150 | }
151 |
152 | private void LoadOrderItems(Order order)
153 | {
154 | var cmd = connection.CreateCommand();
155 | if (transaction != null)
156 | {
157 | cmd.Transaction = transaction;
158 | }
159 |
160 | cmd.CommandText = FIND_ITEMS;
161 | cmd.Parameters.Add(new SqlParameter("@OrderId", System.Data.SqlDbType.UniqueIdentifier)).Value = order.Id;
162 |
163 | using var reader = cmd.ExecuteReader();
164 | if (reader != null)
165 | {
166 | while (reader.Read())
167 | {
168 | order.Items.Add(new OrderItem()
169 | {
170 | Id = reader.GetGuid(0),
171 | ProductId = reader.GetGuid(1),
172 | Quantity = reader.GetInt32(2),
173 | Price = reader.GetDouble(3),
174 | });
175 | }
176 |
177 | reader.Close();
178 | }
179 | }
180 |
181 | public Order? FindById(Guid id)
182 | {
183 | var cmd = connection.CreateCommand();
184 | cmd.CommandText = FIND_BY_ID_QUERY;
185 | if (transaction != null)
186 | {
187 | cmd.Transaction = transaction;
188 | }
189 |
190 | cmd.Parameters.Add(new SqlParameter("@OrderId", System.Data.SqlDbType.UniqueIdentifier)).Value = id;
191 |
192 | var reader = cmd.ExecuteReader();
193 | if (reader != null && reader.Read())
194 | {
195 | return new Order()
196 | {
197 | Id = id,
198 | CustomerId = reader.GetGuid(0),
199 | OrderReference = reader.GetString(1)
200 | };
201 | }
202 | else
203 | {
204 | return null;
205 | }
206 | }
207 |
208 | public Order? FindByReference(string reference)
209 | {
210 | var cmd = connection.CreateCommand();
211 | cmd.CommandText = FIND_BY_REFERENCE_QUERY;
212 | if (transaction != null)
213 | {
214 | cmd.Transaction = transaction;
215 | }
216 |
217 | cmd.Parameters.Add(new SqlParameter("@OrderReference", System.Data.SqlDbType.NVarChar, 20)).Value = reference;
218 |
219 | var reader = cmd.ExecuteReader();
220 | if (reader != null && reader.Read())
221 | {
222 | return new Order()
223 | {
224 | Id = reader.GetGuid(0),
225 | CustomerId = reader.GetGuid(1),
226 | OrderReference = reference
227 | };
228 | }
229 | else
230 | {
231 | return null;
232 | }
233 | }
234 |
235 | public int DeleteAll()
236 | {
237 | var cmd = connection.CreateCommand();
238 | cmd.CommandText = DELETE_ALL;
239 | if (transaction != null)
240 | {
241 | cmd.Transaction = transaction;
242 | }
243 |
244 | return cmd.ExecuteNonQuery();
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/RepositorySample/Repository/SqlServer/SqlServerProductRepository.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.SqlClient;
2 | using RepositorySample.Entities;
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 | using System.Data;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace RepositorySample.Repository.SqlServer
12 | {
13 | internal class SqlServerProductRepository : IProductRepository
14 | {
15 | private const string INSERT_COMMAND = "INSERT INTO Products VALUES (@ProductId, @ProductName, @ProductPrice, @Quantity)";
16 | private const string UPDATE_COMMAND = "UPDATE Products SET ProductName = @ProductName, ProductPrice = @ProductPrice, Quantity = @Quantity WHERE ProductId = @ProductId";
17 | private const string FIND_BY_ID_QUERY = "SELECT ProductName, ProductPrice, Quantity FROM Products WHERE ProductId = @ProductId";
18 | private const string SELECT = "SELECT ";
19 | private const string FIND_ALL = "ProductId, ProductName, ProductPrice, Quantity FROM Products WHERE (1 = 1)";
20 | private const string DELETE_ALL = "DELETE FROM Products";
21 |
22 | private readonly SqlConnection connection;
23 | private readonly SqlTransaction? transaction;
24 |
25 | public SqlServerProductRepository(SqlConnection connection, SqlTransaction? transaction)
26 | {
27 | this.connection = connection ?? throw new ArgumentNullException(nameof(connection));
28 | this.transaction = transaction;
29 | }
30 |
31 | public Product? Add(Product product)
32 | {
33 | var cmd = connection.CreateCommand();
34 | cmd.CommandText = INSERT_COMMAND;
35 | if (transaction != null)
36 | {
37 | cmd.Transaction = transaction;
38 | }
39 |
40 | cmd.Parameters.Add(new SqlParameter("@ProductId", System.Data.SqlDbType.UniqueIdentifier)).Value = product.Id;
41 | cmd.Parameters.Add(new SqlParameter("@ProductName", SqlDbType.NVarChar, 255)).Value = product.Name;
42 | cmd.Parameters.Add(new SqlParameter("@ProductPrice", System.Data.SqlDbType.Float)).Value = product.Price;
43 | cmd.Parameters.Add(new SqlParameter("@Quantity", System.Data.SqlDbType.Int)).Value = product.Quantity;
44 |
45 | if (cmd.ExecuteNonQuery() > 0)
46 | {
47 | return product;
48 | }
49 | else
50 | {
51 | return null;
52 | }
53 | }
54 |
55 | public int DeleteAll()
56 | {
57 | var cmd = connection.CreateCommand();
58 | cmd.CommandText = DELETE_ALL;
59 | if (transaction != null)
60 | {
61 | cmd.Transaction = transaction;
62 | }
63 |
64 | return cmd.ExecuteNonQuery();
65 | }
66 |
67 | public IEnumerable Find(ProductFindCreterias creterias, ProductSortBy sortBy = ProductSortBy.NameAscending)
68 | {
69 | var cmd = connection.CreateCommand();
70 | if (transaction != null)
71 | {
72 | cmd.Transaction = transaction;
73 | }
74 |
75 | var sql = new StringBuilder(SELECT);
76 | if (creterias.Take > 0)
77 | {
78 | sql.Append("TOP ");
79 | sql.Append(creterias.Take);
80 | sql.Append(' ');
81 | }
82 | sql.Append(FIND_ALL);
83 |
84 | if (creterias.Ids.Any())
85 | {
86 | sql.Append(" AND ProductId IN (");
87 | sql.Append(string.Join(',', creterias.Ids.Select(id => $"'{id}'")));
88 | sql.Append(')');
89 | }
90 |
91 | if (creterias.MinPrice != double.MinValue)
92 | {
93 | sql.Append(" AND ProductPrice >= @MinPrice");
94 | cmd.Parameters.Add(new SqlParameter("@MinPrice", SqlDbType.Float)).Value = creterias.MinPrice;
95 | }
96 |
97 | if (creterias.MaxPrice != double.MaxValue)
98 | {
99 | sql.Append(" AND ProductPrice <= @MaxPrice");
100 | cmd.Parameters.Add(new SqlParameter("@MaxPrice", SqlDbType.Float)).Value = creterias.MaxPrice;
101 | }
102 |
103 | if (!string.IsNullOrEmpty(creterias.Name))
104 | {
105 | sql.Append(" AND ProductName LIKE @ProductName");
106 | cmd.Parameters.Add(new SqlParameter("@ProductName", SqlDbType.NVarChar, 255)).Value = "%" + creterias.Name + "%";
107 | }
108 |
109 | if (sortBy == ProductSortBy.PriceAscending)
110 | {
111 | sql.Append(" ORDER BY ProductPrice");
112 | }
113 | else if (sortBy == ProductSortBy.PriceDescending)
114 | {
115 | sql.Append(" ORDER BY ProductPrice DESC");
116 | }
117 | else if (sortBy == ProductSortBy.NameDescending)
118 | {
119 | sql.Append(" ORDER BY ProductName");
120 | }
121 | else
122 | {
123 | sql.Append(" ORDER BY ProductName DESC");
124 | }
125 |
126 | if (creterias.Skip > 0)
127 | {
128 | sql.Append(" OFFSET ");
129 | sql.Append(creterias.Skip);
130 | sql.Append(" ROWS");
131 | }
132 |
133 | cmd.CommandText = sql.ToString();
134 | using var reader = cmd.ExecuteReader();
135 | var orders = new List();
136 |
137 | if (reader != null)
138 | {
139 | while (reader.Read())
140 | {
141 | orders.Add(new Product()
142 | {
143 | Id = reader.GetGuid(0),
144 | Name = reader.GetString(1),
145 | Price = reader.GetDouble(2),
146 | Quantity = reader.GetInt32(3),
147 | });
148 | }
149 | }
150 |
151 | return orders;
152 | }
153 |
154 | public Product? FindById(Guid id)
155 | {
156 | var cmd = connection.CreateCommand();
157 | cmd.CommandText = FIND_BY_ID_QUERY;
158 | if (transaction != null)
159 | {
160 | cmd.Transaction = transaction;
161 | }
162 |
163 | cmd.Parameters.Add(new SqlParameter("@ProductId", System.Data.SqlDbType.UniqueIdentifier)).Value = id;
164 |
165 | using var reader = cmd.ExecuteReader();
166 | if (reader != null && reader.Read())
167 | {
168 | return new Product()
169 | {
170 | Id = id,
171 | Name = reader.GetString(0),
172 | Price = reader.GetDouble(1),
173 | Quantity = reader.GetInt32(2),
174 | };
175 | }
176 | else
177 | {
178 | return null;
179 | }
180 | }
181 |
182 | public int Update(Product product)
183 | {
184 | var cmd = connection.CreateCommand();
185 | cmd.CommandText = UPDATE_COMMAND;
186 | if (transaction != null)
187 | {
188 | cmd.Transaction = transaction;
189 | }
190 |
191 | cmd.Parameters.Add(new SqlParameter("@ProductId", System.Data.SqlDbType.UniqueIdentifier)).Value = product.Id;
192 | cmd.Parameters.Add(new SqlParameter("@ProductName", SqlDbType.NVarChar, 255)).Value = product.Name;
193 | cmd.Parameters.Add(new SqlParameter("@ProductPrice", System.Data.SqlDbType.Float)).Value = product.Price;
194 | cmd.Parameters.Add(new SqlParameter("@Quantity", System.Data.SqlDbType.Int)).Value = product.Quantity;
195 |
196 | return cmd.ExecuteNonQuery();
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/RepositorySample/RepositorySample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Always
23 |
24 |
25 | Always
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/RepositorySample/UnitOfWork/ICheckoutUnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using RepositorySample.Entities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RepositorySample.UnitOfWork
9 | {
10 | internal interface ICheckoutUnitOfWork
11 | {
12 | void CreateOrder(Order order);
13 | void SaveChanges();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/RepositorySample/UnitOfWork/SqlServer/SqlServerCheckoutUnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.SqlClient;
2 | using RepositorySample.Entities;
3 | using RepositorySample.Repository;
4 | using RepositorySample.Repository.SqlServer;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace RepositorySample.UnitOfWork.SqlServer
12 | {
13 | internal class SqlServerCheckoutUnitOfWork : ICheckoutUnitOfWork
14 | {
15 | private readonly SqlConnection connection;
16 | private readonly SqlTransaction transaction;
17 | private SqlServerOrderRepository? orderRepository;
18 | private SqlServerProductRepository? productRepository;
19 |
20 | public SqlServerCheckoutUnitOfWork(SqlConnection connection)
21 | {
22 | this.connection = connection ?? throw new ArgumentNullException(nameof(connection));
23 | transaction = connection.BeginTransaction();
24 | }
25 |
26 | public SqlServerOrderRepository OrderRepository
27 | {
28 | get
29 | {
30 | orderRepository ??= new SqlServerOrderRepository(connection, transaction);
31 |
32 | return orderRepository;
33 | }
34 | }
35 |
36 | public SqlServerProductRepository ProductRepository
37 | {
38 | get
39 | {
40 | productRepository ??= new SqlServerProductRepository(connection, transaction);
41 |
42 | return productRepository;
43 | }
44 | }
45 |
46 | public void CreateOrder(Order order)
47 | {
48 | foreach (var item in order.Items)
49 | {
50 | var product = ProductRepository.FindById(item.ProductId) ?? throw new Exception($"Product not found: {item.ProductId}");
51 |
52 | if (product.Quantity - item.Quantity < 0)
53 | {
54 | throw new Exception("product.Quantity < item.Quantity");
55 | }
56 | product.Quantity -= item.Quantity;
57 |
58 | ProductRepository.Update(product);
59 | }
60 |
61 | OrderRepository.Add(order);
62 | }
63 |
64 | public void SaveChanges()
65 | {
66 | transaction.Commit();
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/RepositorySample/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "ShopDatabase": "Server=.;Database=Shop;Trusted_Connection=True;TrustServerCertificate=True;"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Task-Sample/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Task_Sample
4 | {
5 | internal class Program
6 | {
7 | static async Task Main(string[] args)
8 | {
9 | var stopwatch = new Stopwatch();
10 |
11 | stopwatch.Start();
12 | var t1 = Delay1Async();
13 | var t2 = Delay2Async();
14 | var t3 = Delay3Async();
15 |
16 | var t123 = Task.WhenAny(t1, t2, t3);
17 | await t123;
18 |
19 | var t = CalculateResult(10);
20 | await t;
21 |
22 | stopwatch.Stop();
23 | Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}");
24 | }
25 |
26 | static async Task Delay1Async()
27 | {
28 | Console.WriteLine("Delay1...");
29 | await Task.Delay(1000);
30 | Console.WriteLine("Delay1 done...");
31 | }
32 |
33 | static async Task Delay2Async()
34 | {
35 | Console.WriteLine("Delay2...");
36 | await Task.Delay(2000);
37 | Console.WriteLine("Delay2 done...");
38 | }
39 |
40 | static async Task Delay3Async()
41 | {
42 | Console.WriteLine("Delay3...");
43 | await Task.Delay(3000);
44 | Console.WriteLine("Delay3 done...");
45 | }
46 |
47 | static void RunMethod()
48 | {
49 | for (int i = 0; i < 10; i++) {
50 | Console.WriteLine($"i = {i}");
51 | Task.Delay(500).Wait();
52 | }
53 | }
54 |
55 | static Task CalculateResult(int n)
56 | {
57 | if (n == 0) return Task.FromResult(0.0);
58 |
59 | return Task.FromResult(Math.Pow(10, n));
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Task-Sample/Task-Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | Task_Sample
7 | enable
8 | enable
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ThreadPoolSample/MyThreadPool.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ThreadPoolSample
9 | {
10 | internal class MyThreadPool
11 | {
12 | private static ulong currentId = 0;
13 | private static readonly BlockingCollection<(ulong, Action, ExecutionContext?)> actions = [];
14 |
15 | public static void QueueUserWorkItem(Action action) => actions.Add((Interlocked.Increment(ref currentId), action, ExecutionContext.Capture()));
16 |
17 | static MyThreadPool()
18 | {
19 | for (int i = 0; i < Environment.ProcessorCount; i++)
20 | {
21 | var t = new Thread((threadId) =>
22 | {
23 | Console.WriteLine($"Thread #{Environment.CurrentManagedThreadId} started!");
24 |
25 | while (true)
26 | {
27 | (ulong id, Action action, ExecutionContext? context) = actions.Take();
28 |
29 | Console.WriteLine($"Thread #{Environment.CurrentManagedThreadId} executes task #{id}");
30 |
31 | if (context is null)
32 | {
33 | action();
34 | }
35 | else
36 | {
37 | ExecutionContext.Run(context, state => ((Action)state!).Invoke(), action);
38 | }
39 | }
40 | })
41 | {
42 | IsBackground = true
43 | };
44 | t.Start();
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/ThreadPoolSample/Program.cs:
--------------------------------------------------------------------------------
1 | namespace ThreadPoolSample
2 | {
3 | internal class Program
4 | {
5 | static void Main(string[] args)
6 | {
7 | for (int i = 1; i <= 100; i++) {
8 | int capturedI = i;
9 |
10 | MyThreadPool.QueueUserWorkItem(() => {
11 | Console.WriteLine($"{capturedI} (thread #{Environment.CurrentManagedThreadId})");
12 | });
13 | }
14 |
15 | Console.ReadLine();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ThreadPoolSample/ThreadPoolSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/WebServer.Middleware.StaticContent/StaticContentMiddleware.cs:
--------------------------------------------------------------------------------
1 | using WebServer.SDK;
2 | using WebServer.SDK.ResponseBodyWriters;
3 |
4 | namespace WebServer.Middleware.StaticContent
5 | {
6 | public class StaticContentMiddleware : IMiddleware
7 | {
8 | private string root = "c:\\wwwroot";
9 |
10 | public async Task InvokeAsync(MiddlewareContext context, ICallable next, CancellationToken cancellationToken)
11 | {
12 | if (context.Request.Method == WMethods.Get)
13 | {
14 | var url = context.Request.Url;
15 | if (url.StartsWith("/"))
16 | {
17 | url = url.Substring(1);
18 | }
19 | url = url.Replace("/", "\\");
20 |
21 | var file = new FileInfo(Path.Combine(root, url));
22 | if (file.Exists)
23 | {
24 | var fileContent = await File.ReadAllTextAsync(file.FullName);
25 | context.Response.ContentType = "text/html";
26 | context.Response.ContentLength = fileContent.Length;
27 | context.Response.ResponseBodyWriter = new StringResponseBodyWriter(fileContent);
28 |
29 | context.Response.ResponseCode = HttpResponseCodes.OK;
30 | }
31 | else
32 | {
33 | await next.InvokeAsync(context, cancellationToken);
34 | }
35 | }
36 | else
37 | {
38 | await next.InvokeAsync(context, cancellationToken);
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/WebServer.Middleware.StaticContent/WebServer.Middleware.StaticContent.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/HttpReasonPhrases.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public class HttpReasonPhrases
10 | {
11 | public static string GetByCode(HttpResponseCodes code) => code switch
12 | {
13 | HttpResponseCodes.OK => "OK",
14 | HttpResponseCodes.NotFound => "Not Found",
15 | _ => throw new ArgumentOutOfRangeException("Unknown HTTP status code")
16 | };
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/HttpResponseCodes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public enum HttpResponseCodes: int
10 | {
11 | OK = 200,
12 | NotFound = 404
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/ICallable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public interface ICallable
10 | {
11 | Task InvokeAsync(MiddlewareContext context, CancellationToken cancellationToken);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/IMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public interface IMiddleware
10 | {
11 | Task InvokeAsync(MiddlewareContext context, ICallable next, CancellationToken cancellationToken);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/IRequestReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public interface IRequestReader
10 | {
11 | Task ReadRequestAsync(CancellationToken cancellationToken);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/IRequestReaderFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Sockets;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace WebServer.SDK
9 | {
10 | public interface IRequestReaderFactory
11 | {
12 | IRequestReader Create(Socket socket);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/IResponseBodyWriter.cs:
--------------------------------------------------------------------------------
1 | namespace WebServer.SDK
2 | {
3 | public interface IResponseBodyWriter
4 | {
5 | Task WriteAsync (Stream bodyStream);
6 | }
7 | }
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/IResponseWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Sockets;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace WebServer.SDK
9 | {
10 | public interface IResponseWriter
11 | {
12 | Task SendResponseAsync(WResponse response);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/IResponseWriterFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Sockets;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace WebServer.SDK
9 | {
10 | public interface IResponseWriterFactory
11 | {
12 | IResponseWriter Create(Socket socket);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/MiddlewareContext.cs:
--------------------------------------------------------------------------------
1 | namespace WebServer.SDK
2 | {
3 | public class MiddlewareContext
4 | {
5 | public required WRequest Request { get; init; }
6 | public required WResponse Response { get; init; }
7 | }
8 | }
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/NullResponseBodyWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public class NullResponseBodyWriter : IResponseBodyWriter
10 | {
11 | public Task WriteAsync(Stream bodyStream)
12 | {
13 | return Task.CompletedTask;
14 | }
15 |
16 | public static readonly NullResponseBodyWriter Instance = new();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/ResponseBodyWriters/StringResponseBodyWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using WebServer.SDK;
7 |
8 | namespace WebServer.SDK.ResponseBodyWriters
9 | {
10 | public class StringResponseBodyWriter : IResponseBodyWriter
11 | {
12 | private readonly byte[] contentBytes;
13 |
14 | public StringResponseBodyWriter(string content)
15 | {
16 | contentBytes = Encoding.UTF8.GetBytes(content);
17 | }
18 |
19 | public async Task WriteAsync(Stream bodyStream)
20 | {
21 | await bodyStream.WriteAsync(contentBytes);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/WHeader.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Primitives;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace WebServer.SDK
9 | {
10 | public class WHeader
11 | {
12 | public required string Name { get; set; }
13 | public required StringValues Values { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/WMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public enum WMethods
10 | {
11 | Get
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/WRequest.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Primitives;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.Http.Headers;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace WebServer.SDK
10 | {
11 | public class WRequest
12 | {
13 | public required WMethods Method { get; set; }
14 | public required string Url { get; set; }
15 | public required string HttpVersion { get; set; }
16 | public required string Host { get; set; }
17 | public bool IsKeepAlive { get; set; } = false;
18 | public required IDictionary Headers { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/WResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.SDK
8 | {
9 | public class WResponse
10 | {
11 | public string HttpVersion { get; set; } = "HTTP/1.1";
12 | public HttpResponseCodes ResponseCode { get; set; } = HttpResponseCodes.NotFound;
13 | public string ReasonPhrase { get; set; } = string.Empty;
14 | public int ContentLength { get; set; }
15 | public string ContentType { get; set; } = "text/html";
16 | public IResponseBodyWriter ResponseBodyWriter { get; set; } = NullResponseBodyWriter.Instance;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/WebServer/WebServer.SDK/WebServer.SDK.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/Callable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using WebServer.SDK;
7 |
8 | namespace WebServer.Server
9 | {
10 | internal class Callable : ICallable
11 | {
12 | public required IMiddleware Middleware { get; set; }
13 | public ICallable? Next { get; set; }
14 |
15 | public async Task InvokeAsync(MiddlewareContext context, CancellationToken cancellationToken)
16 | {
17 | if (Next == null)
18 | {
19 | throw new InvalidOperationException();
20 | }
21 | Console.WriteLine($"calling to {Middleware}");
22 | await Middleware.InvokeAsync(context, Next, cancellationToken);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/ClientConnection.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace WebServer.Server
3 | {
4 | internal class ClientConnection
5 | {
6 | public required Task HandlerTask { get; internal set; }
7 | }
8 | }
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/DefaultMiddlewares/NotFoundMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Metadata;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using WebServer.SDK;
8 | using WebServer.SDK.ResponseBodyWriters;
9 |
10 | namespace WebServer.Server.DefaultMiddlewares
11 | {
12 | internal class NotFoundMiddleware : IMiddleware
13 | {
14 | private static readonly IResponseBodyWriter EmptyBodyContentWriter = new StringResponseBodyWriter("");
15 |
16 | public Task InvokeAsync(MiddlewareContext context, ICallable next, CancellationToken cancellationToken)
17 | {
18 | context.Response.ContentLength = 0;
19 | context.Response.ResponseCode = HttpResponseCodes.NotFound;
20 | context.Response.ContentType = "text/html";
21 | context.Response.ResponseBodyWriter = EmptyBodyContentWriter;
22 |
23 | return Task.CompletedTask;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/DefaultMiddlewares/NullMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using WebServer.SDK;
7 |
8 | namespace WebServer.Server.DefaultMiddlewares
9 | {
10 | internal class NullMiddleware : IMiddleware
11 | {
12 | public Task InvokeAsync(MiddlewareContext context, ICallable next, CancellationToken cancellationToken)
13 | {
14 | throw new NotImplementedException();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/HeaderLine.cs:
--------------------------------------------------------------------------------
1 |
2 | using WebServer.SDK;
3 |
4 | namespace WebServer.Server
5 | {
6 | internal class HeaderLine
7 | {
8 | internal static bool TryParse(string? headerLine, out WHeader? header)
9 | {
10 | ArgumentNullException.ThrowIfNull(headerLine, nameof(headerLine));
11 |
12 | var idx = headerLine.IndexOf(':');
13 | if (idx < 0) {
14 | header = default;
15 | return false;
16 | }
17 |
18 | header = new WHeader() {
19 | Name = headerLine[..idx].Trim(),
20 | Values = headerLine[(idx + 1)..].Trim()
21 | };
22 |
23 | return true;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/NullCallable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using WebServer.SDK;
7 |
8 | namespace WebServer.Server
9 | {
10 | internal class NullCallable : ICallable
11 | {
12 | public Task InvokeAsync(MiddlewareContext context, CancellationToken cancellationToken)
13 | {
14 | return Task.CompletedTask;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/Program.cs:
--------------------------------------------------------------------------------
1 | using WebServer.SDK;
2 | using WebServer.Server;
3 |
4 | var builder = Host.CreateApplicationBuilder(args);
5 | builder.Services.AddHostedService();
6 |
7 | builder.Services.AddSingleton(builder.Configuration.GetRequiredSection("Server").Get() ?? new WebServerOptions());
8 | builder.Services.AddSingleton(services => new RequestReaderFactory(services.GetRequiredService()));
9 | builder.Services.AddSingleton(services => new ResponseWriterFactory(services.GetRequiredService()));
10 |
11 | var host = builder.Build();
12 | host.Run();
13 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "WebServer.Server": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "environmentVariables": {
8 | "DOTNET_ENVIRONMENT": "Development"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/RequestLine.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using WebServer.SDK;
7 |
8 | namespace WebServer.Server
9 | {
10 | internal class RequestLine
11 | {
12 | public WMethods Method { get; set; }
13 | public string Url { get; set; } = string.Empty;
14 | public string Version { get; set; } = string.Empty;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/RequestLineParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WebServer.Server
8 | {
9 | internal class RequestLineParser
10 | {
11 | public static bool TryParse(string line, out RequestLine? requestLine)
12 | {
13 | ArgumentNullException.ThrowIfNull(line);
14 |
15 | requestLine = default;
16 |
17 | // GET / HTTP/1.1
18 |
19 | if (line.StartsWith("GET "))
20 | {
21 | int idx = line.IndexOf(' ', 4);
22 | if (idx != -1) {
23 | requestLine = new();
24 | requestLine.Method = SDK.WMethods.Get;
25 | requestLine.Url = line.Substring(4, idx - 4);
26 | requestLine.Version = line.Substring(idx + 1);
27 |
28 | return true;
29 | }
30 | else
31 | {
32 | return false;
33 | }
34 | }
35 |
36 | return false;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/RequestReaderFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.Sockets;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using WebServer.SDK;
9 | using WebServer.Server.RequestReaders;
10 |
11 | namespace WebServer.Server
12 | {
13 | internal class RequestReaderFactory : IRequestReaderFactory
14 | {
15 | private readonly ILoggerFactory loggerFactory;
16 |
17 | public RequestReaderFactory(ILoggerFactory loggerFactory) {
18 | this.loggerFactory = loggerFactory;
19 | }
20 |
21 | public IRequestReader Create(Socket socket)
22 | {
23 | return new DefaultRequestReader(socket, loggerFactory.CreateLogger());
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/RequestReaders/DefaultRequestReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Sockets;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using WebServer.SDK;
8 |
9 | namespace WebServer.Server.RequestReaders
10 | {
11 | internal class DefaultRequestReader: IRequestReader
12 | {
13 | private readonly ILogger _logger;
14 | private readonly Socket socket;
15 |
16 | public DefaultRequestReader(Socket socket, ILogger logger) {
17 | this.socket = socket;
18 | this._logger = logger;
19 | }
20 |
21 | public async Task ReadRequestAsync(CancellationToken cancellationToken)
22 | {
23 | var stream = new NetworkStream(socket);
24 | var reader = new StreamReader(stream, Encoding.ASCII);
25 |
26 | var requestBuilder = new WRequestBuilder();
27 |
28 | var requestLineString = await reader.ReadLineAsync(cancellationToken);
29 | _logger.LogInformation(requestLineString);
30 |
31 | if (requestLineString != null)
32 | {
33 | if (RequestLineParser.TryParse(requestLineString, out var requestLine) && requestLine != null)
34 | {
35 | requestBuilder.Url = requestLine.Url;
36 | requestBuilder.HttpVersion = requestLine.Version;
37 |
38 | var headerLine = await reader.ReadLineAsync(cancellationToken);
39 | while (!string.IsNullOrEmpty(headerLine))
40 | {
41 | _logger.LogInformation(headerLine);
42 | if (HeaderLine.TryParse(headerLine, out var header))
43 | {
44 | requestBuilder.AddHeader(header!);
45 | }
46 |
47 | headerLine = await reader.ReadLineAsync(cancellationToken);
48 | }
49 | }
50 | }
51 |
52 | return requestBuilder.Build();
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/ResponseWriterFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.Sockets;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using WebServer.SDK;
9 | using WebServer.Server.RequestReaders;
10 | using WebServer.Server.ResponseWriters;
11 |
12 | namespace WebServer.Server
13 | {
14 | internal class ResponseWriterFactory : IResponseWriterFactory
15 | {
16 | private readonly ILoggerFactory loggerFactory;
17 |
18 | public ResponseWriterFactory(ILoggerFactory loggerFactory) {
19 | this.loggerFactory = loggerFactory;
20 | }
21 |
22 | public IResponseWriter Create(Socket socket)
23 | {
24 | return new DefaultResponseWriter(socket, loggerFactory.CreateLogger());
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/ResponseWriters/DefaultResponseWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Sockets;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using WebServer.SDK;
8 |
9 | namespace WebServer.Server.ResponseWriters
10 | {
11 | internal class DefaultResponseWriter : IResponseWriter
12 | {
13 | private Socket socket;
14 | private ILogger logger;
15 |
16 | public DefaultResponseWriter(Socket socket, ILogger logger)
17 | {
18 | this.socket = socket;
19 | this.logger = logger;
20 | }
21 |
22 | public async Task SendResponseAsync(WResponse response)
23 | {
24 | var stream = new NetworkStream(socket);
25 | var streamWriter = new StreamWriter(stream);
26 |
27 | string reasonPhrase = response.ReasonPhrase;
28 | if (string.IsNullOrEmpty(reasonPhrase))
29 | {
30 | reasonPhrase = HttpReasonPhrases.GetByCode(response.ResponseCode);
31 | }
32 |
33 | await streamWriter.WriteLineAsync($"{response.HttpVersion} {(int)response.ResponseCode} {reasonPhrase}");
34 | await streamWriter.WriteLineAsync($"Content-Length: {response.ContentLength}");
35 | await streamWriter.WriteLineAsync($"Content-Type: {response.ContentType}");
36 | await streamWriter.WriteLineAsync("Connection: close");
37 | await streamWriter.WriteLineAsync();
38 | await streamWriter.FlushAsync();
39 | if (response.ContentLength > 0)
40 | {
41 | await response.ResponseBodyWriter.WriteAsync(stream);
42 | }
43 | await stream.FlushAsync();
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/WRequestBuilder.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Primitives;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using WebServer.SDK;
8 |
9 | namespace WebServer.Server
10 | {
11 | internal class WRequestBuilder
12 | {
13 | WMethods Method { get; set; } = WMethods.Get;
14 | public string Url { get; set; } = string.Empty;
15 | public string HttpVersion { get; set; } = string.Empty;
16 | public string Host { get; set; } = string.Empty;
17 | public bool IsKeepAlive { get; set; } = false;
18 | public IDictionary Headers { get; set; } = new Dictionary();
19 |
20 | public WRequest Build()
21 | {
22 | Validate();
23 |
24 | var request = new WRequest() {
25 | Method = Method,
26 | Url = Url,
27 | Host = Host,
28 | HttpVersion = HttpVersion,
29 | Headers = Headers,
30 | IsKeepAlive = IsKeepAlive,
31 | };
32 |
33 | return request;
34 | }
35 |
36 | private void Validate()
37 | {
38 | if (string.IsNullOrEmpty(Url))
39 | {
40 | throw new ArgumentNullException(nameof(Url));
41 | }
42 | if (string.IsNullOrEmpty(Host))
43 | {
44 | throw new ArgumentNullException(nameof(Host));
45 | }
46 | if (string.IsNullOrEmpty(HttpVersion))
47 | {
48 | throw new ArgumentNullException(nameof(HttpVersion));
49 | }
50 | }
51 |
52 | internal void AddHeader(WHeader header)
53 | {
54 | if ("Host".Equals(header.Name, StringComparison.OrdinalIgnoreCase))
55 | {
56 | this.Host = header.Values.First() ?? string.Empty;
57 | }
58 | else if ("Connection".Equals(header.Name, StringComparison.OrdinalIgnoreCase))
59 | {
60 | this.IsKeepAlive = "keep-alive".Equals(header.Values.First());
61 | }
62 |
63 | if (!Headers.TryGetValue(header.Name, out var values))
64 | {
65 | Headers.Add(header.Name, header.Values);
66 | }
67 | else
68 | {
69 | Headers[header.Name] = StringValues.Concat(values, header.Values);
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/WebServer.Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | dotnet-WebServer.Server-7e906353-4a5a-4fce-8861-fd6ddbdc7cf1
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/WebServerOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Reflection;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace WebServer.Server
10 | {
11 | public class WebServerOptions
12 | {
13 | public string IPAddress { get; set; } = string.Empty;
14 | public int Port { get; set; } = 80;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/Worker.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Sockets;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading;
5 | using WebServer.Middleware.StaticContent;
6 | using WebServer.SDK;
7 | using WebServer.Server.DefaultMiddlewares;
8 | using WebServer.Server.RequestReaders;
9 | using WebServer.SDK.ResponseBodyWriters;
10 |
11 | namespace WebServer.Server;
12 |
13 | public class Worker : BackgroundService
14 | {
15 | private WebServerOptions options;
16 | private readonly IRequestReaderFactory requestReaderFactory;
17 | private readonly IResponseWriterFactory responseWriterFactory;
18 | private readonly ILogger _logger;
19 |
20 | private ICallable firstMiddleware = new NullCallable();
21 |
22 | public Worker(WebServerOptions options,
23 | IRequestReaderFactory requestReaderFactory,
24 | IResponseWriterFactory responseWriterFactory,
25 | ILogger logger)
26 | {
27 | this.options = options ?? throw new ArgumentNullException(nameof(options));
28 | this.requestReaderFactory = requestReaderFactory;
29 | this.responseWriterFactory = responseWriterFactory;
30 | _logger = logger;
31 | }
32 |
33 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
34 | {
35 | AddMiddleware(new NotFoundMiddleware());
36 | AddMiddleware(new StaticContentMiddleware());
37 |
38 |
39 | var endPoint = new IPEndPoint(string.IsNullOrEmpty(options.IPAddress) ? IPAddress.Any : IPAddress.Parse(options.IPAddress) , options.Port);
40 | using var serverSocket = new Socket(
41 | endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp
42 | );
43 | serverSocket.Bind(endPoint);
44 |
45 | _logger.LogInformation("Listening... (port: {p})", options.Port);
46 | serverSocket.Listen();
47 |
48 | var clientConnections = new List();
49 |
50 | while (!stoppingToken.IsCancellationRequested)
51 | {
52 | var clientSocket = await serverSocket.AcceptAsync(stoppingToken);
53 |
54 | if (clientSocket != null) {
55 | var t = HandleNewClientConnectionAsync(clientSocket, stoppingToken);
56 | clientConnections.Add(new ClientConnection()
57 | {
58 | HandlerTask = t
59 | });
60 | }
61 | }
62 |
63 | Task.WaitAll(clientConnections.Select(c => c.HandlerTask).ToArray());
64 |
65 | serverSocket.Close();
66 | }
67 |
68 | private async Task HandleNewClientConnectionAsync(Socket socket, CancellationToken stoppingToken)
69 | {
70 | var cancelationTokenSource = new CancellationTokenSource(3000);
71 | IRequestReader requestReader = requestReaderFactory.Create(socket);
72 |
73 | // read request from socket
74 | WRequest request = await requestReader.ReadRequestAsync(CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, cancelationTokenSource.Token).Token);
75 | // create response
76 |
77 | var response = new WResponse()
78 | {
79 | };
80 |
81 | var invokeCancelationTokenSource = new CancellationTokenSource(15000);
82 |
83 | // handle the request
84 | await InvokeMiddlewaresAsync(new MiddlewareContext() {
85 | Request = request, Response = response
86 | },
87 | CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, invokeCancelationTokenSource.Token).Token
88 | );
89 |
90 |
91 | // send back the response
92 | IResponseWriter responseWriter = responseWriterFactory.Create(socket);
93 | await responseWriter.SendResponseAsync(response);
94 |
95 | socket.Close();
96 | }
97 |
98 | private async Task InvokeMiddlewaresAsync(MiddlewareContext context, CancellationToken cancellationToken)
99 | {
100 | await firstMiddleware.InvokeAsync(context, cancellationToken);
101 | }
102 |
103 | private void AddMiddleware(IMiddleware middleware)
104 | {
105 | firstMiddleware = new Callable() { Middleware = middleware, Next = firstMiddleware };
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | },
7 | "Console": {
8 | "FormatterName": "simple",
9 | "FormatterOptions": {
10 | "SingleLine": true
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebServer/WebServer.Server/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | },
8 | "Server": {
9 | "Port": 8888
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/Controllers/HomeController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using System.Diagnostics;
3 | using WhyWeNeedAsync.Models;
4 |
5 | namespace WhyWeNeedAsync.Controllers
6 | {
7 | public class HomeController : Controller
8 | {
9 | private readonly ILogger _logger;
10 |
11 | public HomeController(ILogger logger)
12 | {
13 | _logger = logger;
14 | }
15 |
16 | public IActionResult Index()
17 | {
18 | return Content("ASP.NET threadpool starvation demo - https://github.com/daohainam/LearnDotNet-Samples");
19 | }
20 |
21 | private async Task FindProductAsync(int productId, string name)
22 | {
23 | await Task.Delay(1000);
24 | return new Product() { Id = productId, Name = name };
25 | }
26 | private Product FindProduct(int productId, string name)
27 | {
28 | Task.Delay(1000).Wait();
29 | return new Product() { Id = productId, Name = name };
30 | }
31 | [Route("/taskresultwait")]
32 | public IActionResult TaskResultWait()
33 | {
34 | var product = FindProduct(1, "Apple iPhone");
35 |
36 | return Ok("/taskresultwait:success");
37 | }
38 |
39 | [Route("/taskawait")]
40 | public async Task TaskAwait()
41 | {
42 | var product = await FindProductAsync(1, "Apple iPhone");
43 |
44 | return Ok("/taskawait:success");
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/Models/Product.cs:
--------------------------------------------------------------------------------
1 | namespace WhyWeNeedAsync.Models
2 | {
3 | public class Product
4 | {
5 | public required int Id { get; set; }
6 | public required string Name { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/Program.cs:
--------------------------------------------------------------------------------
1 | namespace WhyWeNeedAsync
2 | {
3 | public class Program
4 | {
5 | public static void Main(string[] args)
6 | {
7 | var builder = WebApplication.CreateBuilder(args);
8 |
9 | // Add services to the container.
10 | builder.Services.AddControllersWithViews();
11 |
12 | var app = builder.Build();
13 |
14 | // Configure the HTTP request pipeline.
15 | if (!app.Environment.IsDevelopment())
16 | {
17 | app.UseExceptionHandler("/Home/Error");
18 | }
19 | app.UseStaticFiles();
20 |
21 | app.UseRouting();
22 |
23 | app.UseAuthorization();
24 |
25 | app.MapControllerRoute(
26 | name: "default",
27 | pattern: "{controller=Home}/{action=Index}/{id?}");
28 |
29 | app.Run();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:57104",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "applicationUrl": "http://localhost:5287",
17 | "environmentVariables": {
18 | "ASPNETCORE_ENVIRONMENT": "Development"
19 | }
20 | },
21 | "IIS Express": {
22 | "commandName": "IISExpress",
23 | "launchBrowser": true,
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/README.md:
--------------------------------------------------------------------------------
1 | - Download bombardier: https://github.com/codesenberg/bombardier/releases/tag/v1.2.6
2 | - Run bombardier-windows-amd64.exe http://localhost:5287/taskawait
3 | - And run bombardier-windows-amd64.exe http://localhost:5287/taskresultwait
4 |
5 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/WhyWeNeedAsync.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/WhyWeNeedAsync/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daohainam/lets-learn-dotnet/9136a9e771d1282604540d22e323f9dd05e3fae8/WhyWeNeedAsync/wwwroot/favicon.ico
--------------------------------------------------------------------------------