ITADN
Perigrino/Restaurants_WebAPIs_Using-_Clean_Architecture
Perigrino/Restaurants_WebAPIs_Using-_Clean_Architecture · 文件
文件最后提交记录最后更新时间
README.md

Restaurants Web API

A RESTful web API for exploring restaurants and their dishes. It provides structured data such as restaurant categories, delivery availability, contact details, addresses, and menu items — useful for food delivery platforms, restaurant directories, market research, and local dining guides.

Built with ASP.NET Core 10, Entity Framework Core (PostgreSQL), ASP.NET Core Identity with cookie authentication, and Serilog for logging.

Table of Contents

Features

  • CRUD operations for Restaurants and Dishes
  • Relational model: a User owns many Restaurants; a Restaurant has many Dishes
  • ASP.NET Core Identity with three roles: User, Owner, and Administrator
  • Automatic database migration and data seeding on startup
  • Data validation on all request bodies
  • Case-insensitive search, pagination, and sorting for restaurant listings
  • Policy-based authorization: nationality, minimum age, and restaurant-ownership requirements
  • Consistent JSON response envelope
  • Centralized exception handling returning proper HTTP status codes
  • Swagger/OpenAPI documentation
  • Structured console + rolling file logging with Serilog

Tech Stack

LayerTechnology
Runtime.NET 10
Web frameworkASP.NET Core (minimal hosting)
ORMEntity Framework Core 10 + Npgsql
DatabasePostgreSQL
AuthASP.NET Core Identity (cookie)
CQRSMediatR 14
MappingAutoMapper 16
ValidationFluentValidation 11
API docsSwashbuckle / Swagger
LoggingSerilog

Project Structure

Restaurants_WebAPIs_Using-_Clean_Architecture/
├── Restaurants.API/                    # ASP.NET Core web API (entry point)
│   ├── Controller/                     # Restaurant, Dish, Identity controllers
│   ├── Extensions/                     # AddPresentation (Swagger, MVC, Serilog)
│   ├── Middlewares/                    # ErrorHandling, RequestTimeLogging
│   ├── Program.cs                      # App composition & pipeline
│   └── appsettings.json
├── Restaurants.Application/            # Use cases, DTOs, validators
│   ├── Common/                         # PageResults, FinalResponse
│   ├── Dishes/                         # Commands, queries, DTOs, validators
│   ├── Restaurants/                    # Commands, queries, DTOs, validators
│   ├── Users/                          # CurrentUser, UserContext
│   └── Extensions/                     # AddApplication (MediatR, AutoMapper, FluentValidation)
├── Restaurants.Domain/                 # Core entities, interfaces, constants
│   ├── Constants/                      # UserRoles, ResourceOperation, SortDirection
│   ├── Entities/                       # Restaurant, Dish, User, Address
│   ├── Exceptions/                     # NotFoundException, ForbiddenException
│   └── IRepository/                    # Repository + authorization interfaces
├── Restaurants.Infrastructure/         # Persistence, repositories, seeder, auth
│   ├── Authorisation/                  # Policies, requirements, claims factory
│   ├── Migrations/                     # EF Core migrations
│   ├── Persistence/                    # RestaurantDbContext + design-time factory
│   ├── Repositories/                   # Restaurant, Dish repositories
│   ├── Seeder/                         # Database seeder
│   └── Extensions/                     # AddInfrastructureService (EF, Identity, policies)
├── Restaurants.Tests/                  # xUnit tests (validators, mappings, auth, repos)
└── Restaurants.sln

Getting Started

Prerequisites

  • .NET 10 SDK
  • PostgreSQL (local or remote instance)

Database Setup

The API ships with an EF Core migration (InitialCreate) and applies it automatically on startup. To apply it manually:

dotnet ef database update --project Restaurants.Infrastructure --startup-project Restaurants.API

On startup the seeder also runs: if the tables are empty it inserts the User, Owner, and Administrator roles plus two sample restaurants (KFC and McDonald's) with dishes.

Configuration

The connection string is not stored in appsettings.json. Set it via user-secrets (or an environment variable):

dotnet user-secrets set "ConnectionStrings:Default" "User ID=postgres;Password=123;Host=localhost;Port=5432;Database=Restaurants_Db;Pooling=true;" --project Restaurants.API

Logging (Serilog) is configured in appsettings.json:

{
  "Serilog": {
    "MinimumLevel": {
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.EntityFrameworkCore": "Information"
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": { "outputTemplate": "[{Timestamp:dd-MM HH:mm:ss} {Level:u3}] |{SourceContext}| {NewLine}{Message:lj}{NewLine}{Exception}" }
      },
      {
        "Name": "File",
        "Args": {
          "path": "Logs/Restaurant-Api-.log",
          "rollingInterval": "Day",
          "rollOnFileSizeLimit": true,
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      }
    ]
  }
}

Running the API

dotnet run --project Restaurants.API

The API listens on:

Authentication

The API uses ASP.NET Core Identity with cookie-based authentication, exposed through the standard Identity API endpoints at /api/identity.

  1. Register a user
POST /api/identity/register
Content-Type: application/json

{
  "email": "jane@example.com",
  "password": "password123"
}
  1. Log in
POST /api/identity/login
Content-Type: application/json

{
  "email": "jane@example.com",
  "password": "password123"
}

Login sets an authentication cookie (and returns an access token in the response body). Include the cookie in subsequent requests:

Cookie: .AspNetCore.Identity.Application=<cookie-value>

Roles

Three roles are seeded automatically on startup:

RoleDescription
UserDefault role; can access authenticated endpoints
OwnerCan create restaurants and update/delete their own
AdministratorCan assign/remove roles and delete any restaurant

Policies

PolicyRequirement
HasNationalityUser holds a Nationality claim of Ghanaian or Brazilian
AtLeast20User is at least 20 years old
CreatedAtLeast2RestaurantsUser has created 2 or more restaurants

API Reference

All responses use the standard envelope (see Response Envelope). A Location header is returned on creation.

Identity

The standard ASP.NET Core Identity endpoints are exposed at /api/identity:

MethodEndpointDescription
POST/api/identity/registerRegister a new user
POST/api/identity/loginLog in (sets cookie)
POST/api/identity/refreshRefresh authentication
POST/api/identity/logoutLog out
POST/api/identity/manage/infoManage account info
POST/api/identity/manage/2faManage two-factor auth

Restaurants

MethodEndpointAuthDescription
GET/api/RestaurantAnonymousList restaurants
GET/api/Restaurant/{id}Policy: HasNationalityGet a restaurant
POST/api/RestaurantRole: OwnerCreate a restaurant
PUT/api/Restaurant/{id}Owner or AdministratorUpdate a restaurant
DELETE/api/Restaurant/{id}Owner or AdministratorDelete a restaurant

Create / Update restaurant body:

{
  "name": "KFC",
  "description": "American fast food chain specializing in fried chicken.",
  "category": "Fast Food",
  "hasDelivery": true,
  "contactEmail": "contact@kfc.com",
  "contactNumber": "+123456789",
  "city": "London",
  "street": "Cork St 5",
  "postalCode": "12-345"
}

Query parameters for GET /api/Restaurant:

ParameterTypeDescription
searchPhrasestring?Case-insensitive match on name or description
pageNumberint1-based page number (default 1)
pageSizeintOne of: 5, 10, 15, 20, 25, 30, 35, 40, 45
sortBystring?Name, Category, or Description
sortDirectionint0 = Ascending (default), 1 = Descending

Get all restaurants response:

{
  "statusCode": 200,
  "message": "All restaurants have been successfully retrieved",
  "data": {
    "items": [
      {
        "id": "cdd15c8a-6cf2-43ea-bd02-285144cfc93d",
        "name": "KFC",
        "description": "American fast food chain specializing in fried chicken.",
        "category": "Fast Food",
        "hasDelivery": true,
        "city": "London",
        "street": "Cork St 5",
        "postalCode": "12-345",
        "dishes": []
      }
    ],
    "totalPages": 1,
    "totalItemsCount": 1,
    "itemsFrom": 1,
    "itemsTo": 10
  }
}

Dishes

All dish endpoints are scoped under a restaurant: /api/restaurants/{restaurantId}/Dish.

MethodEndpointAuthDescription
GET/api/restaurants/{restaurantId}/DishAuthList dishes for a restaurant
GET/api/restaurants/{restaurantId}/Dish/{dishId}AuthGet a single dish
POST/api/restaurants/{restaurantId}/DishAuthCreate a dish
PUT/api/restaurants/{restaurantId}/Dish/{dishId}AuthUpdate a dish
DELETE/api/restaurants/{restaurantId}/Dish/{dishId}AuthDelete a single dish
DELETE/api/restaurants/{restaurantId}/DishAuthDelete all dishes for a restaurant

{restaurantId} must match the dish's owning restaurant — create, update, and delete operations validate this.

Create / Update dish body:

{
  "name": "Nashville Hot Chicken",
  "description": "Nashville Hot Chicken (10 pcs.)",
  "price": 10.30,
  "kiloCalories": 850
}

Users

MethodEndpointAuthDescription
PUT/api/Identity/userAuthenticated userUpdate nationality / date of birth
POST/api/Identity/userRoleRole: AdministratorAssign a role to a user
DELETE/api/IdentityRole: AdministratorRemove a role from a user

Update user details body:

{
  "dateOfBirth": "1995-05-10",
  "nationality": "Ghanaian"
}

Assign / remove role body:

{
  "userEmail": "jane@example.com",
  "roleName": "Owner"
}

Response Envelope

Every endpoint returns a consistent envelope:

{
  "statusCode": 200,
  "message": "Resturants retrieved successfully.",
  "data": { }
}
FieldDescription
statusCodeHTTP status code
messageHuman-readable result message
dataPayload (object, array, or null)

Common status codes

CodeMeaning
200OK
201Created (with Location header)
204No Content (update / delete)
400Invalid request or validation failure
401Missing or invalid authentication
403Authenticated but not authorized for the role
404Resource not found
500Unexpected server error

Error Handling

  • Validation failures are handled automatically by [ApiController] and return 400 with field-level errors.
  • Unhandled exceptions are caught by a central ErrorHandlingMiddleware and returned as RFC 7807 ProblemDetails:
ExceptionHTTP StatusTitle
NotFoundException404Not Found
ForbiddenException403Forbidden
any other Exception500Internal Server Error

Each response includes the request's TraceIdentifier, which is also written to the logs (with the exception and stack trace) for correlation. Exception details are never exposed to the client.

Migrations

Migrations live in Restaurants.Infrastructure/Migrations. To add a new migration:

dotnet ef migrations add <MigrationName> --project Restaurants.Infrastructure --startup-project Restaurants.API

License

MIT License