DEV Community

AlexfamDan
AlexfamDan

Posted on Edited on

ASP.NET CORE

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.

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

// Add the using statements from your own project here.
// Example: your DbContext, ApplicationUser, exception handler,
// JWT options, and any application-specific services.

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);

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

// 2. Swagger configuration
builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT",
        Description = "JWT Authorization header using the bearer scheme"
    });

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

// 3. Exception handling
builder.Services.AddExceptionHandler<AppExceptionHandler>();
builder.Services.AddProblemDetails();

// 4. HTTP context access
builder.Services.AddHttpContextAccessor();

// 5. JWT configuration
// Register your JwtOptions and token service here if your project uses them.

// 6. MediatR and FluentValidation
// Register your application's handlers, pipeline behaviors,
// and validators here if your project uses them.

// 7. Controllers and global authorization
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));
});

// 8. ASP.NET Core Identity
builder.Services
    .AddIdentity<ApplicationUser, IdentityRole<Guid>>()
    .AddEntityFrameworkStores<AppDbContext>()
    .AddDefaultTokenProviders();

// 9. JWT authentication
builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme =
            JwtBearerDefaults.AuthenticationScheme;

        options.DefaultChallengeScheme =
            JwtBearerDefaults.AuthenticationScheme;

        options.DefaultScheme =
            JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.SaveToken = true;

        options.TokenValidationParameters =
            new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,

                ValidateIssuerSigningKey = true,

                ValidAudience =
                    builder.Configuration["JWT:ValidAudience"],

                ValidIssuer =
                    builder.Configuration["JWT:ValidIssuer"],

                ClockSkew = TimeSpan.Zero,

                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(
                        builder.Configuration["JWT:Secret"]
                        ?? throw new InvalidOperationException(
                            "JWT secret is missing.")))
            };

        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 = "Please log in with a valid token."
                });
            }
        };
    });

// 10. Build the application
var app = builder.Build();

// 11. Middleware pipeline
app.UseExceptionHandler();

app.UseSwagger();

app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint(
        "/swagger/v1/swagger.json",
        "Web API");

    options.RoutePrefix = string.Empty;
    options.DocumentTitle = "Web API";
});

app.UseAuthentication();
app.UseAuthorization();

// 12. Map controller endpoints
app.MapControllers();

// Add your own database seeding method here if required.

// 13. Start the application
app.Run();
Enter fullscreen mode Exit fullscreen mode

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

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>(...);
Enter fullscreen mode Exit fullscreen mode

to register the database context, and:

builder.Configuration.GetConnectionString("DefaultConnection")
Enter fullscreen mode Exit fullscreen mode

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")));
Enter fullscreen mode Exit fullscreen mode

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

A. AddDbContext<AppDbContext>()

builder.Services.AddDbContext<AppDbContext>(...)
Enter fullscreen mode Exit fullscreen mode

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.

B. options.UseSqlServer()

options.UseSqlServer(...)
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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)] = []
    });
});
Enter fullscreen mode Exit fullscreen mode

What is Swagger?

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

A. AddSwaggerGen()

builder.Services.AddSwaggerGen(...)
Enter fullscreen mode Exit fullscreen mode

B. AddHttpContextAccessor()

builder.Services.AddHttpContextAccessor();
Enter fullscreen mode Exit fullscreen mode

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.


For example:

Registration command
        |
        v
MediatR pipeline
        |
        v
ValidationBehavior
        |
        +---- Invalid ----> Validation exception
        |
        +---- Valid ------> RegisterCommandHandler


---

## 13. Registering FluentValidation Validators

Enter fullscreen mode Exit fullscreen mode


csharp
builder.Services.AddValidatorsFromAssembly(
typeof(RegisterCommandValidator).Assembly);


This tells FluentValidation to discover and register validators in the assembly containing `RegisterCommandValidator`.

A validator might check that:

- An email address is present and valid.
- A password meets requirements.
- A required field is not empty.

For example, a registration validator might contain rules similar to:

Enter fullscreen mode Exit fullscreen mode


csharp
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress();


The example is illustrative; your actual rules depend on your validator.

### Why register validators?

Your `ValidationBehavior<,>` needs to obtain the relevant validators from dependency injection.

The typical flow is:

1. A command is sent through MediatR.
2. The validation behavior finds validators for that command.
3. The validators evaluate the command.
4. If validation succeeds, the handler executes.
5. If validation fails, the behavior reports the errors according to its implementation.

Registering validators alone does not automatically execute them. The pipeline behavior is what connects validation to MediatR request processing.

---

## 14. Configuring Controllers and Global MVC Filters

Enter fullscreen mode Exit fullscreen mode


csharp
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));
Enter fullscreen mode Exit fullscreen mode

});


This registers controller support and configures global MVC filters.

### A. `AddControllers()`

Enter fullscreen mode Exit fullscreen mode


csharp
builder.Services.AddControllers(...)


This enables controller-based API endpoints.

For example, a controller might define:

Enter fullscreen mode Exit fullscreen mode


markdown

The controller can receive HTTP requests and call application services or MediatR handlers.

B. ProducesAttribute

options.Filters.Add(
    new ProducesAttribute("application/json"));
Enter fullscreen mode Exit fullscreen mode

This declares that controller actions produce JSON responses by default.

It helps communicate the expected response format to clients and API documentation tools.


This creates an authorization policy that requires the user to be authenticated.

### E. Adding the authorization filter

Enter fullscreen mode Exit fullscreen mode


csharp
options.Filters.Add(new AuthorizeFilter(policy));


This applies the policy globally to MVC controller actions.

**What does this mean in practice?**

Controller actions generally require an authenticated user unless you explicitly allow anonymous access.

For example, registration and login endpoints usually need to be accessible before the user has logged in.

You can mark such actions with:

Enter fullscreen mode Exit fullscreen mode


csharp
[AllowAnonymous]


This is important because otherwise the global authorization filter can prevent an unauthenticated user from reaching those endpoints.

### Authentication versus authorization

- **Authentication:** Determines who the user is.
- **Authorization:** Determines whether the user is permitted to perform an operation.

Your global policy requires authentication, but it does not by itself restrict actions to particular roles.

---

## 15. Configuring ASP.NET Core Identity

Enter fullscreen mode Exit fullscreen mode


csharp
builder.Services.AddIdentity>()
.AddRoles>()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();


This configures ASP.NET Core Identity for managing application users and roles.

### A. `AddIdentity<ApplicationUser, IdentityRole<Guid>>()`

This configures Identity to use your custom `ApplicationUser` type and a role type whose primary key is `Guid`.

Your custom user class can contain standard Identity fields as well as additional application-specific properties.

### B. `AddRoles<IdentityRole<Guid>>()`

This explicitly enables role-related Identity services.

`AddIdentity` already configures role support for the specified role type, so this additional call is generally redundant. You can usually remove it unless you have a specific reason to retain it.

### C. `AddEntityFrameworkStores<AppDbContext>()`

This configures Identity to persist its user, role, and related Identity data using Entity Framework Core and your `AppDbContext`.

### D. `AddDefaultTokenProviders()`

This registers the default Identity token providers, which can support features such as password-reset and email-confirmation tokens.

These Identity tokens are different from JWT bearer access tokens.

### Why do we need Identity?

Identity provides established user and role management features instead of requiring you to implement every user-management operation manually.

---

## 16. Configuring JWT Authentication

Enter fullscreen mode Exit fullscreen mode


csharp
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// JWT configuration
});


This configures JWT bearer authentication.

### What is JWT authentication?

JWT stands for JSON Web Token.

A client typically receives an access token after successful login and sends it with requests to protected API endpoints.

For example:

Enter fullscreen mode Exit fullscreen mode


http
Authorization: Bearer eyJhbGciOi...


The backend validates the token before allowing the request to proceed.

### A. `AddAuthentication()`

This registers authentication services and configures the default authentication schemes.

### B. `DefaultAuthenticateScheme`

Enter fullscreen mode Exit fullscreen mode


csharp
options.DefaultAuthenticateScheme =
JwtBearerDefaults.AuthenticationScheme;


This tells ASP.NET Core to use the JWT bearer handler to authenticate requests by default.

### C. `DefaultChallengeScheme`

Enter fullscreen mode Exit fullscreen mode


csharp
options.DefaultChallengeScheme =
JwtBearerDefaults.AuthenticationScheme;


This selects the JWT bearer handler when an unauthenticated request needs an authentication challenge.

### D. `DefaultScheme`

Enter fullscreen mode Exit fullscreen mode


csharp
options.DefaultScheme =
JwtBearerDefaults.AuthenticationScheme;


This sets the default scheme for operations that use the default scheme.

### Why configure all three?

It makes JWT bearer authentication the default for the application's authentication operations.

**Important:** Configuring Identity and JWT authentication together does not automatically mean every Identity cookie or endpoint behaves like a JWT endpoint. Your authentication scheme settings determine which handler is used for authentication and challenges.

---

## 17. Configuring the JWT Bearer Handler

Enter fullscreen mode Exit fullscreen mode


csharp
.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"]))
};

// Events are configured here.
Enter fullscreen mode Exit fullscreen mode

});


This configures how incoming JWTs are processed and validated.

### A. `SaveToken`

Enter fullscreen mode Exit fullscreen mode


csharp
options.SaveToken = true;


This tells the handler to save the token in the authentication properties after successful authentication.

It does not mean the token is automatically stored in browser local storage.

### B. `RequireHttpsMetadata`

Enter fullscreen mode Exit fullscreen mode


csharp
options.RequireHttpsMetadata = false;


This setting affects HTTPS requirements when retrieving metadata from an identity provider. It does not turn off HTTPS for your API and does not make an HTTP API secure.

For a typical self-contained JWT validation setup, no metadata endpoint is configured by this line itself. In production, use HTTPS and review this setting in the context of your authentication design.

### C. `TokenValidationParameters`

Enter fullscreen mode Exit fullscreen mode


csharp
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"]))
};


This configures the checks used to validate incoming JWTs.

#### `ValidateIssuer = true`

The token issuer must be validated.

The issuer identifies the authority that created the token.

#### `ValidateAudience = true`

The token audience must be validated.

The audience identifies the intended recipient of the token, such as your API.

#### `ValidAudience`

Enter fullscreen mode Exit fullscreen mode


csharp
ValidAudience = builder.Configuration["JWT:ValidAudience"]


This specifies the expected audience from configuration.

#### `ValidIssuer`

Enter fullscreen mode Exit fullscreen mode


csharp
ValidIssuer = builder.Configuration["JWT:ValidIssuer"]


This specifies the expected issuer.

The values used to generate tokens must match the values expected during validation.

#### `ClockSkew = TimeSpan.Zero`

Enter fullscreen mode Exit fullscreen mode


csharp
ClockSkew = TimeSpan.Zero


This removes the default allowance for small clock differences when checking time-related claims such as expiration.

As a result, expiration checks are applied without that extra tolerance.

#### `IssuerSigningKey`

Enter fullscreen mode Exit fullscreen mode


csharp
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
builder.Configuration["JWT:Secret"]))


This constructs the symmetric signing key used to validate the token's signature.

- `Encoding.UTF8.GetBytes()` converts the configured secret string into 
Enter fullscreen mode Exit fullscreen mode


markdown

The actual value and claim mapping depend on how your token is created and validated.

The ?. operators safely handle missing intermediate objects.

Step 3: Extract TokenVersion

var tokenVersion = context.Principal?
    .FindFirst("TokenVersion")?.Value;
Enter fullscreen mode Exit fullscreen mode

This reads a custom claim named TokenVersion.

Top comments (1)

Collapse
 
alexcodebytes profile image
Oleksandr •

Maintaining a clean, service-oriented structure within the Program.cs entry point is a solid operational foundation for enterprise .NET applications. Your architectural breakdown perfectly aligns with the principles of Clean Architecture and explicit dependency separation.
The configuration layout demonstrates several production-grade practices:

  1. Deterministic Configuration Injection: Utilizing builder.Configuration.GetConnectionString() ensures that database credentials remain fully decoupled from the compilation unit. This abstracts environment-specific variables gracefully across staging and production pipelines.
  2. Metadata vs. Enforcement Separation: Accurately noting that AddSwaggerGen merely documents the bearer schema rather than securing the HTTP pipeline is a crucial distinction. It prevents common authentication bypass vulnerabilities during rapid deployment phases.
  3. Encapsulation via Namespaces: Mapping application contracts to distinct domain and infrastructure boundaries ensures rigid type-safety and prevents circular dependency leaks across the service layer. To improve this initialization pipeline and maintain high testability as the core booking business logic expands, here are two actionable refactoring patterns you can introduce: • Encapsulation with IServiceCollection Extensions: As CQRS handlers, FluentValidation pipelines, and infrastructure abstractions scale, your root initialization block will inevitably become bloated. Consider moving layer-specific registrations into dedicated extension methods inside each project layer (e.g., builder.Services.AddInfrastructureServices(builder.Configuration)). This maintains an clean orchestration layout at the API root level. • Declarative Swagger Comment Reflection: To ensure that API consumer documentation remains dynamically aligned with backend schema mutations, update the Swagger generator definition to include explicit XML comment mapping via options.IncludeXmlComments(). This parses internal C# summary blocks natively, removing the need for manual endpoint documentation overhead while allowing you to quickly correct the minor JWR string typo in the current bearer definition.