├── .editorconfig
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── Chapter10
├── Chapter10.csproj
├── Program.cs
├── README.md
├── Structured
│ ├── Database.cs
│ ├── IDatabase.cs
│ ├── IUserDetailsService.cs
│ ├── IUsersService.cs
│ ├── RegisterUser.cs
│ ├── User.cs
│ ├── UserDetails.cs
│ ├── UserDetailsService.cs
│ ├── UsersController.cs
│ └── UsersService.cs
├── Unstructured
│ └── UsersController.cs
├── appsettings.Development.json
└── appsettings.json
├── Chapter11
├── Chapter11.csproj
├── ConfidentialAttribute.cs
├── ConfidentialMetadataProvider.cs
├── JournalEntry.cs
├── JournalEntryMetadataProvider.cs
├── Patient.cs
└── Program.cs
├── Chapter12
├── AccountBalance.cs
├── AccountLifecycle.cs
├── Chapter12.csproj
├── EventSourcing
│ ├── EventContext.cs
│ ├── EventLog.cs
│ ├── EventSequenceNumber.cs
│ ├── EventSourceId.cs
│ ├── IEvent.cs
│ ├── IEventLog.cs
│ ├── IObservers.cs
│ ├── ObserverAttribute.cs
│ ├── ObserverHandler.cs
│ └── Observers.cs
├── Events.cs
└── Program.cs
├── Chapter13
├── AdminForNamespace.cs
├── AdminForNamespaceHandler.cs
├── Chapter13.csproj
├── Commands
│ ├── CommandActionFilter.cs
│ ├── CommandResult.cs
│ ├── ModelErrorExtensions.cs
│ └── ValidationResult.cs
├── CrossCuttingAuthorizationMiddlewareResultHandler.cs
├── CrossCuttingPoliciesProvider.cs
├── Employee.cs
├── EmployeesController.cs
├── HardCodedAuthenticationHandler.cs
├── HardCodedAuthenticationOptions.cs
├── HttpContextExtensions.cs
├── Program.cs
├── appsettings.Development.json
└── appsettings.json
├── Chapter14
├── Authenticator.cs
├── AuthorizationInterceptor.cs
├── Authorizer.cs
├── Chapter14.csproj
├── DefaultInstaller.cs
├── IAuthenticator.cs
├── IAuthorizer.cs
├── IUsersService.cs
├── IUsersServiceComposition.cs
├── InterceptorSelector.cs
├── LoggingInterceptor.cs
├── LoggingInterceptorLogMessages.cs
├── Program.cs
├── Todo
│ ├── ITodoService.cs
│ └── TodoService.cs
└── UsersService.cs
├── Chapter15
├── Chapter15.csproj
├── Program.cs
└── Something.cs
├── Chapter16
├── Chapter16.csproj
├── Employee.cs
├── EmployeesController.cs
├── EmployeesControllerMetrics.cs
├── GDPRReport.txt
├── Metrics.cs
├── Program.cs
├── ProgramMetrics.cs
├── appsettings.Development.json
├── appsettings.json
└── build.sh
├── Chapter17
├── Chapter17.csproj
├── GDPRReport.txt
├── MyException.cs
└── Program.cs
├── Chapter2
├── Calculator.cs
├── CalculatorTests.cs
├── Chapter2.csproj
├── Person.cs
├── Program.cs
└── Serializer.cs
├── Chapter3
├── Chapter3.csproj
├── Employee.cs
├── EmployeesController.cs
├── Program.cs
├── ValidationFilter.cs
├── appsettings.Development.json
├── appsettings.json
└── employees
├── Chapter4
├── AssemblyDiscovery
│ ├── AssemblyDiscovery.csproj
│ └── Program.cs
└── BusinessApp
│ ├── Concepts
│ ├── Concepts.csproj
│ └── Employees
│ │ ├── FirstName.cs
│ │ ├── LastName.cs
│ │ └── SocialSecurityNumber.cs
│ ├── Domain
│ ├── Domain.csproj
│ └── Employees
│ │ ├── RegisterEmployee.cs
│ │ └── SetSalaryLevelForEmployee.cs
│ └── Main
│ ├── Main.csproj
│ └── Program.cs
├── Chapter5
├── Chapter5.csproj
├── Employee.cs
└── Program.cs
├── Chapter6
├── Chapter6.csproj
├── Employee.cs
├── IgnoreChangesAttribute.cs
├── MyTypeGenerator.cs
├── NotifyChangesForAttribute.cs
├── NotifyingObjectWeaver.cs
└── Program.cs
├── Chapter7
├── Chapter7.csproj
├── Employee.cs
└── Program.cs
├── Chapter8
├── Chapter8.csproj
├── DictionaryStringObjectJsonConverter.cs
├── MyType.cs
├── Program.cs
├── QueryParser.cs
├── data.json
├── definition.json
└── query.json
├── Chapter9
├── Chapter9.csproj
├── InvalidTypeForProperty.cs
├── JsonSchemaType.cs
├── Program.cs
└── person.json
├── Fundamentals
├── ArrayIndexer.cs
├── ArrayIndexers.cs
├── ArrayProperty.cs
├── ChildrenPropertyIsNotEnumerable.cs
├── ChildrenPropertyIsNotEnumerableForType.cs
├── Compliance
│ ├── ComplianceDetailsAttribute.cs
│ ├── ComplianceDetailsExtensions.cs
│ ├── ComplianceMetadata.cs
│ ├── ComplianceMetadataResolver.cs
│ ├── ComplianceMetadataType.cs
│ ├── GDPR
│ │ ├── IHoldPersonalIdentifiableInformation.cs
│ │ ├── PersonalIdentifiableInformationAttribute.cs
│ │ ├── PersonalIdentifiableInformationConceptAs.cs
│ │ └── PersonalIdentifiableInformationMetadataProvider.cs
│ ├── ICanProvideComplianceMetadataForProperty.cs
│ ├── ICanProvideComplianceMetadataForType.cs
│ ├── IComplianceMetadataResolver.cs
│ ├── NoComplianceMetadataForProperty.cs
│ └── NoComplianceMetadataForType.cs
├── ConceptAs.cs
├── ConceptAsJsonConverter.cs
├── ConceptAsJsonConverterFactory.cs
├── ConceptAsTypeConverter.cs
├── ConceptExtensions.cs
├── ConceptFactory.cs
├── ConceptMap.cs
├── ContractToImplementorsMap.cs
├── ExpandoObjectExtensions.cs
├── ExpressionExtensions.cs
├── Fundamentals.csproj
├── Globals.cs
├── IArrayIndexers.cs
├── ICommand.cs
├── IContractToImplementorsMap.cs
├── IImplementationsOf.cs
├── IInstancesOf.cs
├── IPropertyPathSegment.cs
├── ITypeInfo.cs
├── ITypes.cs
├── ImplementationsOf.cs
├── InstancesOf.cs
├── LICENSE
├── MemberInfoExtensions.cs
├── Metrics
│ ├── CounterAttribute.cs
│ └── GlobalMetrics.cs
├── MissingArrayIndexerForPropertyPath.cs
├── MultipleTypesFound.cs
├── ObjectExtensions.cs
├── PIIConceptAs.cs
├── PropertyName.cs
├── PropertyPath.cs
├── README.md
├── SegmentValueIsNotCollection.cs
├── ServiceCollectionExtensions.cs
├── SingletonAttribute.cs
├── StringExtensions.cs
├── TypeConversion.cs
├── TypeExtensions.cs
├── TypeInfo.cs
├── TypeIsNotAConcept.cs
├── Types.cs
├── UnableToResolvePropertyPathOnType.cs
└── UnableToResolveTypeByName.cs
├── LICENSE
├── README.md
├── Roslyn.Extensions.Tests
├── ExceptionShouldNotBeSuffixed
│ ├── AnalyzerTests.cs
│ └── CodeFixTests.cs
└── Roslyn.Extensions.Tests.csproj
└── Roslyn.Extensions
├── .globalconfig
├── AnalyzerReleases.Shipped.md
├── AnalyzerReleases.Unshipped.md
├── CodeAnalysis
└── ExceptionShouldNotBeSuffixed
│ ├── Analyzer.cs
│ └── CodeFix.cs
├── GDPR
├── GDPRSourceGenerator.cs
└── GDPRSyntaxReceiver.cs
├── Metrics
├── CounterTagTemplateData.cs
├── CounterTemplateData.cs
├── MetricsSourceGenerator.cs
├── MetricsSyntaxReceiver.cs
└── MetricsTemplateData.cs
├── Roslyn.Extensions.csproj
├── Roslyn.Extensions.props
├── Templates
├── Metrics.hbs
└── TemplateTypes.cs
├── logo.png
└── stylecop.json
/.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 | .buildsettings
7 |
8 | # User-specific files
9 | *.suo
10 | *.user
11 | *.userosscache
12 | *.sln.docstates
13 |
14 | # User-specific files (MonoDevelop/Xamarin Studio)
15 | *.userprefs
16 |
17 | # Build results
18 | [Dd]ebug/
19 | [Dd]ebugPublic/
20 | [Rr]elease/
21 | [Rr]eleases/
22 | x64/
23 | x86/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 | [Ll]og/
28 |
29 | # Visual Studio 2015 cache/options directory
30 | .vs/
31 | # Uncomment if you have tasks that create the project's static files in wwwroot
32 | wwwroot/
33 |
34 | # MSTest test Results
35 | [Tt]est[Rr]esult*/
36 | [Bb]uild[Ll]og.*
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # .NET Core
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 | **/Properties/launchSettings.json
48 |
49 | *_i.c
50 | *_p.c
51 | *_i.h
52 | *.ilk
53 | *.meta
54 | *.obj
55 | *.pch
56 | *.pdb
57 | *.pgc
58 | *.pgd
59 | *.rsp
60 | *.sbr
61 | *.tlb
62 | *.tli
63 | *.tlh
64 | *.tmp
65 | *.tmp_proj
66 | *.log
67 | *.vspscc
68 | *.vssscc
69 | .builds
70 | *.pidb
71 | *.svclog
72 | *.scc
73 |
74 | # Chutzpah Test files
75 | _Chutzpah*
76 |
77 | # Visual C++ cache files
78 | ipch/
79 | *.aps
80 | *.ncb
81 | *.opendb
82 | *.opensdf
83 | *.sdf
84 | *.cachefile
85 | *.VC.db
86 | *.VC.VC.opendb
87 |
88 | # Visual Studio profiler
89 | *.psess
90 | *.vsp
91 | *.vspx
92 | *.sap
93 |
94 | # TFS 2012 Local Workspace
95 | $tf/
96 |
97 | # Guidance Automation Toolkit
98 | *.gpState
99 |
100 | # ReSharper is a .NET coding add-in
101 | _ReSharper*/
102 | *.[Rr]e[Ss]harper
103 | *.DotSettings.user
104 |
105 | # JustCode is a .NET coding add-in
106 | .JustCode
107 |
108 | # TeamCity is a build add-in
109 | _TeamCity*
110 |
111 | # DotCover is a Code Coverage Tool
112 | *.dotCover
113 |
114 | # Visual Studio code coverage results
115 | *.coverage
116 | *.coveragexml
117 |
118 | # NCrunch
119 | _NCrunch_*
120 | .*crunch*.local.xml
121 | nCrunchTemp_*
122 |
123 | # MightyMoose
124 | *.mm.*
125 | AutoTest.Net/
126 |
127 | # Web workbench (sass)
128 | .sass-cache/
129 |
130 | # Installshield output folder
131 | [Ee]xpress/
132 |
133 | # DocProject is a documentation generator add-in
134 | DocProject/buildhelp/
135 | DocProject/Help/*.HxT
136 | DocProject/Help/*.HxC
137 | DocProject/Help/*.hhc
138 | DocProject/Help/*.hhk
139 | DocProject/Help/*.hhp
140 | DocProject/Help/Html2
141 | DocProject/Help/html
142 |
143 | # Click-Once directory
144 | publish/
145 |
146 | # Publish Web Output
147 | *.[Pp]ublish.xml
148 | *.azurePubxml
149 | # TODO: Comment the next line if you want to checkin your web deploy settings
150 | # but database connection strings (with potential passwords) will be unencrypted
151 | *.pubxml
152 | *.publishproj
153 |
154 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
155 | # checkin your Azure Web App publish settings, but sensitive information contained
156 | # in these scripts will be unencrypted
157 | PublishScripts/
158 |
159 | # NuGet Packages
160 | **/*.nupkg
161 | **/*.snupkg
162 | # The packages folder can be ignored because of Package Restore
163 | **/packages/*
164 | # except build/, which is used as an MSBuild target.
165 | !**/packages/build/
166 | # Uncomment if necessary however generally it will be regenerated when needed
167 | #!**/packages/repositories.config
168 | # NuGet v3's project.json files produces more ignorable files
169 | *.nuget.props
170 | *.nuget.targets
171 |
172 | # Microsoft Azure Build Output
173 | csx/
174 | *.build.csdef
175 |
176 | # Microsoft Azure Emulator
177 | ecf/
178 | rcf/
179 |
180 | # Windows Store app package directories and files
181 | AppPackages/
182 | BundleArtifacts/
183 | Package.StoreAssociation.xml
184 | _pkginfo.txt
185 |
186 | # Visual Studio cache files
187 | # files ending in .cache can be ignored
188 | *.[Cc]ache
189 | # but keep track of directories ending in .cache
190 | !*.[Cc]ache/
191 |
192 | # Others
193 | ClientBin/
194 | ~$*
195 | *~
196 | *.dbmdl
197 | *.dbproj.schemaview
198 | *.jfm
199 | *.pfx
200 | *.publishsettings
201 | orleans.codegen.cs
202 |
203 | # Since there are multiple workflows, uncomment next line to ignore bower_components
204 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
205 | #bower_components/
206 |
207 | # RIA/Silverlight projects
208 | Generated_Code/
209 |
210 | # Backup & report files from converting an old project file
211 | # to a newer Visual Studio version. Backup files are not needed,
212 | # because we have git ;-)
213 | _UpgradeReport_Files/
214 | Backup*/
215 | UpgradeLog*.XML
216 | UpgradeLog*.htm
217 |
218 | # SQL Server files
219 | *.mdf
220 | *.ldf
221 | *.ndf
222 |
223 | # Business Intelligence projects
224 | *.rdl.data
225 | *.bim.layout
226 | *.bim_*.settings
227 |
228 | # Microsoft Fakes
229 | FakesAssemblies/
230 |
231 | # GhostDoc plugin setting file
232 | *.GhostDoc.xml
233 |
234 | # Node.js Tools for Visual Studio
235 | .ntvs_analysis.dat
236 | node_modules/
237 |
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 | *.d.ts
242 | !*.scss.d.ts
243 | yarn.lock
244 |
245 | # Visual Studio 6 build log
246 | *.plg
247 |
248 | # Visual Studio 6 workspace options file
249 | *.opt
250 |
251 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
252 | *.vbw
253 |
254 | # Visual Studio LightSwitch build output
255 | **/*.HTMLClient/GeneratedArtifacts
256 | **/*.DesktopClient/GeneratedArtifacts
257 | **/*.DesktopClient/ModelManifest.xml
258 | **/*.Server/GeneratedArtifacts
259 | **/*.Server/ModelManifest.xml
260 | _Pvt_Extensions
261 |
262 | # Paket dependency manager
263 | .paket/paket.exe
264 | paket-files/
265 |
266 | # FAKE - F# Make
267 | .fake/
268 |
269 | # JetBrains Rider
270 | .idea/
271 | *.sln.iml
272 |
273 | # CodeRush
274 | .cr/
275 |
276 | # Python Tools for Visual Studio (PTVS)
277 | __pycache__/
278 | *.pyc
279 |
280 | # Cake - Uncomment if you are using it
281 | # tools/**
282 | # !tools/packages.config
283 |
284 | yarn.lock
285 | package-json.lock
286 | package-lock.json
287 | dist
288 | **/node_modules/
289 | *.tsbuildinfo
290 | .eslintcache
291 |
292 | wwwroot
293 | out
294 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Attach",
9 | "type": "coreclr",
10 | "request": "attach"
11 | },
12 | {
13 | "name": ".NET Core Launch",
14 | "type": "coreclr",
15 | "request": "launch",
16 | "preLaunchTask": "build",
17 | "program": "${workspaceFolder}/Chapter14/bin/Debug/net7.0/Chapter14.dll",
18 | "args": [],
19 | "cwd": "${workspaceFolder}/Chapter14",
20 | "stopAtEntry": false,
21 | "env": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Brtrue",
4 | "Callvirt",
5 | "Castclass",
6 | "Cratis",
7 | "Expando",
8 | "inheritdoc",
9 | "Ldarg",
10 | "Ldfld",
11 | "Ldloc",
12 | "Ldnull",
13 | "Ldstr",
14 | "Newobj",
15 | "Stfld",
16 | "Stloc",
17 | "typeparam",
18 | "Vtable",
19 | "Xunit"
20 | ]
21 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "--no-restore",
11 | "${workspaceFolder}/Chapter14/Chapter14.csproj",
12 | "/property:GenerateFullPaths=true",
13 | "/consoleloggerparameters:NoSummary"
14 | ],
15 | "problemMatcher": "$msCompile"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/Chapter10/Chapter10.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Chapter10/Program.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | var builder = WebApplication.CreateBuilder(args);
4 | builder.Services.AddControllers();
5 | var types = new Types();
6 | builder.Services.AddSingleton(types);
7 | builder.Services.AddBindingsByConvention(types);
8 |
9 | var app = builder.Build();
10 | app.UseRouting();
11 | app.UseEndpoints(_ => _.MapControllers());
12 | app.Run();
13 |
--------------------------------------------------------------------------------
/Chapter10/README.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 | ```shell
4 | docker run -d -p 27017:27017 mongo
5 | ```
--------------------------------------------------------------------------------
/Chapter10/Structured/Database.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 | using Humanizer;
3 | using MongoDB.Driver;
4 |
5 | namespace Chapter10.Structured;
6 |
7 | [Singleton]
8 | public class Database : IDatabase
9 | {
10 | readonly IMongoDatabase _mongoDatabase;
11 |
12 | public Database()
13 | {
14 | var client = new MongoClient("mongodb://localhost:27017");
15 | _mongoDatabase = client.GetDatabase("TheSystem");
16 | }
17 |
18 | public IMongoCollection GetCollectionFor() => _mongoDatabase.GetCollection(typeof(T).Name.Pluralize());
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter10/Structured/IDatabase.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Driver;
2 |
3 | namespace Chapter10.Structured;
4 |
5 | public interface IDatabase
6 | {
7 | IMongoCollection GetCollectionFor();
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter10/Structured/IUserDetailsService.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter10.Structured;
2 |
3 | public interface IUserDetailsService
4 | {
5 | Task Register(string firstName, string lastName, string socialSecurityNumber, Guid userId);
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter10/Structured/IUsersService.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter10.Structured;
2 |
3 | public interface IUsersService
4 | {
5 | Task Register(string userName, string password);
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter10/Structured/RegisterUser.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter10.Structured;
2 |
3 | public record RegisterUser(string FirstName, string LastName, string SocialSecurityNumber, string UserName, string Password);
4 |
--------------------------------------------------------------------------------
/Chapter10/Structured/User.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter10.Structured;
2 |
3 | public record User(Guid Id, string UserName, string Password);
4 |
--------------------------------------------------------------------------------
/Chapter10/Structured/UserDetails.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter10.Structured;
2 |
3 | public record UserDetails(Guid Id, Guid UserId, string FirstName, string LastName, string SocialSecurityNumber);
4 |
--------------------------------------------------------------------------------
/Chapter10/Structured/UserDetailsService.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace Chapter10.Structured;
4 |
5 | [Singleton]
6 | public class UserDetailsService : IUserDetailsService
7 | {
8 | readonly IDatabase _database;
9 |
10 | public UserDetailsService(IDatabase database)
11 | {
12 | _database = database;
13 | }
14 |
15 | public Task Register(string firstName, string lastName, string socialSecurityNumber, Guid userId)
16 | => _database.GetCollectionFor().InsertOneAsync(new(Guid.NewGuid(), userId, firstName, lastName, socialSecurityNumber));
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter10/Structured/UsersController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace Chapter10.Structured;
4 |
5 | [Route("/api/users")]
6 | public class UsersController : Controller
7 | {
8 | readonly IUsersService _usersService;
9 | readonly IUserDetailsService _userDetailsService;
10 |
11 | public UsersController(IUsersService usersService, IUserDetailsService userDetailsService)
12 | {
13 | _usersService = usersService;
14 | _userDetailsService = userDetailsService;
15 | }
16 |
17 | [HttpPost("register")]
18 | public async Task Register([FromBody] RegisterUser userRegistration)
19 | {
20 | var userId = await _usersService.Register(userRegistration.UserName, userRegistration.Password);
21 | await _userDetailsService.Register(
22 | userRegistration.FirstName,
23 | userRegistration.LastName,
24 | userRegistration.SocialSecurityNumber,
25 | userId);
26 | }
27 | }
--------------------------------------------------------------------------------
/Chapter10/Structured/UsersService.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace Chapter10.Structured;
4 |
5 | [Singleton]
6 | public class UsersService : IUsersService
7 | {
8 | readonly IDatabase _database;
9 |
10 | public UsersService(IDatabase database)
11 | {
12 | _database = database;
13 | }
14 |
15 | public async Task Register(string userName, string password)
16 | {
17 | var user = new User(Guid.NewGuid(), userName, password);
18 | await _database.GetCollectionFor().InsertOneAsync(user);
19 | return user.Id;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chapter10/Unstructured/UsersController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using MongoDB.Driver;
3 |
4 | namespace Chapter10.Unstructured;
5 |
6 | public record RegisterUser(string FirstName, string LastName, string SocialSecurityNumber, string UserName, string Password);
7 |
8 | public record User(Guid Id, string UserName, string Password);
9 |
10 | public record UserDetails(Guid Id, Guid UserId, string FirstName, string LastName, string SocialSecurityNumber);
11 |
12 | [Route("/api/unstructured/users")]
13 | public class UsersController : Controller
14 | {
15 | IMongoCollection _userCollection;
16 | IMongoCollection _userDetailsCollection;
17 |
18 | public UsersController()
19 | {
20 | var client = new MongoClient("mongodb://localhost:27017");
21 | var database = client.GetDatabase("TheSystem");
22 | _userCollection = database.GetCollection("users");
23 | _userDetailsCollection = database.GetCollection("user-details");
24 | }
25 |
26 | [HttpPost("register")]
27 | public async Task Register([FromBody] RegisterUser userRegistration)
28 | {
29 | var user = new User(Guid.NewGuid(), userRegistration.UserName, userRegistration.Password);
30 | var userDetails = new UserDetails(Guid.NewGuid(), user.Id, userRegistration.FirstName, userRegistration.LastName, userRegistration.SocialSecurityNumber);
31 | await _userCollection.InsertOneAsync(user);
32 | await _userDetailsCollection.InsertOneAsync(userDetails);
33 | }
34 | }
--------------------------------------------------------------------------------
/Chapter10/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter10/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter11/Chapter11.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Exe
13 | net7.0
14 | enable
15 | enable
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Chapter11/ConfidentialAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter11;
2 |
3 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
4 | public sealed class ConfidentialAttribute : Attribute
5 | {
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter11/ConfidentialMetadataProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Fundamentals.Compliance;
3 |
4 | namespace Chapter11;
5 |
6 | public class ConfidentialMetadataProvider : ICanProvideComplianceMetadataForType
7 | {
8 | public bool CanProvide(Type type) => type.GetCustomAttribute() != null;
9 |
10 | public ComplianceMetadata Provide(Type type) => new("8dd1709a-bbe1-4b98-84e1-9e7be2fd4912", "The data is confidential");
11 | }
--------------------------------------------------------------------------------
/Chapter11/JournalEntry.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter11;
2 |
3 | public class JournalEntry
4 | {
5 | public string Title { get; set; } = string.Empty;
6 |
7 | public string Content { get; set; } = string.Empty;
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter11/JournalEntryMetadataProvider.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals.Compliance;
2 |
3 | namespace Chapter11;
4 |
5 | public class JournalEntryMetadataProvider : ICanProvideComplianceMetadataForType
6 | {
7 | public bool CanProvide(Type type) => type == typeof(JournalEntry);
8 |
9 | public ComplianceMetadata Provide(Type type) => new("7242aed8-8d70-49df-8713-eea45e2764d4", "Journal entry");
10 | }
--------------------------------------------------------------------------------
/Chapter11/Patient.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals.Compliance.GDPR;
2 |
3 | namespace Chapter11;
4 |
5 | [Confidential]
6 | public class Patient
7 | {
8 | [PersonalIdentifiableInformation("Employment records")]
9 | public string FirstName { get; set; } = string.Empty;
10 |
11 | [PersonalIdentifiableInformation("Employment records")]
12 | public string LastName { get; set; } = string.Empty;
13 |
14 | [PersonalIdentifiableInformation("Uniquely identifies the employee")]
15 | public string SocialSecurityNumber { get; set; } = string.Empty;
16 |
17 | public IEnumerable JournalEntries { get; set; } = Enumerable.Empty();
18 | }
19 |
--------------------------------------------------------------------------------
/Chapter11/Program.cs:
--------------------------------------------------------------------------------
1 | using Chapter11;
2 | using Fundamentals;
3 | using Fundamentals.Compliance;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | var host = Host.CreateDefaultBuilder()
8 | .ConfigureServices((context, services) =>
9 | {
10 | var types = new Types();
11 | services.AddSingleton(types);
12 | services.AddBindingsByConvention(types);
13 | services.AddSelfBinding(types);
14 | })
15 | .Build();
16 |
17 | var complianceMetadataResolver = host.Services.GetRequiredService();
18 |
19 | var typeToCheck = typeof(Patient);
20 | Console.WriteLine($"Checking type for compliance rules: {typeToCheck.FullName}");
21 |
22 | if (complianceMetadataResolver.HasMetadataFor(typeToCheck))
23 | {
24 | var metadata = complianceMetadataResolver.GetMetadataFor(typeToCheck);
25 | foreach (var item in metadata)
26 | {
27 | Console.WriteLine($"Type level - {item.Details}");
28 | }
29 | }
30 |
31 | Console.WriteLine("");
32 |
33 | foreach (var property in typeToCheck.GetProperties())
34 | {
35 | if (complianceMetadataResolver.HasMetadataFor(property))
36 | {
37 | var metadata = complianceMetadataResolver.GetMetadataFor(property);
38 | foreach (var item in metadata)
39 | {
40 | Console.WriteLine($"Property: {property.Name} - {item.Details}");
41 | }
42 | }
43 | else if (complianceMetadataResolver.HasMetadataFor(property.PropertyType))
44 | {
45 | var metadata = complianceMetadataResolver.GetMetadataFor(property.PropertyType);
46 | foreach (var item in metadata)
47 | {
48 | Console.WriteLine($"Property: {property.Name} - {item.Details}");
49 | }
50 | }
51 | else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition().IsAssignableTo(typeof(IEnumerable<>)))
52 | {
53 | var type = property.PropertyType.GetGenericArguments().First();
54 | if (complianceMetadataResolver.HasMetadataFor(type))
55 | {
56 | Console.WriteLine($"\nProperty {property.Name} is a collection of type {type.FullName} with type level metadata");
57 |
58 | var metadata = complianceMetadataResolver.GetMetadataFor(type);
59 | foreach (var item in metadata)
60 | {
61 | Console.WriteLine($"{property.Name} - {item.Details}");
62 | }
63 | }
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/Chapter12/AccountBalance.cs:
--------------------------------------------------------------------------------
1 | using EventSourcing;
2 |
3 | namespace Chapter12;
4 |
5 | [Observer]
6 | public class AccountBalance
7 | {
8 | public Task DepositPerformed(DepositPerformed @event, EventContext context)
9 | {
10 | Console.WriteLine($"Deposit of {@event.Amount} performed on {context.EventSourceId}");
11 | return Task.CompletedTask;
12 | }
13 |
14 | public Task WithdrawalPerformed(WithdrawalPerformed @event, EventContext context)
15 | {
16 | Console.WriteLine($"Withdrawal of {@event.Amount} performed on {context.EventSourceId}");
17 | return Task.CompletedTask;
18 | }
19 | }
--------------------------------------------------------------------------------
/Chapter12/AccountLifecycle.cs:
--------------------------------------------------------------------------------
1 | using EventSourcing;
2 |
3 | namespace Chapter12;
4 |
5 | [Observer]
6 | public class AccountLifecycle
7 | {
8 | public Task Opened(BankAccountOpened @event)
9 | {
10 | Console.WriteLine($"Account opened for {@event.CustomerName}");
11 | return Task.CompletedTask;
12 | }
13 |
14 | public Task Closed(BankAccountClosed @event, EventContext context)
15 | {
16 | Console.WriteLine($"Account with id {context.EventSourceId} closed");
17 | return Task.CompletedTask;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter12/Chapter12.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Exe
13 | net7.0
14 | enable
15 | enable
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/EventContext.cs:
--------------------------------------------------------------------------------
1 | namespace EventSourcing;
2 |
3 | public record EventContext(
4 | EventSourceId EventSourceId,
5 | EventSequenceNumber SequenceNumber,
6 | DateTimeOffset Occurred);
7 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/EventLog.cs:
--------------------------------------------------------------------------------
1 | namespace EventSourcing;
2 |
3 | public class EventLog : IEventLog
4 | {
5 | readonly IObservers _observers;
6 | EventSequenceNumber _sequenceNumber = 0;
7 |
8 | public EventLog(IObservers observers)
9 | {
10 | _observers = observers;
11 | }
12 |
13 | public async Task Append(EventSourceId eventSourceId, IEvent @event)
14 | {
15 | await _observers.OnNext(
16 | @event,
17 | new EventContext(eventSourceId, _sequenceNumber, DateTimeOffset.UtcNow));
18 | _sequenceNumber++;
19 | }
20 | }
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/EventSequenceNumber.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace EventSourcing;
4 |
5 | public record EventSequenceNumber(ulong Value) : ConceptAs(Value)
6 | {
7 | public static implicit operator EventSequenceNumber(ulong value) => new(value);
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/EventSourceId.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace EventSourcing;
4 |
5 | public record EventSourceId(string Value) : ConceptAs(Value)
6 | {
7 | public static EventSourceId New() => new(Guid.NewGuid().ToString());
8 | }
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/IEvent.cs:
--------------------------------------------------------------------------------
1 | namespace EventSourcing;
2 |
3 | public interface IEvent { }
4 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/IEventLog.cs:
--------------------------------------------------------------------------------
1 | namespace EventSourcing;
2 |
3 | public interface IEventLog
4 | {
5 | Task Append(EventSourceId eventSourceId, IEvent @event);
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/IObservers.cs:
--------------------------------------------------------------------------------
1 | namespace EventSourcing;
2 |
3 | public interface IObservers
4 | {
5 | Task OnNext(IEvent @event, EventContext context);
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/ObserverAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace EventSourcing;
2 |
3 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
4 | public sealed class ObserverAttribute : Attribute
5 | {
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/ObserverHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace EventSourcing;
4 |
5 | public class ObserverHandler
6 | {
7 | readonly Dictionary> _methodsByEventType;
8 | readonly IServiceProvider _serviceProvider;
9 | readonly Type _targetType;
10 |
11 | public IEnumerable EventTypes => _methodsByEventType.Keys;
12 |
13 | public ObserverHandler(IServiceProvider serviceProvider, Type targetType)
14 | {
15 | _serviceProvider = serviceProvider;
16 | _targetType = targetType;
17 |
18 | _methodsByEventType = targetType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
19 | .Where(_ => IsObservingMethod(_))
20 | .GroupBy(_ => _.GetParameters()[0].ParameterType)
21 | .ToDictionary(_ => _.Key, _ => _.ToArray().AsEnumerable());
22 | }
23 |
24 | public async Task OnNext(IEvent @event, EventContext context)
25 | {
26 | var eventType = @event.GetType();
27 |
28 | if (_methodsByEventType.ContainsKey(eventType))
29 | {
30 | var actualObserver = _serviceProvider.GetService(_targetType);
31 | Task returnValue;
32 | foreach (var method in _methodsByEventType[eventType])
33 | {
34 | var parameters = method.GetParameters();
35 |
36 | if (parameters.Length == 2)
37 | {
38 | returnValue = (Task)method.Invoke(actualObserver, new object[] { @event, context })!;
39 | }
40 | else
41 | {
42 | returnValue = (Task)method.Invoke(actualObserver, new object[] { @event })!;
43 | }
44 |
45 | if (returnValue is not null) await returnValue;
46 | }
47 | }
48 | }
49 |
50 | bool IsObservingMethod(MethodInfo methodInfo)
51 | {
52 | var isObservingMethod = methodInfo.ReturnType.IsAssignableTo(typeof(Task)) ||
53 | methodInfo.ReturnType == typeof(void);
54 |
55 | if (!isObservingMethod) return false;
56 | var parameters = methodInfo.GetParameters();
57 | if (parameters.Length >= 1)
58 | {
59 | isObservingMethod = parameters[0].ParameterType.IsAssignableTo(typeof(IEvent));
60 | if (parameters.Length == 2)
61 | {
62 | isObservingMethod &= parameters[1].ParameterType == typeof(EventContext);
63 | }
64 | else if (parameters.Length > 2)
65 | {
66 | isObservingMethod = false;
67 | }
68 | return isObservingMethod;
69 | }
70 |
71 | return false;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Chapter12/EventSourcing/Observers.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Fundamentals;
3 |
4 | namespace EventSourcing;
5 |
6 | [Singleton]
7 | public class Observers : IObservers
8 | {
9 | readonly IEnumerable _handlers;
10 |
11 | public Observers(ITypes types, IServiceProvider serviceProvider)
12 | {
13 | _handlers = types.All.Where(_ => _.HasAttribute())
14 | .Select(_ =>
15 | {
16 | var observer = _.GetCustomAttribute()!;
17 | return new ObserverHandler(serviceProvider, _);
18 | });
19 | }
20 |
21 | public Task OnNext(IEvent @event, EventContext context)
22 | {
23 | var tasks = _handlers.Where(_ => _.EventTypes.Contains(@event.GetType()))
24 | .Select(_ => _.OnNext(@event, context));
25 | return Task.WhenAll(tasks);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Chapter12/Events.cs:
--------------------------------------------------------------------------------
1 | using EventSourcing;
2 |
3 | namespace Chapter12;
4 |
5 | public record BankAccountOpened(string CustomerName) : IEvent;
6 | public record BankAccountClosed() : IEvent;
7 | public record DepositPerformed(decimal Amount) : IEvent;
8 | public record WithdrawalPerformed(decimal Amount) : IEvent;
9 |
--------------------------------------------------------------------------------
/Chapter12/Program.cs:
--------------------------------------------------------------------------------
1 | using Chapter12;
2 | using EventSourcing;
3 | using Fundamentals;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | var host = Host.CreateDefaultBuilder()
8 | .ConfigureServices((context, services) =>
9 | {
10 | var types = new Types();
11 | services.AddSingleton(types);
12 | services.AddBindingsByConvention(types);
13 | services.AddSelfBinding(types);
14 | })
15 | .Build();
16 |
17 | var eventLog = host.Services.GetRequiredService();
18 |
19 | var bankAccountId = EventSourceId.New();
20 | eventLog.Append(bankAccountId, new BankAccountOpened("Jane Doe"));
21 | eventLog.Append(bankAccountId, new DepositPerformed(100));
22 | eventLog.Append(bankAccountId, new WithdrawalPerformed(32));
23 | eventLog.Append(bankAccountId, new BankAccountClosed());
24 |
--------------------------------------------------------------------------------
/Chapter13/AdminForNamespace.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 |
3 | namespace Chapter13;
4 |
5 | public class AdminForNamespace : IAuthorizationRequirement
6 | {
7 | public AdminForNamespace(string @namespace)
8 | {
9 | Namespace = @namespace;
10 | }
11 |
12 | public string Namespace { get; }
13 | }
--------------------------------------------------------------------------------
/Chapter13/AdminForNamespaceHandler.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Mvc.Controllers;
3 |
4 | namespace Chapter13;
5 |
6 | public class AdminForNamespaceHandler : AuthorizationHandler
7 | {
8 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminForNamespace requirement)
9 | {
10 | if (context.Resource is HttpContext httpContext)
11 | {
12 | var endpoint = httpContext.GetEndpoint();
13 | if (endpoint is not null)
14 | {
15 | var controllerActionDescriptor = endpoint!.Metadata.GetMetadata();
16 | if (controllerActionDescriptor?
17 | .MethodInfo
18 | .DeclaringType?
19 | .Namespace?
20 | .StartsWith(requirement.Namespace, StringComparison.InvariantCulture) == true &&
21 | !httpContext.User.IsInRole("Admin"))
22 | {
23 | context.Fail();
24 | }
25 | else
26 | {
27 | context.Succeed(requirement);
28 | }
29 | }
30 | }
31 | return Task.CompletedTask;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Chapter13/Chapter13.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | net7.0
9 | enable
10 | enable
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Chapter13/Commands/CommandActionFilter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.Filters;
3 |
4 | namespace Chapter13.Commands;
5 |
6 | public class CommandActionFilter : IAsyncActionFilter
7 | {
8 | public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
9 | {
10 | if (context.HttpContext.Request.Method == HttpMethod.Post.Method)
11 | {
12 | var authorizationResult = context.HttpContext.GetAuthorizationResult();
13 | var isAuthorized = authorizationResult?.Succeeded ?? true;
14 |
15 | var exceptionMessages = new List();
16 | var exceptionStackTrace = string.Empty;
17 | ActionExecutedContext? result = null;
18 | object? response = null;
19 | if (context.ModelState.IsValid && isAuthorized)
20 | {
21 | result = await next();
22 |
23 | if (result.Exception is not null)
24 | {
25 | var exception = result.Exception;
26 | exceptionStackTrace = exception.StackTrace;
27 |
28 | do
29 | {
30 | exceptionMessages.Add(exception.Message);
31 | exception = exception.InnerException;
32 | }
33 | while (exception is not null);
34 |
35 | result.Exception = null!;
36 | }
37 |
38 | if (result.Result is ObjectResult objectResult)
39 | {
40 | response = objectResult.Value;
41 | }
42 | }
43 |
44 | var commandResult = new CommandResult
45 | {
46 | CorrelationId = Guid.NewGuid(),
47 | IsAuthorized = isAuthorized,
48 | ValidationResults = context.ModelState.SelectMany(_ => _.Value!.Errors.Select(e => e.ToValidationResult(_.Key))),
49 | ExceptionMessages = exceptionMessages.ToArray(),
50 | ExceptionStackTrace = exceptionStackTrace ?? string.Empty,
51 | Response = response
52 | };
53 |
54 | if (!commandResult.IsAuthorized)
55 | {
56 | context.HttpContext.Response.StatusCode = 401; // Forbidden: https://www.rfc-editor.org/rfc/rfc9110.html#name-401-unauthorized
57 | }
58 | else if (!commandResult.IsValid)
59 | {
60 | context.HttpContext.Response.StatusCode = 409; // Conflict: https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict
61 | }
62 | else if (commandResult.HasExceptions)
63 | {
64 | context.HttpContext.Response.StatusCode = 500; // Internal Server error: https://www.rfc-editor.org/rfc/rfc9110.html#name-500-internal-server-error
65 | }
66 |
67 | var actualResult = new ObjectResult(commandResult);
68 |
69 | if (result is not null)
70 | {
71 | result.Result = actualResult;
72 | }
73 | else
74 | {
75 | context.Result = actualResult;
76 | }
77 | }
78 | else
79 | {
80 | await next();
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Chapter13/Commands/CommandResult.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter13.Commands;
2 |
3 | public class CommandResult
4 | {
5 | public Guid CorrelationId { get; init; }
6 | public bool IsSuccess => IsAuthorized && IsValid && !HasExceptions;
7 | public bool IsAuthorized { get; init; } = true;
8 | public bool IsValid => !ValidationResults.Any();
9 | public bool HasExceptions => ExceptionMessages.Any();
10 | public IEnumerable ValidationResults { get; init; } = Enumerable.Empty();
11 | public IEnumerable ExceptionMessages { get; init; } = Enumerable.Empty();
12 | public string ExceptionStackTrace { get; init; } = string.Empty;
13 | public object? Response { get; init; }
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter13/Commands/ModelErrorExtensions.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 | using Microsoft.AspNetCore.Mvc.ModelBinding;
3 |
4 | namespace Chapter13.Commands;
5 |
6 | public static class ModelErrorExtensions
7 | {
8 | public static ValidationResult ToValidationResult(this ModelError error, string member)
9 | {
10 | member = string.Join('.', member.Split('.').Select(_ => _.ToCamelCase()));
11 | return new ValidationResult(error.ErrorMessage, member);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Chapter13/Commands/ValidationResult.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter13.Commands;
2 |
3 | public record ValidationResult(string Message, string Member);
4 |
--------------------------------------------------------------------------------
/Chapter13/CrossCuttingAuthorizationMiddlewareResultHandler.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Authorization.Policy;
3 |
4 | namespace Chapter13;
5 |
6 | public class CrossCuttingAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
7 | {
8 | readonly AuthorizationMiddlewareResultHandler _defaultHandler = new();
9 |
10 | public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
11 | {
12 | context.SetAuthorizationResult(authorizeResult);
13 | await _defaultHandler.HandleAsync(next, context, policy, PolicyAuthorizationResult.Success());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Chapter13/CrossCuttingPoliciesProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 |
3 | namespace Chapter13;
4 |
5 | public class CrossCuttingPoliciesProvider : IAuthorizationPolicyProvider
6 | {
7 | readonly AuthorizationPolicy _policy;
8 |
9 | public CrossCuttingPoliciesProvider()
10 | {
11 | _policy = new AuthorizationPolicyBuilder()
12 | .AddRequirements(new AdminForNamespace("Chapter13")
13 | ).Build();
14 | }
15 |
16 | public Task GetDefaultPolicyAsync() => Task.FromResult(_policy);
17 | public Task GetFallbackPolicyAsync() => Task.FromResult(_policy);
18 | public Task GetPolicyAsync(string policyName) => Task.FromResult(_policy);
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter13/Employee.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Chapter13;
4 |
5 | public record Employee(
6 | [Required]
7 | string FirstName,
8 |
9 | [Required]
10 | string LastName);
11 |
--------------------------------------------------------------------------------
/Chapter13/EmployeesController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace Chapter13;
4 |
5 | [Route("/api/employees")]
6 | public class EmployeesController : Controller
7 | {
8 | [HttpPost]
9 | public int Register([FromBody] Employee employee)
10 | {
11 | return 1;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Chapter13/HardCodedAuthenticationHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Claims;
2 | using System.Text.Encodings.Web;
3 | using Microsoft.AspNetCore.Authentication;
4 | using Microsoft.Extensions.Options;
5 |
6 | namespace Chapter13;
7 |
8 | public class HardCodedAuthenticationHandler : AuthenticationHandler
9 | {
10 | public const string SchemeName = "HardCodedAuthenticationHandler";
11 |
12 | public HardCodedAuthenticationHandler(
13 | IOptionsMonitor options,
14 | ILoggerFactory logger,
15 | UrlEncoder encoder,
16 | ISystemClock clock) : base(options, logger, encoder, clock)
17 | {
18 | }
19 |
20 | protected override Task HandleAuthenticateAsync() => Task.FromResult(
21 | AuthenticateResult.Success(
22 | new AuthenticationTicket(
23 | new ClaimsPrincipal(
24 | new ClaimsIdentity(
25 | new[]
26 | {
27 | new Claim(ClaimTypes.Name, "Bob"),
28 | new Claim(ClaimTypes.Role, "User")
29 | },
30 | SchemeName)), SchemeName)));
31 | }
32 |
--------------------------------------------------------------------------------
/Chapter13/HardCodedAuthenticationOptions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authentication;
2 |
3 | namespace Chapter13;
4 |
5 | public class HardCodedAuthenticationOptions : AuthenticationSchemeOptions
6 | {
7 | }
8 |
--------------------------------------------------------------------------------
/Chapter13/HttpContextExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization.Policy;
2 |
3 | namespace Chapter13;
4 |
5 | public static class HttpContextExtensions
6 | {
7 | const string AuthorizeResultKey = "_AuthorizeResult";
8 |
9 | public static PolicyAuthorizationResult? GetAuthorizationResult(this HttpContext context) => (context.Items[AuthorizeResultKey] as PolicyAuthorizationResult)!;
10 |
11 | public static void SetAuthorizationResult(this HttpContext context, PolicyAuthorizationResult result) => context.Items[AuthorizeResultKey] = result;
12 | }
--------------------------------------------------------------------------------
/Chapter13/Program.cs:
--------------------------------------------------------------------------------
1 | using Chapter13;
2 | using Chapter13.Commands;
3 | using Microsoft.AspNetCore.Authorization;
4 |
5 | var builder = WebApplication.CreateBuilder(args);
6 | builder.Services.AddControllers(mvcOptions => mvcOptions.Filters.Add());
7 |
8 | builder.Services.AddAuthorization(options => options.AddPolicy("Chapter13Admins", policy => policy.Requirements.Add(new AdminForNamespace("Chapter13"))));
9 | builder.Services.AddSingleton();
10 | builder.Services.AddSingleton();
11 | builder.Services.AddSingleton();
12 | builder.Services
13 | .AddAuthentication(options => options.DefaultScheme = HardCodedAuthenticationHandler.SchemeName)
14 | .AddScheme(HardCodedAuthenticationHandler.SchemeName, _ => {});
15 |
16 | var app = builder.Build();
17 | app.MapControllers();
18 |
19 | app.UseAuthentication();
20 | app.UseAuthorization();
21 |
22 | app.Run();
23 |
--------------------------------------------------------------------------------
/Chapter13/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter13/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter14/Authenticator.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14;
2 |
3 | public class Authenticator : IAuthenticator
4 | {
5 | public bool Authenticate(string username, string password)
6 | {
7 | return true;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter14/AuthorizationInterceptor.cs:
--------------------------------------------------------------------------------
1 | using Castle.DynamicProxy;
2 |
3 | namespace Chapter14;
4 |
5 | public class AuthorizationInterceptor : IInterceptor
6 | {
7 | readonly IUsersServiceComposition _usersService;
8 |
9 | public AuthorizationInterceptor(IUsersServiceComposition usersService)
10 | {
11 | _usersService = usersService;
12 | }
13 |
14 | public void Intercept(IInvocation invocation)
15 | {
16 | if (_usersService.IsAuthorized("jane@doe.io", invocation.Method.Name))
17 | {
18 | invocation.Proceed();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chapter14/Authorizer.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14;
2 |
3 | public class Authorizer : IAuthorizer
4 | {
5 | public bool IsAuthorized(string username, string action)
6 | {
7 | return false;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter14/Chapter14.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Chapter14/DefaultInstaller.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Castle.DynamicProxy;
3 | using Castle.MicroKernel.Registration;
4 | using Castle.MicroKernel.SubSystems.Configuration;
5 | using Castle.Windsor;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace Chapter14;
9 |
10 | public class DefaultInstaller : IWindsorInstaller
11 | {
12 | public void Install(IWindsorContainer container, IConfigurationStore store)
13 | {
14 | container.Register(Component.For());
15 | container.Register(Component.For());
16 |
17 | container.Register(
18 | Component.For()
19 | .ImplementedBy()
20 | .LifestyleTransient());
21 |
22 | container.Register(
23 | Component.For()
24 | .ImplementedBy()
25 | .LifestyleTransient());
26 |
27 | container.Register(
28 | Component.For()
29 | .ImplementedBy()
30 | .Proxy.AdditionalInterfaces(typeof(IAuthorizer), typeof(IAuthenticator))
31 | .Proxy.MixIns(_ => _
32 | .Component()
33 | .Component())
34 | .Interceptors()
35 | .Interceptors()
36 | .SelectInterceptorsWith(s => s.Service())
37 | .LifestyleTransient());
38 |
39 | container.Register(
40 | Component.For()
41 | .UsingFactoryMethod((kernel, context) =>
42 | {
43 | var proxyGenerator = new ProxyGenerator();
44 | var proxyGenerationOptions = new ProxyGenerationOptions();
45 | proxyGenerationOptions.AddMixinInstance(container.Resolve());
46 | proxyGenerationOptions.AddMixinInstance(container.Resolve());
47 | var logger = container.Resolve>();
48 | proxyGenerationOptions.AddMixinInstance(new UsersService(logger));
49 | return (proxyGenerator.CreateClassProxyWithTarget(
50 | typeof(object),
51 | new[] { typeof(IUsersServiceComposition) },
52 | new object(),
53 | proxyGenerationOptions) as IUsersServiceComposition)!;
54 | }));
55 |
56 | container.Register(Component.For());
57 | container.Register(Classes.FromAssemblyInThisApplication(Assembly.GetEntryAssembly())
58 | .Pick()
59 | .WithService.DefaultInterfaces()
60 | .Configure(_ => _
61 | .Interceptors()
62 | .Interceptors()
63 | .SelectInterceptorsWith(s => s.Service()))
64 | .LifestyleTransient());
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Chapter14/IAuthenticator.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14;
2 |
3 | public interface IAuthenticator
4 | {
5 | bool Authenticate(string username, string password);
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter14/IAuthorizer.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14;
2 |
3 | public interface IAuthorizer
4 | {
5 | bool IsAuthorized(string username, string action);
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter14/IUsersService.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14;
2 |
3 | public interface IUsersService
4 | {
5 | Task Register(string userName, string password);
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter14/IUsersServiceComposition.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14;
2 |
3 | public interface IUsersServiceComposition : IUsersService, IAuthenticator, IAuthorizer
4 | {
5 | }
6 |
--------------------------------------------------------------------------------
/Chapter14/InterceptorSelector.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Castle.DynamicProxy;
3 |
4 | namespace Chapter14;
5 |
6 | public class InterceptorSelector : IInterceptorSelector
7 | {
8 | public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
9 | {
10 | if (type.Namespace?.StartsWith("Chapter14.Todo", StringComparison.InvariantCulture) ?? false)
11 | {
12 | return interceptors;
13 | }
14 |
15 | return interceptors.Where(_ => _.GetType() != typeof(AuthorizationInterceptor)).ToArray();
16 | }
17 | }
--------------------------------------------------------------------------------
/Chapter14/LoggingInterceptor.cs:
--------------------------------------------------------------------------------
1 | using Castle.DynamicProxy;
2 | using Microsoft.Extensions.Logging;
3 |
4 | namespace Chapter14;
5 |
6 | public class LoggingInterceptor : IInterceptor
7 | {
8 | readonly ILoggerFactory _loggerFactory;
9 |
10 | public LoggingInterceptor(ILoggerFactory loggerFactory)
11 | {
12 | _loggerFactory = loggerFactory;
13 | }
14 |
15 | public void Intercept(IInvocation invocation)
16 | {
17 | var logger = _loggerFactory.CreateLogger(invocation.TargetType)!;
18 | logger.BeforeInvocation(invocation.Method.Name);
19 |
20 | try
21 | {
22 | invocation.Proceed();
23 |
24 | if (invocation.ReturnValue is Task task)
25 | {
26 | task.ContinueWith(t =>
27 | {
28 | if (t.IsFaulted)
29 | {
30 | logger.InvocationError(invocation.Method.Name, t.Exception!);
31 | }
32 | else
33 | {
34 | logger.AfterInvocation(invocation.Method.Name);
35 | }
36 | });
37 | }
38 | else
39 | {
40 | logger.AfterInvocation(invocation.Method.Name);
41 | }
42 | }
43 | catch (Exception ex)
44 | {
45 | logger.InvocationError(invocation.Method.Name, ex);
46 | throw;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Chapter14/LoggingInterceptorLogMessages.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 |
3 | namespace Chapter14;
4 |
5 | internal static partial class LoggingInterceptorLogMessages
6 | {
7 | [LoggerMessage(1, LogLevel.Information, "Before invoking {Method}", EventName = "BeforeInvocation")]
8 | internal static partial void BeforeInvocation(this ILogger logger, string method);
9 |
10 | [LoggerMessage(2, LogLevel.Error, "Error invoking {Method}", EventName = "InvocationError")]
11 | internal static partial void InvocationError(this ILogger logger, string method, Exception exception);
12 |
13 | [LoggerMessage(3, LogLevel.Information, "Before invoking {Method}", EventName = "AfterInvocation")]
14 | internal static partial void AfterInvocation(this ILogger logger, string method);
15 | }
--------------------------------------------------------------------------------
/Chapter14/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Castle.MicroKernel.Registration;
3 | using Castle.Windsor;
4 | using Castle.Windsor.Installer;
5 | using Chapter14;
6 | using Chapter14.Todo;
7 | using Microsoft.Extensions.Logging;
8 |
9 |
10 | var container = new WindsorContainer();
11 |
12 | var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
13 | container.Register(Component.For().Instance(loggerFactory));
14 |
15 | var createLoggerMethod = typeof(LoggerFactoryExtensions)
16 | .GetMethods(BindingFlags.Public | BindingFlags.Static)
17 | .First(_ => _.Name == nameof(LoggerFactory.CreateLogger) && _.IsGenericMethod);
18 |
19 | container.Register(Component.For().UsingFactoryMethod((kernel, context) =>
20 | {
21 | var loggerFactory = kernel.Resolve();
22 | return loggerFactory.CreateLogger(context.Handler.ComponentModel.Implementation);
23 | }).LifestyleTransient());
24 | container.Register(Component.For(typeof(ILogger<>)).UsingFactoryMethod((kernel, context) =>
25 | {
26 | var loggerFactory = kernel.Resolve();
27 | var logger = createLoggerMethod.MakeGenericMethod(context.RequestedType.GenericTypeArguments[0]).Invoke(null, new[] { loggerFactory });
28 | return logger;
29 | }));
30 |
31 | container.Install(FromAssembly.InThisApplication(Assembly.GetEntryAssembly()));
32 |
33 | var usersService = container.Resolve();
34 | var result = await usersService.Register("jane@doe.io", "Password1");
35 |
36 | var authenticated = (usersService as IAuthenticator)!.Authenticate("jane@doe.io", "Password1");
37 | var authorized = (usersService as IAuthorizer)!.IsAuthorized("jane@doe.io", "Some Action");
38 | Console.WriteLine($"Authenticated: {authenticated}");
39 | Console.WriteLine($"Authorized: {authorized}");
40 |
41 | var composition = container.Resolve();
42 | authenticated = composition.Authenticate("jane@doe.io", "Password1");
43 | authorized = composition.IsAuthorized("jane@doe.io", "Some Action");
44 | Console.WriteLine($"Authenticated: {authenticated}");
45 | Console.WriteLine($"Authorized: {authorized}");
46 |
47 | var todo = container.Resolve();
48 | todo.Add("Buy milk");
49 |
50 | Console.ReadLine();
--------------------------------------------------------------------------------
/Chapter14/Todo/ITodoService.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14.Todo;
2 |
3 | public interface ITodoService
4 | {
5 | void Add(string item);
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/Chapter14/Todo/TodoService.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter14.Todo;
2 |
3 | public class TodoService : ITodoService
4 | {
5 | public void Add(string item)
6 | {
7 | Console.WriteLine($"Adding '{item}' to the todo list");
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter14/UsersService.cs:
--------------------------------------------------------------------------------
1 |
2 | using Microsoft.Extensions.Logging;
3 |
4 | namespace Chapter14;
5 |
6 | public class UsersService : IUsersService
7 | {
8 | readonly ILogger _logger;
9 |
10 | public UsersService(ILogger logger)
11 | {
12 | _logger = logger;
13 | }
14 |
15 | public Task Register(string userName, string password)
16 | {
17 | _logger.LogInformation("Inside register method");
18 | var id = Guid.NewGuid();
19 | return Task.FromResult(id);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chapter15/Chapter15.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Chapter15/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://aka.ms/new-console-template for more information
2 | Console.WriteLine("Hello, World!");
3 |
--------------------------------------------------------------------------------
/Chapter15/Something.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter15;
2 |
3 | ///
4 | /// asdad.
5 | ///
6 | public class Something
7 | {
8 | readonly string _blah;
9 |
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | public Something()
14 | {
15 | _blah = "blah";
16 |
17 | Console.WriteLine(_blah);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter16/Chapter16.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Exe
7 | net7.0
8 | enable
9 | enable
10 | true
11 | $(MSBuildThisFileDirectory)GDPRReport.txt
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Chapter16/Employee.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals.Compliance.GDPR;
2 |
3 | namespace Chapter16;
4 |
5 | public record Employee(
6 | [PersonalIdentifiableInformation("Needed for registration")]
7 | string FirstName,
8 |
9 | [PersonalIdentifiableInformation("Needed for registration")]
10 | string LastName,
11 |
12 | [PersonalIdentifiableInformation("Needed for uniquely identifying an employee")]
13 | string SocialSecurityNumber);
--------------------------------------------------------------------------------
/Chapter16/EmployeesController.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Diagnostics.Metrics;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Chapter16;
6 |
7 | [Route("/api/employees")]
8 | public class EmployeesController : Controller
9 | {
10 | static readonly Counter _registeredEmployees = Metrics.Meter.CreateCounter("RegisteredEmployees", "# of registered employees");
11 |
12 | [HttpGet("manual")]
13 | public IActionResult RegisterManual()
14 | {
15 | var now = DateTimeOffset.UtcNow;
16 | var tags = new TagList(new ReadOnlySpan>(new KeyValuePair[]
17 | {
18 | new("Year", now.Year),
19 | new("Month", now.Month),
20 | new("Day", now.Day),
21 | }));
22 |
23 | _registeredEmployees.Add(1, tags);
24 |
25 | return Ok();
26 | }
27 |
28 | [HttpGet]
29 | public IActionResult Register()
30 | {
31 | EmployeesControllerMetrics.RegisteredEmployees(DateOnly.FromDateTime(DateTime.UtcNow));
32 |
33 | return Ok();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Chapter16/EmployeesControllerMetrics.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.Metrics;
2 | using Fundamentals.Metrics;
3 |
4 | namespace Chapter16;
5 |
6 | public static partial class EmployeesControllerMetrics
7 | {
8 | [Counter("RegisteredEmployees", "# of registered employees")]
9 | public static partial void RegisteredEmployees(DateOnly date);
10 | }
--------------------------------------------------------------------------------
/Chapter16/GDPRReport.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Metaprogramming-in-C-Sharp/ac312276b9e01f8c6caa800fd1dab10ace942cc6/Chapter16/GDPRReport.txt
--------------------------------------------------------------------------------
/Chapter16/Metrics.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.Metrics;
2 |
3 | namespace Chapter16;
4 |
5 | public static class Metrics
6 | {
7 | public static readonly Meter Meter = new("Chapter16");
8 | }
--------------------------------------------------------------------------------
/Chapter16/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.Metrics;
2 | using Fundamentals.Metrics;
3 |
4 | GlobalMetrics.Meter = new Meter("Chapter16");
5 |
6 | var builder = WebApplication.CreateBuilder(args);
7 | builder.Services.AddControllers();
8 |
9 | var app = builder.Build();
10 | app.UseRouting();
11 | app.MapControllers();
12 | app.Run();
13 |
--------------------------------------------------------------------------------
/Chapter16/ProgramMetrics.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals.Metrics;
2 |
3 | namespace ConsoleApp;
4 |
5 | public static partial class ProgramMetrics
6 | {
7 | [Counter("Started", "How many times we've started the program")]
8 | public static partial void Started(int something, string blabbedi, string theThird);
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter16/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter16/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter16/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | dotnet clean
3 | dotnet build-server shutdown
4 | dotnet build
--------------------------------------------------------------------------------
/Chapter17/Chapter17.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Exe
10 | net7.0
11 | enable
12 | enable
13 | $(MSBuildThisFileDirectory)GDPRReport.txt
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Chapter17/GDPRReport.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Metaprogramming-in-C-Sharp/ac312276b9e01f8c6caa800fd1dab10ace942cc6/Chapter17/GDPRReport.txt
--------------------------------------------------------------------------------
/Chapter17/MyException.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter17;
2 |
3 | public class MyException : Exception
4 | {
5 | }
6 |
--------------------------------------------------------------------------------
/Chapter17/Program.cs:
--------------------------------------------------------------------------------
1 | using Chapter17;
2 |
3 | throw new MyException();
--------------------------------------------------------------------------------
/Chapter2/Calculator.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter2;
2 |
3 | public static class Calculator
4 | {
5 | public static int Add(int left, int right) => left + right;
6 | }
7 |
--------------------------------------------------------------------------------
/Chapter2/CalculatorTests.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | namespace Chapter2;
4 |
5 | public class CalculatorTests
6 | {
7 | [Fact]
8 | public void Add()
9 | {
10 | // Arrange
11 | var left = 5;
12 | var right = 3;
13 | var expectedResult = 8;
14 |
15 | // Act
16 | var actualResult = Calculator.Add(left, right);
17 | Assert.Equal(expectedResult, actualResult);
18 | }
19 | }
--------------------------------------------------------------------------------
/Chapter2/Chapter2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 | true
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Chapter2/Person.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter2;
2 |
3 | public class Person
4 | {
5 | public string FirstName { get; set; }
6 | public string LastName { get; set; }
7 | public string SocialSecurityNumber { get; set; }
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter2/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://aka.ms/new-console-template for more information
2 | using Chapter2;
3 |
4 | Console.WriteLine("Hello, World!");
5 |
6 |
7 | var person = new Person
8 | {
9 | FirstName = "Jane",
10 | LastName = "Doe",
11 | SocialSecurityNumber = "12345abcd"
12 | };
13 | Console.WriteLine(Serializer.SerializeToJson(person));
--------------------------------------------------------------------------------
/Chapter2/Serializer.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace Chapter2;
4 |
5 | public static class Serializer
6 | {
7 | public static string SerializeToJson(object instance)
8 | {
9 | var stringBuilder = new StringBuilder();
10 | var type = instance.GetType();
11 | var properties = type.GetProperties();
12 | stringBuilder.Append("{\n");
13 | var first = true;
14 |
15 | foreach (var property in properties)
16 | {
17 | if (!first)
18 | {
19 | stringBuilder.Append(",\n");
20 | }
21 | first = false;
22 | stringBuilder.Append($" \"{property.Name}\": \"{property.GetValue(instance)}\"");
23 | }
24 |
25 | stringBuilder.Append("\n}");
26 |
27 | return stringBuilder.ToString();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Chapter3/Chapter3.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Chapter3/Employee.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Chapter3;
4 |
5 | public record Employee(
6 | [Required]
7 | string FirstName,
8 |
9 | [Required]
10 | string LastName);
11 |
--------------------------------------------------------------------------------
/Chapter3/EmployeesController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace Chapter3;
4 |
5 | [Route("/api/employees")]
6 | public class EmployeesController : Controller
7 | {
8 | [HttpPost]
9 | public IActionResult Register(Employee employee)
10 | {
11 | return Ok();
12 | }
13 |
14 | [HttpGet]
15 | public IEnumerable AllEmployees()
16 | {
17 | return new Employee[]
18 | {
19 | new("Jane", "Doe"),
20 | new("John", "Doe")
21 | };
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Chapter3/Program.cs:
--------------------------------------------------------------------------------
1 | using Chapter3;
2 | var builder = WebApplication.CreateBuilder(args);
3 | builder.Services.AddControllers(mvcOptions => mvcOptions.Filters.Add());
4 | var app = builder.Build();
5 | app.MapControllers();
6 | app.Run();
7 |
--------------------------------------------------------------------------------
/Chapter3/ValidationFilter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.Filters;
3 |
4 | namespace Chapter3;
5 |
6 | public class ValidationFilter : IAsyncActionFilter
7 | {
8 | public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
9 | {
10 | if (context.ModelState.IsValid)
11 | {
12 | await next();
13 | }
14 | else
15 | {
16 | context.Result = new BadRequestObjectResult(new ValidationProblemDetails(context.ModelState));
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/Chapter3/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Chapter3/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter3/employees:
--------------------------------------------------------------------------------
1 | HEllo world
--------------------------------------------------------------------------------
/Chapter4/AssemblyDiscovery/AssemblyDiscovery.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Chapter4/AssemblyDiscovery/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | var assembly = Assembly.GetEntryAssembly();
4 | Console.WriteLine(assembly!.FullName);
5 | var assemblies = assembly!.GetReferencedAssemblies();
6 | var assemblyNames = string.Join(", ", assemblies.Select(_ => _.Name));
7 | Console.WriteLine(assemblyNames);
8 |
9 |
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Concepts/Concepts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net7.0
4 | enable
5 | enable
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Concepts/Employees/FirstName.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace Concepts.Employees;
4 |
5 | public record FirstName(string Value) : PIIConceptAs(Value);
6 |
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Concepts/Employees/LastName.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace Concepts.Employees;
4 |
5 | public record LastName(string Value) : PIIConceptAs(Value);
6 |
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Concepts/Employees/SocialSecurityNumber.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace Concepts.Employees;
4 |
5 | public record SocialSecurityNumber(string Value) : PIIConceptAs(Value);
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Domain/Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net7.0
4 | enable
5 | enable
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Domain/Employees/RegisterEmployee.cs:
--------------------------------------------------------------------------------
1 | using Concepts.Employees;
2 | using Fundamentals;
3 |
4 | namespace Domain.Employees;
5 |
6 | public class RegisterEmployee : ICommand
7 | {
8 | public FirstName FirstName { get; set; } = new(string.Empty);
9 | public LastName LastName { get; set; } = new(string.Empty);
10 | public SocialSecurityNumber SocialSecurityNumber { get; set; } = new(string.Empty);
11 | }
12 |
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Domain/Employees/SetSalaryLevelForEmployee.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals;
2 |
3 | namespace Domain.Employees;
4 |
5 | public class SetSalaryLevelForEmployee : ICommand
6 | {
7 | public string SocialSecurityNumber { get; set; } = string.Empty;
8 | public decimal SalaryLevel { get; set; }
9 | }
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Main/Main.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Exe
10 | net7.0
11 | enable
12 | enable
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Chapter4/BusinessApp/Main/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Fundamentals;
3 |
4 | var types = new Types();
5 | var commands = types.FindMultiple();
6 | var typeNames = string.Join("\n", commands.Select(_ => _.Name));
7 | Console.WriteLine(typeNames);
8 |
9 | Console.WriteLine("\n\nGDPR Report");
10 | var typesWithConcepts = types.All
11 | .SelectMany(_ =>
12 | _.GetProperties()
13 | .Where(p => p.PropertyType.IsPIIConcept()))
14 | .GroupBy(_ => _.DeclaringType);
15 |
16 | foreach (var typeWithConcepts in typesWithConcepts)
17 | {
18 | Console.WriteLine($"Type: {typeWithConcepts.Key!.FullName}");
19 | foreach (var property in typeWithConcepts)
20 | {
21 | Console.WriteLine($" Property : {property.Name}");
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Chapter5/Chapter5.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Chapter5/Employee.cs:
--------------------------------------------------------------------------------
1 | using Fundamentals.Compliance.GDPR;
2 |
3 | namespace Chapter5;
4 |
5 | public class Employee
6 | {
7 | [PersonalIdentifiableInformation("Employment records")]
8 | public string FirstName { get; set; } = string.Empty;
9 |
10 | [PersonalIdentifiableInformation("Employment records")]
11 | public string LastName { get; set; } = string.Empty;
12 |
13 | [PersonalIdentifiableInformation("Uniquely identifies the employee")]
14 | public string SocialSecurityNumber { get; set; } = string.Empty;
15 | }
16 |
--------------------------------------------------------------------------------
/Chapter5/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Fundamentals;
3 |
4 | var types = new Types();
5 |
6 | var piiTypes = types.All.Where(_ => _
7 | .GetMembers()
8 | .Any(m => m.HasAttribute()));
9 | var typeNames = string.Join("\n", piiTypes.Select(_ => _.FullName));
10 | Console.WriteLine(typeNames);
11 |
12 | Console.WriteLine("\n\nGDPR Report");
13 | var typesWithPII = types.All
14 | .SelectMany(_ =>
15 | _.GetProperties()
16 | .Where(p => p.HasAttribute()))
17 | .GroupBy(_ => _.DeclaringType);
18 |
19 | foreach (var typeWithPII in typesWithPII)
20 | {
21 | Console.WriteLine($"Type: {typeWithPII.Key!.FullName}");
22 | foreach (var property in typeWithPII)
23 | {
24 | var pii = property.GetCustomAttribute();
25 | Console.WriteLine($" Property : {property.Name}");
26 | Console.WriteLine($" Reason : {pii!.ReasonForCollecting}");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Chapter6/Chapter6.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Chapter6/Employee.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter6;
2 |
3 | public class Employee
4 | {
5 | [NotifyChangesFor(nameof(FullName))]
6 | public virtual string FirstName { get; set; } = string.Empty;
7 |
8 | [NotifyChangesFor(nameof(FullName))]
9 | public virtual string LastName { get; set; } = string.Empty;
10 |
11 | public virtual string FullName => $"{FirstName} {LastName}";
12 | }
--------------------------------------------------------------------------------
/Chapter6/IgnoreChangesAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter6;
2 |
3 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
4 | public class IgnoreChangesAttribute : Attribute
5 | {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/Chapter6/MyTypeGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Reflection.Emit;
3 |
4 | namespace Chapter6;
5 |
6 | public class MyTypeGenerator
7 | {
8 | public static Type Generate()
9 | {
10 | var name = new AssemblyName("MyDynamicAssembly");
11 | var assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
12 | var module = assembly.DefineDynamicModule("MyDynamicModule");
13 | var typeBuilder = module.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
14 | var methodBuilder = typeBuilder.DefineMethod("SaySomething", MethodAttributes.Public);
15 | methodBuilder.SetParameters(typeof(string));
16 | methodBuilder.DefineParameter(0, ParameterAttributes.None, "message");
17 | var methodILGenerator = methodBuilder.GetILGenerator();
18 |
19 | var consoleType = typeof(Console);
20 | var writeLineMethod = consoleType.GetMethod(nameof(Console.WriteLine), new[] { typeof(string) })!;
21 |
22 | methodILGenerator.Emit(OpCodes.Ldarg_1);
23 | methodILGenerator.EmitCall(OpCodes.Call, writeLineMethod, new[] { typeof(string) });
24 | methodILGenerator.Emit(OpCodes.Ret);
25 |
26 | var toStringMethod = typeof(object).GetMethod(nameof(object.ToString))!;
27 | var newToStringMethod = typeBuilder.DefineMethod(nameof(object.ToString), toStringMethod.Attributes, typeof(string), Array.Empty());
28 | var toStringGenerator = newToStringMethod.GetILGenerator();
29 | toStringGenerator.Emit(OpCodes.Ldstr, "A message from ToString()");
30 | toStringGenerator.Emit(OpCodes.Ret);
31 | typeBuilder.DefineMethodOverride(newToStringMethod, toStringMethod);
32 |
33 | return typeBuilder.CreateType()!;
34 | }
35 | }
--------------------------------------------------------------------------------
/Chapter6/NotifyChangesForAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter6;
2 |
3 | [AttributeUsage(AttributeTargets.Property)]
4 | public class NotifyChangesForAttribute : Attribute
5 | {
6 | public NotifyChangesForAttribute(params string[] propertyNames)
7 | {
8 | PropertyNames = propertyNames;
9 | }
10 |
11 | public string[] PropertyNames { get; }
12 | }
13 |
--------------------------------------------------------------------------------
/Chapter6/Program.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Chapter6;
3 |
4 | var myType = MyTypeGenerator.Generate();
5 | var method = myType.GetMethod("SaySomething")!;
6 | var myTypeInstance = Activator.CreateInstance(myType);
7 | method.Invoke(myTypeInstance, new[] { "Hello world" });
8 |
9 | Console.WriteLine(myTypeInstance);
10 |
11 | var type = NotifyingObjectWeaver.GetProxyType();
12 | Console.WriteLine($"Type name : {type}");
13 |
14 | var instance = (Activator.CreateInstance(type) as INotifyPropertyChanged)!;
15 | instance.PropertyChanged += (sender, e) => Console.WriteLine($"{e.PropertyName} changed");
16 |
17 | var instanceAsViewModel = (instance as Employee)!;
18 | instanceAsViewModel.FirstName = "John";
19 |
--------------------------------------------------------------------------------
/Chapter7/Chapter7.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Chapter7/Employee.cs:
--------------------------------------------------------------------------------
1 | namespace Chapter7;
2 |
3 | public record Employee(string FirstName, string LastName);
--------------------------------------------------------------------------------
/Chapter7/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Linq.Expressions;
2 | using Chapter7;
3 |
4 | Expression e = (int i) => Console.WriteLine("Hello");
5 |
6 | int[] items = { 1, 2, 3, 42, 84, 37, 23 };
7 |
8 | // Imperative
9 | var filtered = new List();
10 | foreach (var item in items)
11 | {
12 | if (item >= 37)
13 | {
14 | filtered.Add(item);
15 | }
16 | }
17 | Console.WriteLine(string.Join(',', filtered));
18 |
19 | // LINQ
20 | var selectedItems =
21 | from i in items
22 | where i >= 37
23 | select i;
24 |
25 | Console.WriteLine(string.Join(',', selectedItems));
26 |
27 | // LINQ fluent interface
28 | var selectedItemsExpressions = items
29 | .Where(i => i >= 37)
30 | .Select(i => i);
31 |
32 | Console.WriteLine(string.Join(',', selectedItemsExpressions));
33 |
34 | // LINQ fluent interface with expression
35 | Expression> filter = (i) => i >= 37;
36 |
37 | var selectedItemsQueryable = items
38 | .AsQueryable()
39 | .Where(filter)
40 | .Select(i => i);
41 |
42 | Console.WriteLine(string.Join(',', selectedItemsQueryable));
43 |
44 | Expression> addExpression = (i) => i + 42;
45 |
46 | Console.WriteLine(addExpression);
47 |
48 | Expression> employeeFilter = (employee) => employee.FirstName == "Jane";
49 |
50 | Console.WriteLine(employeeFilter);
51 |
--------------------------------------------------------------------------------
/Chapter8/Chapter8.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Chapter8/DictionaryStringObjectJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace Chapter8;
5 |
6 | // From: https://josef.codes/custom-dictionary-string-object-jsonconverter-for-system-text-json/
7 | public class DictionaryStringObjectJsonConverter : JsonConverter>
8 | {
9 | public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
10 | {
11 | if (reader.TokenType != JsonTokenType.StartObject)
12 | {
13 | throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported");
14 | }
15 |
16 | var dictionary = new Dictionary();
17 | while (reader.Read())
18 | {
19 | if (reader.TokenType == JsonTokenType.EndObject)
20 | {
21 | return dictionary;
22 | }
23 |
24 | if (reader.TokenType != JsonTokenType.PropertyName)
25 | {
26 | throw new JsonException("JsonTokenType was not PropertyName");
27 | }
28 |
29 | var propertyName = reader.GetString();
30 |
31 | if (string.IsNullOrWhiteSpace(propertyName))
32 | {
33 | throw new JsonException("Failed to get property name");
34 | }
35 |
36 | reader.Read();
37 |
38 | dictionary.Add(propertyName, ExtractValue(ref reader, options));
39 | }
40 |
41 | return dictionary;
42 | }
43 |
44 | public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options)
45 | {
46 | JsonSerializer.Serialize(writer, value, options);
47 | }
48 |
49 | private object ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions options)
50 | {
51 | switch (reader.TokenType)
52 | {
53 | case JsonTokenType.String:
54 | if (reader.TryGetDateTime(out var date))
55 | {
56 | return date;
57 | }
58 | return reader.GetString();
59 | case JsonTokenType.False:
60 | return false;
61 | case JsonTokenType.True:
62 | return true;
63 | case JsonTokenType.Null:
64 | return null;
65 | case JsonTokenType.Number:
66 | if (reader.TryGetInt32(out var result))
67 | {
68 | return result;
69 | }
70 | return reader.GetDecimal();
71 | case JsonTokenType.StartObject:
72 | return Read(ref reader, null, options);
73 | case JsonTokenType.StartArray:
74 | var list = new List