The purpose of this repository is to showcase various tools, development principles, solution architecture and the usage of 3rd party tools that demonstrate my experience. This demo code solution uses .NETCore 2.2. It is built in modules, so it does not implement any architecture design ot principle.
Although the solution is not strictly Clean Architecture, it does implement the basic principles. I did take some shortcuts in order to make it easier to follow the code and try it out. One of the rules of CA is a strict direction of data flow and to use interfaces when communicating the opposite direction.
One way to implement these rules, is to use 3rd prty packages that reduce the amount of code you have to write. An example of such a package is documented below (Automapper).
Each software module should have only one reason to change.
This link demonstrates the smell and this link demonstrates the fix.
Software entities should be open for extension, but closed for modification
There are at least three wyas to resolve the OPC:
- Using parameters.
- Virtual methods.
- Factories. This option can be expanded to use interfaces instead of abstract classes and reflection to eliminate the conditional code.
Subtypes must be substitutable for their base types
There are at least three smells that can be used to identify when we break LSP:
- The need to check the type,
- The need to check for nulls,
- We don't implement the whole contract and require to throw a NotImplementedException.
The forst two break the Tell, Don't Ask Principle. This link shows some possible fixes.
Clients should not be forced to depend on methods they do not use.
Closely related to LSP, this code adds another smell that breaks ISP. And here is a possible fix.
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
This code shows us the problem. and a possible solution. One thing to note, .NET Core has a built-in IoC(Inversion of Control) container for us to use.
To view the code and execution, make the NetCoreAsync project as the startup project. It uses a simple breakfast preparation steps to illustrate how async helps you prepare it faster and how it helps the code be more scalabale.
In Program.cs, you will see the natural regression from non-async code to an async code: Uncomment each PrepareBreakfast call to see the execution.
- sync.PrepareBrakfast() is a non-async call.
- async.PrepareBreakfast() shows you how to convert the non-async code into async code.
- async.PrepareBreakfast2() groups together related methods to make the code more readable.
- async.PrepareBreakfast3() adds the WhenAll functionality to improve the readbility and async controll better.
Microsoft introduced ILogger to help us log information. There are numerous other 3rd party packages that extend ILOgger and add additional functionality. I use Serilog in order to help me log information and errors in JSON format in a daily rolling file with a max size.
The Infrastructure.Log project does it all. It is referenced by each project that requires to log something. I set up some properties in Startup.cs in a private AddSerilogServices() method. We are also loading the class as scoped. The reason being is that it also generates a unique ID for each http request, so when we log the events and errors, we can easily search by the ID and order by the date so we can see a complete picture of a specific request. You can vew the code here.
Serilog has a rich number of supporting packages. The following NugetPackages are used:
- Serilog.Exceptions which makes it easier to log exceptions and their properties.
- Serilog.Settings.Configuration is used to read some configuration settings
- Serilog.Sinks.Async is used to log asynchronously
- Serilog.Sinks.RollingFile allows me to easily setup the rolling log file.
Notice that we are sending a parameter type to the logger methods. This allows us to send in as many parameters as are needed to be logged as illustrated by the following interface contract:
void LogError(Exception ex, params object[] data);
Being a .NETCore solution, EFCore is being used. While most of the demos do not require a database, some calls under the EFCore menu item do, so you would need to perform the following steps:
- Install the Wide World Importers database
- Download the WideWorldImporters-Standard.bak backup database in order to restore it from this site.
- Compile the application stored procedures in Infrastructure.Database.SQL.StoredProcedures
- Update the EFCore connection string in the appsettings file.
AutoMapper is a library I use to map the different data objects that are passed between the different layers in Clean Architecture solution. Mapper rules are setup in WebUi.Shared.DataMapper class and is added as a service in Startup.cs.
I use XUnit to run my tests. It has a few nice features:
- Useage of constructors instead of initializers
- Test fixtures that are loaded once and used in every test
- Pass in data as parameters to test cases that will allow you to run the same test with multiple data elements. You can see this in this test. OrderAuthorizationTests() expects two classes that will return the parameters the test method requires.
- Will start an Http Client based on the startup settings.
This package automatically generates json and a UI representation of your services. You can expand it by adding .NET XML documentation to the properties and methods and creating test data for the models. All the settings are in one class that is referenced in the startup class. You can also document sample data
To get the JSON code that can be used to communicate with consumers, add the following to the end of the home URL: /swagger/v1.0/swagger.json e.g. http://localhost:62681/swagger/v1.0/swagger.json
To view the UI, add the following to the end of the home URL: /swagger/index.html e.g. http://localhost:62681/swagger/index.html
MiniProfiler helps us log performance counters to better detect any performance issues. It's a configurable set of functions that display data on an MVC page, store data in a database, implements role security and hooks into Http and EF Core calls to supply additional details like the SQL statement or Http details.
Once you install the related Nuget packages and configure them in the Startup, all you need to do is add it to the UI. You can see the output from the Misc\MiniProfiler menu.
We use middleware to manage the request pipeline. Any classes that the middleware require need to be added to the container in Startup.cs and we need to also add the middleware to the application also in Startup.Configure() method.
This middleware automatically logs the request values and the response, It can be found here
This middleware will catch any unhandled exceptions, build a custom response and return it so we won't return any secure information just in case someone missed a try\catch.
We can now explicitly return the type of the action. In the past, we used to return ActionResult, but now we can explicitly say what the type is, e.g. Task<ActionResult>.
I added a custom validator attribute to showcase how we can pass the existing property we need to validate and anotherproperty value into the validator. The validator code can be found here and is used as follows:
[DisplayName("Preferred Name")]
public string PreferredName { get; set; }
[DisplayName("Logon Name")]
[ValidLogon("PreferredName", ErrorMessage = "Invalid Logon")]
public string LogonName { get; set; }
As you can see, we are validating the LogonName against the PreferredName.
Remote validations perform an asynchronous call to the server to perform a validation while the client continues to fill out the form. For example, if an email address has to be unique, once they tab away from the input, and continue to fill out the form, an AJAX call is made to make sure the field is not already in use. The validation code can be found here at the end in the PhoneNumberIsUnique() method and is setup as an attribute on the property:
[Remote("CheckIfEmailAlreadyExists", "Employees", ErrorMessage = "Email already exists")]
public string EmailAddress { get; set; }
The first parameter is the action an the second is the controller.
This is the source code. You can see it in action by running the site and selecting Closures from the JQUery menu.
This is the source code. You can see it in action by running the site and selecting Promises from the JQUery menu.
This demonstrates a simple AJAX call to get some results from an API call. You can look at the code here and see it in action by running the site and selecting AJAX from the JQUery menu.
This code demonstrates the drag\drop features, append, JQuery data and and how to manage JSON data.
The following section lists additional code I want to add to the solution.