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.PostgreSQL
andNpgsql.EntityFrameworkCore.PostgreSQL.Design
- Create the database
DbContext
class (MWTestDb.cs
) with only the constructor - Add the
DBConnectionOptions
key in theappsettings.json
file - Add the
AddMWTestDbService
method in theRegisterServices
extension class - In the
ConfigureServices
method ofStartup
add the database service - npgsql documetation
- Create a class to represent a model (
User
). Use annotations to specify constraints on the properties - Add a
DbSet
of 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.DotNet
dependency to the*.csproj
file. - From the CLI run
dotnet ef migrations add migration_name
to generate the first migration (changemigration_name
to 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
JWTIssuerOptions
key inappsettings.json
- Add class
JwtIssuerOptions
- Add interface
IJwtFactory
and implementing classJwtFctory
- Add class
JwtController
- Add the
app.UseAuthentication();
line in theConfigure
method ofStartup
- Add the
[Authorize]
attribute on the controller or action that needs authorization
- Add the
Role
property toUser
- Add the
"Role"
claim inJwtFactory
'sclaims
variable - Add the authorization options to the
IServiceCollection
inRegisterServices
- 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
ValidateModelAttribute
class - Register it in the
AddMWTestServices
method ofRegisterServises
- Add the
[ValidateModel]
attribute to the action (like inUserController
'sPost
method)
- Add dependency for
Swashbuckle.AspNetCore
- Call the
AddSwaggerGen
method inStartup
'sConfigureServices
method - Add the
UseSwagger
andUseSwaggerUI
methods toStartup
'sConfigure
method