This project is meant to test and combine the most common features of a web API.
It is made up of a web app in C# and a database running postgresql.
To start the database run docker-compose up in the terminal from the root folder of the project. This will start two containers:
- postgres: runs the database;
username=mwtest, password=mwtest, port=5432, database=mwtest - pgadmin: eases access to the database. It is reachable at
localhost:5050;
username=pgadmin4@pgadmin.org, password=admin
To run migrations execute dotnet ef database update in the terminal from the root folder of the project.
The documentation for the api is served at https://localhost:5000/swagger
- database connection (EntityFramework + Postgresql)
- database migrations (EntityFramework)
- authentication (Jwt)
- authorization (role based from Jwt)
- automatic swagger documentation
- testing
- input validation and payloads
- cors
- https
- error responses
Here is a list of things that have been included in the project. This is not meant as a tutorial but as a quick reference for understanding what has gone into creating the project. Hopefully this will be useful when trying to replicate these features.
This application builds on top of Visual Studio's Web API template.
For a deeper understanding of ASP.Net Core's dependency injection this explanation is very helpful.
Following Microsoft's pattern of extending the IServiceCollection with methods to register services the RegisterServices static class was created. In it we add all necessary dependencies and then we call it's AddMWTestServices method in the ConfigureServices method of Startup.
To add the configuration to any service (or to Startup) just add it as a dependency in the constructor and the dependency injection engine will handle the rest. It is a good idea where appropriate to use the Options pattern in configuration.
This enables the configuration to be mapped as objects and be injected in services that require it.
- Add dependencies
Npgsql.EntityFrameworkCore.PostgreSQLandNpgsql.EntityFrameworkCore.PostgreSQL.Design - Create the database
DbContextclass (MWTestDb.cs) with only the constructor - Add the
DBConnectionOptionskey in theappsettings.jsonfile - Add the
AddMWTestDbServicemethod in theRegisterServicesextension class - In the
ConfigureServicesmethod ofStartupadd the database service - npgsql documetation
- Create a class to represent a model (
User). Use annotations to specify constraints on the properties - Add a
DbSetof the model type to theDbContext(MWTestDb.cs) - Microsoft's documentation
These migrations are generated by a tool starting from the database model in the code. When generating a migration a snapshot is created along with the actual migration and it is used to build the next migration.
- Manually add the
Microsoft.EntityFrameworkCore.Tools.DotNetdependency to the*.csprojfile. - From the CLI run
dotnet ef migrations add migration_nameto generate the first migration (changemigration_nameto whatever you want to name the migration) - Microsoft's documentation
If you encounter a Build failed. message then you can run the command with the -v flag to see what is causing the problem. If a file cannot be accessed you might need stop the project (web app) if it is running.
To enable routing there are two methods. Configuring it on startup or using attributes in the controller. You can find an example of how the attributes are used in the UserController class. More details on routing can be found in Microsoft's documentation.
This part is heavily inspired (pronounced copy-pasted) from this tutorial.
- Add dependency for
Microsoft.AspNetCore.Authentication.JwtBearer - Add
JWTIssuerOptionskey inappsettings.json - Add class
JwtIssuerOptions - Add interface
IJwtFactoryand implementing classJwtFctory - Add class
JwtController - Add the
app.UseAuthentication();line in theConfiguremethod ofStartup - Add the
[Authorize]attribute on the controller or action that needs authorization
- Add the
Roleproperty toUser - Add the
"Role"claim inJwtFactory'sclaimsvariable - Add the authorization options to the
IServiceCollectioninRegisterServices - Add proper
[Authoriza( Policy = "PolicyName")]annotations in the controllers
The simplest way to validate input is to create an object that represents the input payload (like UserPostPayload) and set it as the parameter of the action (like UserController's Post action). The system will automatically try to populate the properties with values from the request.
If further validation is needed you can add attributes to the object's properties like [Required] or [EmailAddress]. Then you must check if the ModelState.IsValid property is true or false. You can do this in the controller but it is better to create a reusable Filter to accomplish this task:
- Add the
ValidateModelAttributeclass - Register it in the
AddMWTestServicesmethod ofRegisterServises - Add the
[ValidateModel]attribute to the action (like inUserController'sPostmethod)
- Add dependency for
Swashbuckle.AspNetCore - Call the
AddSwaggerGenmethod inStartup'sConfigureServicesmethod - Add the
UseSwaggerandUseSwaggerUImethods toStartup'sConfiguremethod