├── .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 --------------------------------------------------------------------------------