ASP.NET Core Web API

Interface Implementation Purpose
IAuthService AuthService Authentication-related application operations.
IVehicleBookingService VehicleBookingService Vehicle-booking operations.
IVehicleBookingRepository VehicleBookingRepository Data access for vehicle bookings.
IVehicleRepository VehicleRepository Data access for vehicles.
IBookingStatusHistoryRepository BookingStatusHistoryRepository Data access for booking status history.

The exact responsibilities depend on the methods defined in each interface and implementation.

What does AddScoped() mean?

A scoped service is normally created once per dependency injection scope. In a typical ASP.NET Core Web API request, this means one instance per request scope.

This is commonly used for application services and repositories that depend on a scoped AppDbContext.

Why use interfaces?

Interfaces separate what a service does from how it does it.

For example:

  • The application layer depends on IVehicleRepository.
  • Infrastructure provides VehicleRepository.
  • You can replace the implementation without rewriting every consumer.

This supports maintainability and the separation of concerns used in layered architectures.

using CompanyVehicleBooking.Application.Behaviour; using CompanyVehicleBooking.Application.Features.Auth.Command.Register; using CompanyVehicleBooking.Application.Options; using CompanyVehicleBooking.Application.Services; using CompanyVehicleBooking.Application.ServicesContracts; using CompanyVehicleBooking.Domain.Entities; using CompanyVehicleBooking.Domain.RepositoryContracts; using CompanyVehicleBooking.Infrastructure.Context; using CompanyVehicleBooking.Infrastructure.Repository; using CompanyVehicleBooking.Infrastructure.Seed; using CompanyVehicleBookingManagementSystem.API.Middleware; using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi; using System.Security.Claims; using System.Text; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "JWR Authorization header using the bearer scheme" }); options.AddSecurityRequirement(document => new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference("bearer", document)] = [] }); }); builder.Services.AddExceptionHandler<AppExceptionHandler>(); builder.Services.AddProblemDetails(); builder.Services.AddTransient<ICurrentUser,CurrentUser>(); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped<IAuthService, AuthService>(); builder.Services.AddScoped<IVehicleBookingService, VehicleBookingService>(); builder.Services.AddScoped<IVehicleBookingRepository,VehicleBookingRepository>(); builder.Services.AddScoped<IVehicleRepository,VehicleRepository>(); builder.Services.AddScoped<IBookingStatusHistoryRepository, BookingStatusHistoryRepository>(); builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("JWT")); builder.Services.AddScoped<ITokenService, TokenService>(); builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(RegisterCommandHandler).Assembly); cfg.AddOpenBehavior(typeof(ValidationBehavior<,>)); }); builder.Services.AddValidatorsFromAssembly(typeof(RegisterCommandValidator).Assembly); builder.Services.AddControllers(options => { options.Filters.Add(new ProducesAttribute("application/json")); options.Filters.Add(new ConsumesAttribute("application/json")); var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }); builder.Services.AddIdentity<ApplicationUser,IdentityRole<Guid>>() .AddRoles<IdentityRole<Guid>>() .AddEntityFrameworkStores<AppDbContext>() .AddDefaultTokenProviders(); // Authentication builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; } ) .AddJwtBearer(options => { options.SaveToken = true; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidAudience = builder.Configuration["JWT:ValidAudience"], ValidIssuer = builder.Configuration["JWT:ValidIssuer"], ClockSkew = TimeSpan.Zero, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Secret"])) }; options.Events = new JwtBearerEvents { OnTokenValidated = async context => { var userManager = context.HttpContext.RequestServices .GetRequiredService<UserManager<ApplicationUser>>(); var userId = context.Principal? .FindFirst(ClaimTypes.NameIdentifier)?.Value; var tokenVersion = context.Principal? .FindFirst("TokenVersion")?.Value; if (userId == null || tokenVersion == null) { context.Fail("Invalid token."); return; } var user = await userManager.FindByIdAsync(userId); if (user == null || user.TokenVersion.ToString() != tokenVersion) { context.Fail("Token has been revoked."); } }, OnChallenge = async context => { context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json"; await context.Response.WriteAsJsonAsync(new { StatusCode = 401, Message = "you are not authorized please login with valid token" }); } }; } ); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/Swagger/v1/swagger.json", "Company Vehicle Booking Management System"); c.RoutePrefix = string.Empty; c.DocumentTitle = "Company Vehicle Booking Management System"; }); app.UseExceptionHandler(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); await DbSeeder.SeedData(app); app.Run(); 

2. What Is the Purpose of Program.cs?

In an ASP.NET Core Web API, Program.cs is the application's entry point and configuration file.

It tells ASP.NET Core:

  • Which database to use.
  • Which services and repositories to register.
  • How to validate incoming requests.
  • How to authenticate users using JWT.
  • How to authorize access to controllers.
  • How to display API documentation through Swagger.
  • How to handle exceptions.
  • How to initialize database seed data.
  • How to start accepting HTTP requests.

Your project uses a layered architecture:

  • API layer: Receives HTTP requests and configures the application.
  • Application layer: Contains business operations, services, CQRS handlers, and validation.
  • Domain layer: Contains core entities and repository contracts.
  • Infrastructure layer: Contains database access, repository implementations, and seeding.

The purpose of Program.cs is to connect these pieces and configure the application's execution.

3. Understanding the using Statements

The using statements import namespaces so you can use classes without writing their complete namespace paths every time.

Namespace Purpose
CompanyVehicleBooking.Application.Behaviour Contains the validation pipeline behavior.
CompanyVehicleBooking.Application.Features.Auth.Command.Register Contains registration commands, handlers, and validators.
CompanyVehicleBooking.Application.Options Contains configuration classes such as JwtOptions.
CompanyVehicleBooking.Application.Services Contains application service implementations.
CompanyVehicleBooking.Application.ServicesContracts Contains service interfaces and contracts.
CompanyVehicleBooking.Domain.Entities Contains domain entities such as ApplicationUser.
CompanyVehicleBooking.Domain.RepositoryContracts Contains repository interfaces.
CompanyVehicleBooking.Infrastructure.Context Contains AppDbContext.
CompanyVehicleBooking.Infrastructure.Repository Contains repository implementations.
CompanyVehicleBooking.Infrastructure.Seed Contains database seed-data logic.
CompanyVehicleBookingManagementSystem.API.Middleware Contains the custom exception handler.
FluentValidation Provides validation functionality.
Microsoft.AspNetCore.Authentication.JwtBearer Provides JWT bearer authentication.
Microsoft.AspNetCore.Authorization Provides authorization policies.
Microsoft.AspNetCore.Identity Provides user and role management.
Microsoft.AspNetCore.Mvc Provides controller and API-related types.
Microsoft.AspNetCore.Mvc.Authorization Provides MVC authorization filters.
Microsoft.EntityFrameworkCore Provides Entity Framework Core functionality.
Microsoft.IdentityModel.Tokens Provides token validation and cryptographic types.
Microsoft.OpenApi Provides OpenAPI documentation types.
System.Security.Claims Provides claims used to represent user identity information.
System.Text Provides text encoding functionality, including UTF-8 encoding.

4. Creating the Application Builder

var builder = WebApplication.CreateBuilder(args); 

What does it do?

This creates a WebApplicationBuilder that prepares the application for configuration.

It provides access to:

  • builder.Services: Register services in the dependency injection container.
  • builder.Configuration: Read settings from configuration sources.
  • builder.Environment: Access environment information, such as Development or Production.
  • builder.Logging: Configure logging.

What is args?

args contains command-line arguments passed to the application.

Why do we use it?

Before the application starts handling requests, we need to register the services and configure how the application behaves.

For example, your code uses:

builder.Services.AddDbContext<AppDbContext>(...); 

to register the database context, and:

builder.Configuration.GetConnectionString("DefaultConnection") 

to retrieve the database connection string.

In simple words: The builder is where you prepare and configure your application before building it.

5. Configuring Entity Framework Core and SQL Server

builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("DefaultConnection"))); 

This registers your Entity Framework Core database context with dependency injection.

A. AddDbContext<AppDbContext>()

builder.Services.AddDbContext<AppDbContext>(...) 

AppDbContext represents the application's session with the database.

It typically contains:

  • DbSet properties representing database tables.
  • Entity configuration.
  • Database relationship configuration.
  • Database operations performed through Entity Framework Core.

For example, your application might have entities representing users, vehicles, bookings, and booking status history.

B. options.UseSqlServer()

options.UseSqlServer(...) 

This configures Entity Framework Core to use SQL Server as the database provider.

Other providers exist, but this application uses SQL Server.

C. GetConnectionString("DefaultConnection")

builder.Configuration.GetConnectionString("DefaultConnection") 

This retrieves the connection string named DefaultConnection from the application's configuration.

For example, your appsettings.json might contain:

{ "ConnectionStrings": { "DefaultConnection": "YOUR_SQL_SERVER_CONNECTION_STRING" } } 

The actual connection string should be configured for your environment rather than hard-coded into Program.cs.

Why do we need this?

When a controller, handler, or repository needs to access the database, dependency injection can provide an AppDbContext configured for SQL Server.

Execution flow:

  1. The application reads the connection string.
  2. Entity Framework Core is configured to use SQL Server.
  3. AppDbContext is registered with dependency injection.
  4. Components that request the context can use it for database operations.

6. Configuring Swagger and JWT Authorization

builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "JWR Authorization header using the bearer scheme" }); options.AddSecurityRequirement(document => new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference("bearer", document)] = [] }); }); 

What is Swagger?

Swagger UI provides a browser-based interface for exploring and testing your API endpoints.

For example, you might use it to test:

  • Registration.
  • Login.
  • Vehicle management.
  • Booking creation.
  • Booking history.

A. AddSwaggerGen()

builder.Services.AddSwaggerGen(...) 

This registers the service that generates the OpenAPI description of your API.

B. AddSecurityDefinition()

options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "JWR Authorization header using the bearer scheme" }); 

This defines the bearer authentication scheme for Swagger.

  • Type = SecuritySchemeType.Http: Uses HTTP authentication.
  • Scheme = "bearer": Uses the Bearer authorization scheme.
  • BearerFormat = "JWT": Documents that the bearer token is a JWT.
  • Description: Provides explanatory text in the Swagger interface.

The description currently says JWR; it would be clearer to correct this typo to JWT Authorization header using the bearer scheme.

C. AddSecurityRequirement()

options.AddSecurityRequirement(document => new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference("bearer", document)] = [] }); 

This adds a security requirement referencing the defined bearer scheme.

It tells Swagger that the API documentation uses bearer authentication requirements, so Swagger UI can offer an authorization mechanism for supplying a JWT.

How does it help?

After obtaining a valid access token from your login endpoint, you can use Swagger's Authorize control to provide the token when testing protected endpoints.

A typical header looks like:

Authorization: Bearer YOUR_ACCESS_TOKEN 

Important: Swagger configuration only documents and enables token entry in Swagger UI. Actual authentication is performed by the application's authentication middleware, not by Swagger itself.

7. Configuring Exception Handling

builder.Services.AddExceptionHandler<AppExceptionHandler>(); builder.Services.AddProblemDetails(); 

These statements configure centralized exception handling.

A. AddExceptionHandler<AppExceptionHandler>()

This registers your custom exception handler.

Your AppExceptionHandler class can define how unhandled exceptions are converted into HTTP responses.

For example, it may handle an application-specific exception and return a meaningful response rather than exposing internal implementation details.

B. AddProblemDetails()

This registers services for producing standardized HTTP problem-details responses.

Problem details are a standard format for communicating API errors, often containing properties such as:

  • status
  • title
  • detail

The actual output depends on your exception handler and response configuration.

Why do we need centralized exception handling?

Without a centralized strategy, different endpoints may return inconsistent error responses.

A common handler can keep error handling more consistent and avoid exposing sensitive exception details to clients.

Later in the pipeline, your code calls:

app.UseExceptionHandler(); 

That activates the configured exception-handling middleware.

8. Registering the Current User Service

builder.Services.AddTransient<ICurrentUser,CurrentUser>(); builder.Services.AddHttpContextAccessor(); 

These statements support accessing information about the current HTTP request and its authenticated user.

A. ICurrentUser and CurrentUser

builder.Services.AddTransient<ICurrentUser,CurrentUser>(); 

This tells dependency injection:

When a class requests ICurrentUser, provide an instance of CurrentUser.

ICurrentUser is the abstraction; CurrentUser is its implementation.

Depending on your implementation, the service may expose details such as the current user's ID or claims.

What does AddTransient() mean?

A transient service is created each time the service is requested from the dependency injection container.

This lifetime is appropriate for lightweight services that do not need to maintain shared state across requests.

B. AddHttpContextAccessor()

builder.Services.AddHttpContextAccessor(); 

This registers IHttpContextAccessor, which can provide access to the current HttpContext where available.

The HttpContext represents the current HTTP request and response and can provide access to:

  • Request headers.
  • Response status.
  • Request services.
  • The authenticated user's ClaimsPrincipal.

This is useful if CurrentUser obtains user information from the current HTTP context.

Important: HttpContext is request-specific. Do not store it or request-specific user information in a long-lived object for use across unrelated requests.

9. Registering Application Services and Repositories

builder.Services.AddScoped<IAuthService, AuthService>(); builder.Services.AddScoped<IVehicleBookingService, VehicleBookingService>(); builder.Services.AddScoped<IVehicleBookingRepository,VehicleBookingRepository>(); builder.Services.AddScoped<IVehicleRepository,VehicleRepository>(); builder.Services.AddScoped<IBookingStatusHistoryRepository, BookingStatusHistoryRepository>(); 

These statements register your application's services and repository implementations.

What is dependency injection?

Dependency injection allows a class to receive the objects it needs rather than creating those objects itself.

For example, a controller might request:

public class VehicleController(IVehicleRepository vehicleRepository) { // Use vehicleRepository here. } 

The dependency injection container knows which implementation to provide because of your registration:

builder.Services.AddScoped<IVehicleRepository,VehicleRepository>(); 

Understanding the registrations

Interface Implementation Purpose
IAuthService AuthService Authentication-related application operations.
IVehicleBookingService VehicleBookingService Vehicle-booking operations.
IVehicleBookingRepository VehicleBookingRepository Data access for vehicle bookings.
IVehicleRepository VehicleRepository Data access for vehicles.
IBookingStatusHistoryRepository BookingStatusHistoryRepository Data access for booking status history.

The exact responsibilities depend on the methods defined in each interface and implementation.

What does AddScoped() mean?

A scoped service is normally created once per dependency injection scope. In a typical ASP.NET Core Web API request, this means one instance per request scope.

This is commonly used for application services and repositories that depend on a scoped AppDbContext.

Why use interfaces?

Interfaces separate what a service does from how it does it.

For example:

  • The application layer depends on IVehicleRepository.
  • Infrastructure provides VehicleRepository.
  • You can replace the implementation without rewriting every consumer.

This supports maintainability and the separation of concerns used in layered architectures.

10. Configuring JWT Options

builder<...
Source:https://dev.to/alex_455773380f24c0073a3/aspnet-core-2agd