From June 2016 that ASP.NET Core 1.0 released I have used it at least in 2 projects. Each of them learned me new points especially in project architecture including layering, DI, mapping and DTOs. While ago I wrote about my experience here. Now I am trying to enhance previous experiences and describe a project based on the enhanced structure.
One of most important decisions is project layers. Personally I do not like multiple layers, but here I choose to have 3 layers for a good reason. I want to hide database from presentation. I do not like Controllers or Web APIs be aware of internal structure of tables and fields. Because in this way:
Designing controller actions and Web APIs are easier as they do not have to know everything about internal table designs
Security is higher. As ASP.NET binding does not fill input parameters with data from user. Indeed as services are not aware of complete model (table) design, they can not bind it incorrect or malicious inputs of the user.
Avoiding dirty checking mechanism of ORMs. If you receive an entire db model, there is a change that Entity Framework detects it as a dirty object and tries to save it in database while you did not mean it.
Avoiding confusing mappings by have only needed properties
It is my suggested layering:
ASP.NET Core project layers[/caption]ASP.NET Core project layers[/caption]ASP.NET Core project layers[/caption]
User works with presentation layer. Presentation layer is aware of only service layer and transfer data to it via DTOs. Service layer in turn communicates with data access layer via database models. In other hands service layer isolates presentation and data access layers from each other. Data access layer contains anything that should not be visible to presentation layer.
Presentation layer can be supposed as a thin layer as it contains database models and DbContext only. Service layer contains repositories and all services. Presentation layer contains ASP.NET controllers, cshtml and css files.
Entity Framework’s DbContext itself is a repository itself. Normally there is no need to warp its Add method. Unless in enterprise projects that special processes are needed on each add or update. For example setting last update time in a specific field. Adding extra repository on DbContext/DbSet makes it harder if you want to update just some fields of a record.
3- Unit of work
Unit of work is usually not a tough problem. You simply call SaveChanges() of DbContext on Dispose method of the controller. This provides automatic unit of work to all actions. But wait, there is a special case that this is not a good idea. What if a problem occurs while committing changes to the database?
You will not be aware of that. Worse than it your changes fail to commit to database and the user will not be even aware of it. Because when Dispose method is called, user response has been sent to the user’s machine and it is too late to inform him/her. My idea is not using Dispose method and call SaveChanges() manually on each Controller action so you can detect possible errors and inform the user about it.
4- Handling validations in application or in database?
One popular approach to do database validations is to commit data changes into database and see if everything goes well or not. For example putting duplicate values into a unique column does not cause errors in application side, but when it is sent to database it will generate an error complaining about duplicate values.
There 2 approaches here. First, leave it as is and let database to control our business logic for us. This approach almost needs no effort to implement as everything is passed to database side. But it requires accurate model definition as that will be converted to database schema. This approach is dependent on underlying database. If underlying database changes, there is chance that behavior of the system changes. One thing that works on MSSQL 2014 may not work as same as SQLite. This approach also make unit tests hard. As some business logic is not in the code so can be unit tested.
Second approach in not relying on database. All validations and rules are checked in application itself. Here there is no dependency on database and unit test can be applied well but this need extra codes and also have performance penalty as database is hit more than once. At least one time for validations and one time for committing changes to the database. Normally my personal choice is to do validations in application side.
5- A class for each service
I has been used to use some big classes for services and use multiple DTO or ViewModel or Model classes to get input from ASP.NET binder from user input. But now I think it is not a good idea. It was causing large service classes that can be considered God classes along with multiple model classes that contains only simple properties and no methods.
Now I prefer to use a separate class for each single service. It contains all model properties and needed code for implementing that service. It is more OOP and it is more manageable. In ASP.NET Core I use IServiceProvider to the class so it can get needed services form ASP.NET Core internal DI. See a sample code:
void Init(IServiceProvider serviceProvider)
_userManager = (UserManager<User>)serviceProvider.GetService(typeof(UserManager<User>));
_matchService = (MatchService)serviceProvider.GetService(typeof(MatchService));
6- Misc points
Model’s Id type to be GUID not auto increment integer value. It has better database performance as you do not need to query to database again to get assigned id.
Not using interfaces at all. It is not useful.
Use bower or similar tools to install client side frameworks.
Be careful while using AutMapper. Properties can be simply different in 2 sides and no error is raised when it is.