Harri.SchoolDemoAPI is a demo REST API built with ASP.NET Core 8.0 that manages students, schools, and applications. It showcases modern .NET backend development practices, including comprehensive automated testing, containerization, structured logging, and CI/CD integration with Azure DevOps.
Also see a front-end Blazor WASM SPA developed for this API here: Blazor Admin UI
An emphasis on comprehensive automated testing has been used when developing this demo API.
Included are Unit, Contract, Integration, and E2E test projects. See the full Test README here
All test projects are run as part of the Azure DevOps pipeline as part of the Build stage (for Unit and Contract) or the 'Deploy & Test' stage (For Integration and E2E), and are run in-agent.
So far the /students/ API is complete: StudentsApiController.cs
- JSON API Request/Response examples
- Running the SchoolDemo REST Web API
- Build pipeline
- Logging using Application Insights & Serilog
- Nuget packages used
flowchart LR
Client[C# Client]
SQL[(SQL)]
subgraph API[REST API]
direction LR
Controller---Service---Repository
end
Client-- Network ---Controller
Repository-- Network ---SQL
Operation | Resource |
---|---|
POST | /students |
GET | /students/{sId} |
PUT | /students/{sId} |
PATCH | /students/{sId} |
DELETE | /students/{sId} |
GET | /students |
Request:
POST /students
Request Body:
{
"name": "Student Name"
}
or with optional GPA:
{
"name": "Student Name",
"GPA": 3.52
}
Response
containing created student ID:
HTTP/1.1 200 OK
Content-Type: application/json
1348
Request:
GET /students/1348
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"sId": 1348,
"name": "Student Name",
"GPA": 3.52
}
(Update entire student record)
Request:
PUT /students/1348
Request Body:
{
"name": "Student Name Updated",
"GPA": 3.65
}
Response
HTTP/1.1 200 OK
(Update partial student record)
Request:
PATCH /students/1348
Request Body:
{
"name": "Student Name Patched"
}
Response
containing modified student record
HTTP/1.1 200 OK
Content-Type: application/json
{
"sId": 1348,
"name": "Student Name Patched",
"GPA": 3.65
}
Request:
DELETE /students/1348
Response
HTTP/1.1 200 OK
Request:
GET /students?sId=1
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"items": [
{
"sId": 1,
"name": "Olivia Jackson",
"GPA": 1.43
},
{
"sId": 10,
"name": "Harper Jones",
"GPA": 3.94
},
{
"sId": 11,
"name": "Noah Lopez",
"GPA": 1.78
},
{
"sId": 12,
"name": "Liam Taylor",
"GPA": 3.65
},
{
"sId": 13,
"name": "Emma Davis",
"GPA": 1.72
},
{
"sId": 14,
"name": "Amelia Johnson",
"GPA": 3.62
},
{
"sId": 15,
"name": "Harper Taylor",
"GPA": 1.24
},
{
"sId": 16,
"name": "Lucas Thomas",
"GPA": 3.42
},
{
"sId": 17,
"name": "Ava Rodriguez",
"GPA": 3.15
},
{
"sId": 18,
"name": "Benjamin Lopez",
"GPA": 3.69
}
],
"page": 1,
"pageSize": 10,
"totalCount": 328,
"totalPageCount": 33,
"hasNextPage": true,
"hasPreviousPage": false
}
Request:
GET /students?name=Ethan%20S&page=1&pageSize=3
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"items": [
{
"sId": 124,
"name": "Ethan Smith",
"GPA": 2.84
},
{
"sId": 137,
"name": "Ethan Smith",
"GPA": 3.12
},
{
"sId": 956,
"name": "Ethan Smith",
"GPA": 1.35
}
],
"page": 1,
"pageSize": 3,
"totalCount": 3,
"totalPageCount": 1,
"hasNextPage": false,
"hasPreviousPage": false
}
Request:
GET /students?GPA.Gt=2&orderBy=ASC&sortColumn=GPA&page=1&pageSize=4
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"items": [
{
"sId": 324,
"name": "Harper Johnson",
"GPA": 2.01
},
{
"sId": 874,
"name": "Aria Jackson",
"GPA": 2.03
},
{
"sId": 975,
"name": "Charlotte Jackson",
"GPA": 2.03
},
{
"sId": 853,
"name": "Charlotte Thomas",
"GPA": 2.04
}
],
"page": 1,
"pageSize": 4,
"totalCount": 694,
"totalPageCount": 174,
"hasNextPage": true,
"hasPreviousPage": false
}
You have three options for running this web API,
-
Build it from source and run with dotnet sdk. See Building from source below
-
Build a docker container and run. See Building container from source below
-
Pull and run a docker container. See Running from container below
For all options above you will also need to pull and run the database container
Use the included build scripts in the root of the repo or build in Visual Studio
./build.bat
build
./build.sh
The build script then prompts you to run using
dotnet run --project src\Harri.SchoolDemoAPI\Harri.SchoolDemoAPI.csproj
The API will be accessible via http://localhost:8080 by default
Also make sure to set up the database or the API will return 500 Internal server error Running the database from container
Build the container locally run:
docker build -t schooldemoapi -f .\src\Harri.SchoolDemoAPI\Dockerfile .
If you built the container yourself locally as above run:
docker run -it -p 8080:8080 --name schooldemoapi schooldemoapi
If you don't want to build the container you can pull the latest main branch linux container image from the DockerHub harri-schooldemoapi repository
docker pull harrisonslater/harri-schooldemoapi:latest
And run
docker run -it -p 8080:8080 --name schooldemoapi harrisonslater/harri-schooldemoapi:latest
The API will be accessible via http://localhost:8080 or you can specify a different port in the docker run command above like 5000:8080
The database required by the SchoolDemoAPI is available as a linux container image prefilled with student, school, and application data from the DockerHub harri-schooldemosql-database repository
docker pull harrisonslater/harri-schooldemosql-database:latest
And to run the database container:
docker run -e "MSSQL_SA_PASSWORD=p@ssw0rd" -p 1433:1433 -d harrisonslater/harri-schooldemosql-database:latest
Azure DevOps pipeline defined in yaml
A successful pipeline run based on main looks like:
In a real world pipeline Deploy & Test would be separate stages where Deploy actually deploys to an environment. In this pipeline 'Deploy' just runs the container image / .NET dll in-agent. This is done to remove ongoing hosting costs
Logging accessible via standard ILogger interface
.UseHttpLogging(); is used for request and response body logging
Serilog is fully configured in the appsettings.json
Other than console and debug logs this also logs to:
- Log file: /Logs/log.txt
- Application Insights: set up for local usage within visual studio
- Custom events are logged via Serilog
- SEQ: free personal edition that can be run in a container
For App insights and SEQ:
- Requests and responses with body are logged as custom events
In a production scenario you probably don't want to do this as sensitive data may be logged
Response implemented with AspNetCore.HealthChecks.UI.Client/UIResponseWriter.cs to return a result like:
{
"status": "Healthy",
"totalDuration": "00:00:00.0858619",
"entries": {
"sql": {
"data": {},
"duration": "00:00:00.0856835",
"status": "Healthy",
"tags": []
}
}
}
Also included is a RestSharp client in a separate project Harri.SchoolDemoAPI.HealthCheckClient along with consumer driven contract tests
- Dapper
- RestSharp for the client
- AspNetCore.Diagnostics.HealthChecks