Tuesday, 22 September 2026

ASP.NET Core Dependency Injection Lifetimes: Preventing Shared State Errors

Leave a Comment

Injection of Dependencies in ASP.NET Core is often taught as a purely mechanical concept: register a type, use a constructor to request it, and let the framework take care of the wiring. The fact that choosing the incorrect lifetime for a service registration might subtly reintroduce a well-known bug category the same one that caused traditional ASP.NET is something that is frequently overlooked. problems with session corruption that appear at a separate application layer.

 

Understanding the Three DI Lifetimes

ASP.NET Core's dependency injection container supports three registration lifetimes:

// A single shared instance for the application's entire lifetime
services.AddSingleton<IclsWorkFlow, clsWorkFlow>();

// A new instance created once per HTTP request
services.AddScoped<IclsWorkFlow, clsWorkFlow>();

// A brand-new instance every time it's requested, even within one request
services.AddTransient<IclsWorkFlow, clsWorkFlow>();

A Controller consuming this service never instantiates it directly:

public class WorkFlowController : ControllerBase
{
    private readonly IclsWorkFlow _workFlowDataAccess;

    public WorkFlowController(IclsWorkFlow workFlowDataAccess)
    {
        _workFlowDataAccess = workFlowDataAccess;
    }
}

The framework supplies the instance automatically. The lifetime chosen during registration determines how that instance is created and shared.

How Shared Mutable State Creates Problems

The key issue is not that a particular lifetime is inherently unsafe. The important question is whether the service's lifetime is compatible with its state, dependencies, and concurrency requirements.

A service that stores mutable state in instance fields can become problematic when the same instance is accessed by multiple concurrent operations.

Example: Singleton With Per-Request State

Consider clsWorkFlow registered as a Singleton. There is now exactly one instance of this class, shared across every request the application handles for as long as the application runs.

Suppose that class, even briefly, stores some per-operation state as an instance field:

public class clsWorkFlow : IclsWorkFlow
{
    private string _currentEmpId; // risky if this class is a Singleton

    public List<CtrlSlnoName> GetAddWorkFlows(int employeeSlno, int createdBy)
    {
        _currentEmpId = employeeSlno.ToString();
        // fetch data using _currentEmpId
    }
}

With two requests arriving close together — one from User A, one from User B — both served by the identical shared instance, this sequence becomes entirely possible:

  1. User A's request sets _currentEmpId = "1023"

  2. Before User A's operation finishes, User B's request overwrites the same field with "2045", using that same shared instance

  3. User A's request completes, but now processes User B's employee ID instead of its own

Nothing crashes. No exception surfaces. The application passes normal sequential testing without issue, and the defect only appears under genuine concurrent load — precisely the condition most difficult to catch before production.

The underlying problem is shared mutable state being accessed concurrently.

Why This Resembles Session-Scoping Bugs

This can be compared to a classic ASP.NET Session problem where state associated with multiple operations can be unintentionally shared or overwritten.

The two scenarios are not literally the same mechanism. ASP.NET Session has its own session-state behavior and locking semantics, while ASP.NET Core dependency injection manages service instance lifetimes separately.

However, the two scenarios share the same underlying concurrency pattern: mutable state that is unintentionally shared between operations.

A Singleton service holding per-request state creates this problem at the service-instance level. Instead of separate operations working with isolated state, concurrent requests can access and modify the same mutable object.

Why Scoped Often Fits Request-Oriented Services

Registering clsWorkFlow as Scoped provides a separate instance within each HTTP request:

services.AddScoped<IclsWorkFlow, clsWorkFlow>();

Under Scoped lifetime, User A and User B, even arriving simultaneously, receive separate clsWorkFlow instances within their respective requests. A mutable instance field therefore is not shared between those requests.

Scoped is commonly used for request-oriented services and database contexts such as DbContext, where one instance is typically intended to be shared within a request.

However, Scoped is not automatically the correct lifetime for every data-access or application service. The appropriate lifetime depends on the service's actual state, dependencies, and design.

Singleton Does Not Mean Unsafe

A Singleton service is not inherently unsafe.

The important rule is:

A Singleton must be safe for concurrent use.

A stateless service can safely be Singleton when its dependencies are also compatible with Singleton usage.

For example:

public class EmployeeService : IEmployeeService
{
    public Employee GetEmployee(int id)
    {
        // Local variable, not shared instance state
        var employee = LoadEmployee(id);
        return employee;
    }
}

The absence of mutable instance state makes concurrent access easier to reason about.

A Singleton becomes problematic when it contains mutable state that is not designed for concurrent access or depends on services whose lifetimes are incompatible with Singleton usage.

Transient Does Not Guarantee Thread Safety

Transient creates a new instance whenever the container resolves the service, but that does not automatically make the service thread-safe or isolate all of its state.

For example:

Transient Service
       |
       v
Singleton Dependency
       |
       v
Shared State

The transient service could still interact with shared mutable state through one of its dependencies.

Therefore, changing a service from Singleton to Transient does not automatically solve concurrency problems. The complete dependency graph and the location of mutable state still need to be considered.

Captive Dependencies

Another important DI lifetime issue is the captive dependency problem.

Consider the following registrations:

services.AddSingleton<IReportService, ReportService>();
services.AddScoped<IReportRepository, ReportRepository>();

If ReportService directly depends on IReportRepository, the Singleton is attempting to hold a Scoped dependency for longer than the Scoped lifetime allows.

The basic rule is:

Singleton
   ↓
Should not depend on
   ↓
Scoped service

ASP.NET Core's scope validation can detect many such registrations during development.

This is important because DI lifetime problems are not limited to mutable fields. A service can also have an inappropriate lifetime because of the lifetimes of the services it depends on.

DI Lifetime and Authorization Are Different Concerns

It's worth being precise about the boundaries of this problem.

Role-based authorization — determining whether a given user should be permitted to see or modify particular data — operates independently of thread-safety and instance scoping.

A system can enforce authorization rules and still be exposed to a shared-state bug, because the two concerns sit at different layers:

  • Authorization governs what a user may access.

  • Scoping governs how service instances are created and shared.

  • Thread safety governs whether shared state can be accessed safely by concurrent operations.

Correct authorization logic does not provide protection against concurrency issues caused by inappropriate service state or lifetime configuration.

Practical Code Review Checklist

When reviewing dependency injection registrations, check more than the lifetime declaration itself.

Ask the following questions:

  • Does the service store mutable state in instance fields?

  • Is that state specific to a request, user, or operation?

  • Can multiple requests access the same instance concurrently?

  • Are all dependencies compatible with the service's lifetime?

  • Could a Singleton depend on a Scoped service?

  • Does a Transient service interact with shared Singleton state?

  • Is the service genuinely stateless?

  • Are local variables being used instead of shared instance fields where appropriate?

  • Can the service and its dependencies safely support concurrent access?

The goal is not to choose Scoped for everything. The goal is to make the lifetime match the service's state, dependency behavior, and intended usage.

Summary

ASP.NET Core dependency injection lifetimes determine how service instances are created and shared. The important question is not simply whether a service is Singleton, Scoped, or Transient, but whether that lifetime matches the service's state, dependencies, and concurrency requirements.

A Singleton that stores mutable per-request or per-user state can allow concurrent requests to interfere with one another. Scoped services provide a separate instance within each request and are therefore commonly used for request-oriented services. Transient services provide new instances when resolved, but they do not automatically make shared dependencies or application state thread-safe.

When reviewing a DI registration, look beyond the registration itself. Check instance fields, mutable state, dependencies, and whether the service can safely be used concurrently. This makes it easier to identify shared-state and lifetime problems before they become difficult-to-reproduce production bugs.

 

Read More...

Monday, 14 September 2026

What is the difference between Authentication and Authorization?

Leave a Comment

Two key ideas in application security are permission and authentication. Many people believe they signify the same thing since they are frequently used together. They don't.

The simplest method to comprehend the distinction is:


What authentication entails is: Who are you?
What does authorization mean?
While permission determines what a person may access once their identification has been confirmed, authentication verifies a user's identity.

Let's use some basic examples to comprehend both ideas.

What is Authentication?

Authentication is the process of verifying the identity of a user.

Whenever you log in to a website, mobile application, or online service, authentication is happening.

For example, you enter:

Email: [email protected]
Password: ********

The application checks whether the email and password are correct.

If the credentials are valid, the application confirms your identity.

User enters login details
        ↓
Application checks credentials
        ↓
Credentials are valid
        ↓
User is authenticated

If the credentials are incorrect, authentication fails and access is denied.

So authentication simply answers one question:

Are you really the person you claim to be?

Common Authentication Methods

Applications can authenticate users in several ways.

Username and Password

This is the most common authentication method.

The user provides a username or email address along with a password.

The server checks the credentials and allows access if they are correct.

One-Time Password

Many applications send a temporary code to the user's phone or email.

For example:

OTP: 583921

The user enters the code to verify their identity.

JWT Token

JWT, or JSON Web Token, is commonly used in web APIs.

A user first logs in using valid credentials.

Login
  ↓
Verify user
  ↓
Generate JWT token
  ↓
Return token

The client then sends this token with future API requests.

For example:

Authorization: Bearer eyJhbGciOiJIUzI1Ni...

The server validates the token before processing the request.

Social Login

Applications can also authenticate users using services such as:

Google
Microsoft
GitHub
Apple
Facebook

Instead of creating a separate password, users can sign in using an existing account.

Biometric Authentication

Mobile applications often use:

Fingerprint
Face recognition
Face ID

These methods help verify the identity of the user.

What is Authorization?

Authorization happens after authentication.

Once the application knows who the user is, it needs to decide what that user is allowed to do.

Suppose an application has three types of users:

Admin
Manager
Employee

All three users can successfully log in.

This means they are authenticated.

However, they may have different permissions.

For example:

Admin
  → View users
  → Create users
  → Delete users
  → Manage settings

Manager
  → View users
  → View reports
  → Manage team

Employee
  → View profile
  → View personal dashboard

The process of deciding which features each user can access is called authorization.

A Simple Real-World Example

Imagine you work in a large office.

When you arrive at the entrance, the security guard asks for your employee ID card.

You show the card.

The security system confirms:

Name: John
Employee ID: 1025
Status: Active

You are allowed inside.

This is authentication.

The system has confirmed who you are.

Now suppose you try to enter the server room.

You scan your access card.

The system checks whether you have permission to enter that room.

If you are part of the IT team:

Access Granted

If you do not have permission:

Access Denied

This is authorization.

So the complete idea is:

Who are you?
      ↓
Authentication

What are you allowed to access?
      ↓
Authorization

Authentication vs Authorization

Here is a simple comparison.

Authentication

Authorization

Verifies identity

Verifies permissions

Answers "Who are you?"

Answers "What can you do?"

Happens first

Happens after authentication

Usually happens during login

Happens when accessing protected resources

Uses password, OTP, token, biometrics

Uses roles, permissions, policies, claims

Example: Logging in

Example: Opening an admin page

The easiest way to remember it is:

Authentication = Identity

Authorization = Permission

How Authentication and Authorization Work Together

Consider an application with an admin dashboard.

A user sends a request to:

/admin/users

The application first checks authentication.

Request
   ↓
Authentication
   ↓
Who is this user?

Suppose the user is successfully authenticated.

The application now knows:

User: John
Role: Employee

Next, authorization checks whether John is allowed to access the admin page.

Authentication Successful
        ↓
Authorization
        ↓
Does John have Admin permission?

If not:

Access Denied

If John has the required permission:

Access Granted
      ↓
Admin Dashboard

So the complete process looks like this:

User Request
     ↓
Authentication
     ↓
Identity Verified
     ↓
Authorization
     ↓
Permission Verified
     ↓
Application

Authentication in ASP.NET Core

ASP.NET Core provides built-in authentication support.

For example, an application may configure JWT authentication:

builder.Services.AddAuthentication()
    .AddJwtBearer();

Then authentication middleware is added:

app.UseAuthentication();

This middleware checks authentication information included with incoming requests.

For example, it may validate a JWT token and identify the current user.

Authorization in ASP.NET Core

Authorization is generally configured after authentication.

app.UseAuthentication();

app.UseAuthorization();

The order is important.

First:

Authentication
Who is the user?

Then:

Authorization
What can the user access?

The application needs to know the user's identity before checking permissions.

Using the Authorize Attribute

ASP.NET Core provides the [Authorize] attribute for protecting endpoints.

For example:

[Authorize]
[HttpGet]
public IActionResult GetProfile()
{
    return Ok();
}

This endpoint can only be accessed by authenticated users.

If a user is not authenticated, access will be denied.

Role-Based Authorization

Sometimes simply being authenticated is not enough.

For example, imagine an API that deletes users:

DELETE /api/users/100

Only administrators should be allowed to use this API.

We can restrict the endpoint using a role:

[Authorize(Roles = "Admin")]
[HttpDelete("{id}")]
public IActionResult DeleteUser(int id)
{
    return Ok();
}

The application now performs two checks.

First:

Is the user authenticated?

Then:

Does the user have the Admin role?

Only when both conditions are true will the request be allowed.

Permission-Based Authorization

Roles are useful, but sometimes applications need more control.

Instead of using only roles, an application can use permissions.

For example:

CanViewUsers
CanCreateUsers
CanEditUsers
CanDeleteUsers
CanViewReports
CanManagePayments

A user could have:

Name: John

Role:
Manager

Permissions:
CanViewUsers
CanViewReports
CanEditReports

John can perform only the operations allowed by these permissions.

Permission-based authorization can be useful in large applications where different users require different levels of access.

Authentication with JWT

JWT authentication is very common when building APIs.

Suppose a user sends this request:

POST /api/login

with:

{
  "email": "[email protected]",
  "password": "password"
}

The server checks the credentials.

If they are correct:

Login Request
      ↓
Check Credentials
      ↓
Authentication Successful
      ↓
Generate JWT
      ↓
Return Token

The client receives the token and uses it for future API requests.

For example:

GET /api/profile

Authorization: Bearer <JWT_TOKEN>

The server validates the token.

If the token is valid, the user is authenticated.

Authorization can then check whether the user has permission to access /api/profile.

What is 401 Unauthorized?

HTTP status code 401 Unauthorized usually means the user has not been successfully authenticated.

For example:

GET /api/profile

without a valid authentication token may return:

401 Unauthorized

Common reasons include:

Token is missing
Token is invalid
Token has expired
Credentials are incorrect

Although the status code is called Unauthorized, it is mainly related to authentication.

What is 403 Forbidden?

HTTP status code 403 Forbidden usually means the user has been authenticated but does not have permission to access the requested resource.

For example:

User: John
Role: Employee

John tries to access:

/api/admin/users

The endpoint requires the Admin role.

The application knows who John is, but John does not have permission.

The server returns:

403 Forbidden

A simple way to remember this is:

401 = I cannot verify who you are.

403 = I know who you are, but you cannot access this.

Authentication is Not Authorization

One common security mistake is assuming that a logged-in user should automatically have access to everything.

That is not correct.

Imagine an application with:

5,000 users
50 managers
5 administrators

All 5,000 users may be authenticated.

But that does not mean all users should be able to access:

/admin/users
/admin/settings
/admin/payments
/admin/reports

Authentication confirms identity.

Authorization protects sensitive resources.

Secure applications normally need both.

Frontend Authorization Is Not Enough

Applications often hide buttons or pages based on the user's role.

For example, a React application may hide the Delete User button for normal users.

That is useful for the user experience, but it is not enough for security.

A user could still manually call:

DELETE /api/users/100

Therefore, authorization must also be checked on the backend.

The backend should always be the final authority when deciding whether a user is allowed to perform an action.

Authentication and Authorization in an API Request

Let's look at a complete API request.

A client sends:

GET /api/admin/reports

Authorization: Bearer <JWT_TOKEN>

The request may travel through the application like this:

Client
   ↓
HTTP Request
   ↓
Authentication Middleware
   ↓
Validate Token
   ↓
Identify User
   ↓
Authorization Middleware
   ↓
Check Role or Permission
   ↓
Controller
   ↓
Service
   ↓
Database
   ↓
HTTP Response

Authentication establishes the user's identity.

Authorization determines whether that user can access the requested resource.

Why Authentication Comes Before Authorization

Suppose an application needs to answer:

Does this user have Admin permission?

Before answering that question, the application needs to know:

Which user?

That is why authentication comes first.

Step 1

Who are you?
     ↓
Authentication


Step 2

What can you access?
     ↓
Authorization

This is also why ASP.NET Core applications normally use:

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

in this order.

Common Authentication and Authorization Mistakes

There are a few common mistakes developers should avoid.

Protecting the Login but Not the APIs

A login page may be secure, but individual APIs must also be protected.

Sensitive endpoints should have proper authorization checks.

Trusting the Frontend

Never rely only on the frontend to decide permissions.

A hidden button is not a security control.

The backend should always verify permissions.

Giving Too Many Permissions

Users should only receive the permissions required to perform their work.

For example, an employee who only needs to view reports should not have permission to delete users.

This follows the principle of least privilege.

Confusing 401 and 403

Remember:

401 → Authentication problem

403 → Authorization problem

Checking Roles Without Checking Identity

Authorization normally depends on authentication.

The application first identifies the user and then checks their roles or permissions.

Another Simple Example

Think about travelling by airplane.

At the airport, you show your passport.

Your passport proves who you are.

That is:

Authentication

Then you show your boarding pass.

Your boarding pass tells you which flight and seat you are allowed to use.

That is:

Authorization

So:

Passport
   ↓
Authentication

Boarding Pass
   ↓
Authorization

The same concept applies to web applications.

Authentication vs Authorization in One Sentence

If you ever forget the difference, just remember:

Authentication verifies the user. Authorization verifies the user's access.

Or even more simply:

Authentication = Who are you?

Authorization = What can you do?

Conclusion

Authentication and authorization are closely related, but they perform different jobs.

Authentication verifies the identity of a user using methods such as passwords, OTPs, JWT tokens, social login, or biometrics.

Authorization happens after authentication and decides which resources and operations the authenticated user can access.

The normal security flow is:

Request
   ↓
Authentication
   ↓
Identity Verified
   ↓
Authorization
   ↓
Permission Verified
   ↓
API / Application

In ASP.NET Core, authentication middleware identifies the user, while authorization middleware checks whether that user has permission to access a protected resource.

Understanding this difference is essential when building secure APIs, websites, mobile applications, and enterprise systems.

The simplest rule to remember is:

Authentication tells the application who you are.
Authorization tells the application what you are allowed to do.

ASP.NET Core 10.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 9.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE.eu, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

Read More...

Monday, 7 September 2026

C# Access Specifiers: A Comprehensive Guide with Examples

Leave a Comment

In C#, access specifiers are used to specify whether types and their members are visible or accessible.
To put it simply, an access specifier establishes who may access a class, method, property, field, constructor, or other member, as well as from what location.

Let's take an example where a class has several methods. While certain methods may only be accessible inside the same class or within derived classes, others may need to be accessible to all classes in an application.


To manage this visibility and assist developers in implementing encapsulation, C# has access modifiers.

Take a look at this example:

public class Employee
{
    private string employeeName;

    public void SetEmployeeName(string name)
    {
        employeeName = name;
    }

    public string GetEmployeeName()
    {
        return employeeName;
    }
}

Here, employeeName is declared as private, so it cannot be accessed directly from outside the Employee class. The public methods provide controlled access to the value.

This is one of the fundamental concepts of object-oriented programming in C#.

Different Types of Access Specifiers in C#

C# provides six commonly used access modifiers:

  1. private

  2. public

  3. protected

  4. internal

  5. protected internal

  6. private protected

The private protected access modifier was introduced in C# 7.2.

The accessibility provided by these modifiers depends on whether the accessing code is located in the same class, the same assembly, or another assembly, and whether inheritance is involved.

1. Private Access Modifier

The private access modifier restricts access to the containing type.

A private member can be accessed from within the class where it is declared, but it cannot normally be accessed directly from derived classes or unrelated classes.

Example

public class Employee
{
    private string employeeName = "John";

    public void DisplayName()
    {
        Console.WriteLine(employeeName);
    }
}

The employeeName field can be accessed inside the Employee class:

Employee employee = new Employee();
employee.DisplayName();

But the following code is not allowed:

Employee employee = new Employee();

// Compile-time error
// employee.employeeName = "David";

Accessibility of a Private Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

No

Non-derived class in same assembly

No

Derived class in another assembly

No

Non-derived class in another assembly

No

private is useful when an implementation detail should be completely hidden from other types.

2. Public Access Modifier

The public access modifier provides the widest accessibility.

A public member can be accessed from code that can access the containing type, including code in other assemblies.

Example

public class Employee
{
    public string EmployeeName = "John";

    public void DisplayName()
    {
        Console.WriteLine(EmployeeName);
    }
}

The member can be accessed from another class:

Employee employee = new Employee();

Console.WriteLine(employee.EmployeeName);
employee.DisplayName();

It can also be accessed from a derived class:

public class Manager : Employee
{
    public void DisplayManagerName()
    {
        Console.WriteLine(EmployeeName);
    }
}

Accessibility of a Public Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

Yes

Derived class in another assembly

Yes

Non-derived class in another assembly

Yes

Public members should generally represent functionality that is intentionally exposed as part of a type's API.

3. Protected Access Modifier

The protected access modifier allows access within the containing type and from derived types.

Unlike public, a protected member cannot normally be accessed through an object from an unrelated class.

Example

public class Employee
{
    protected string employeeName = "John";
}

public class Manager : Employee
{
    public void DisplayName()
    {
        Console.WriteLine(employeeName);
    }
}

Manager can access employeeName because Manager derives from Employee.

However, an unrelated class cannot access it directly:

public class Test
{
    public void Display()
    {
        Employee employee = new Employee();

        // Compile-time error
        // Console.WriteLine(employee.employeeName);
    }
}

Accessibility of a Protected Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

No

Derived class in another assembly

Yes

Non-derived class in another assembly

No

protected is commonly used when a base class needs to expose implementation details to derived classes without making those details publicly accessible.

4. Internal Access Modifier

The internal access modifier restricts access to the current assembly.

An assembly is typically the compiled output of a .NET project, such as a .dll or .exe.

An internal member can be accessed by other types within the same assembly but not directly from another assembly.

Example

internal class Employee
{
    internal string EmployeeName = "John";
}

Another class in the same project can access it:

public class Department
{
    public void DisplayEmployee()
    {
        Employee employee = new Employee();

        Console.WriteLine(employee.EmployeeName);
    }
}

However, code in another assembly cannot directly access the internal type or member unless mechanisms such as InternalsVisibleTo are used.

Accessibility of an Internal Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

Yes

Derived class in another assembly

No

Non-derived class in another assembly

No

internal is useful when functionality needs to be shared among types within a project but should not form part of the project's public API.

5. Protected Internal Access Modifier

protected internal combines two accessibility conditions.

A member declared as protected internal can be accessed:

  • From anywhere within the same assembly, or

  • From a derived class, including a derived class in another assembly.

The important point is that this is effectively an OR relationship between protected and internal.

Example

public class Employee
{
    protected internal string EmployeeName = "John";
}

A class in the same assembly can access the member:

public class Department
{
    public void DisplayEmployee()
    {
        Employee employee = new Employee();

        Console.WriteLine(employee.EmployeeName);
    }
}

A derived class in another assembly can also access the member.

Accessibility of a Protected Internal Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

Yes

Derived class in another assembly

Yes

Non-derived class in another assembly

No

Because protected internal provides relatively broad access, it should be used when both same-assembly access and derived-type access are intentionally required.

6. Private Protected Access Modifier

The private protected access modifier provides more restricted access than protected internal.

A private protected member can be accessed only:

  • Within the containing type, or

  • From derived types that are located in the same assembly.

It was introduced in C# 7.2.

Example

public class Employee
{
    private protected string employeeName = "John";
}

public class Manager : Employee
{
    public void DisplayName()
    {
        Console.WriteLine(employeeName);
    }
}

The Manager class can access employeeName because it derives from Employee and is in the same assembly.

A derived class in another assembly cannot access the member.

Accessibility of a Private Protected Member

Accessing Code

Accessible?

Containing class

Yes

Derived class in same assembly

Yes

Non-derived class in same assembly

No

Derived class in another assembly

No

Non-derived class in another assembly

No

This modifier is useful when a base class wants to expose a member only to derived types that are part of the same assembly.

Access Modifier Comparison

The following table provides a quick comparison:

Access Modifier

Same Class

Same Assembly

Derived Type in Other Assembly

Non-Derived Type in Other Assembly

private

Yes

No

No

No

public

Yes

Yes

Yes

Yes

protected

Yes

No for unrelated types

Yes for derived types

No

internal

Yes

Yes

No

No

protected internal

Yes

Yes

Yes for derived types

No

private protected

Yes

Yes for derived types

No

No

The key distinction to remember is:

protected internal = protected OR internal

private protected = protected AND internal

This makes the difference between the two modifiers easier to understand.

Access Modifiers for Types

Access modifiers can also be applied to types such as classes, interfaces, structs, delegates, and enums, subject to the accessibility rules of each type.

For example:

public class Employee
{
}

The Employee class is publicly accessible.

An internal class can be declared as:

internal class Department
{
}

The Department type is accessible only within the same assembly.

For top-level types, private and protected are not valid access modifiers. They are primarily used for members and nested types.

Why Access Modifiers Are Important

Access modifiers are an important part of encapsulation.

They allow developers to decide which parts of a class should be exposed and which implementation details should remain hidden.

For example:

public class BankAccount
{
    private decimal balance;

    public void Deposit(decimal amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
    }

    public decimal GetBalance()
    {
        return balance;
    }
}

Here, balance is private. External code cannot directly change it:

BankAccount account = new BankAccount();

// Not allowed
// account.balance = -5000;

Instead, the class controls how the balance changes through the public Deposit method.

This helps protect the internal state of the object and keeps business rules inside the class.

Common Mistakes

Making Everything Public

A common beginner mistake is declaring every field and method as public.

For example:

public class Employee
{
    public string name;
    public decimal salary;
}

This exposes the internal state of the class unnecessarily.

A better approach is to expose only what other parts of the application actually need.

Confusing Protected and Internal

protected is primarily related to inheritance, while internal is related to the assembly boundary.

For example:

protected
    -> containing type + derived types

internal
    -> types within the same assembly

Understanding these two boundaries makes the other access modifiers much easier to understand.

Confusing Protected Internal and Private Protected

These two modifiers look similar but provide different levels of access.

protected internal
    = protected OR internal

private protected
    = protected AND internal

Therefore, protected internal is broader, while private protected is more restrictive.

Best Practices

When choosing an access modifier, consider the smallest scope required by the member.

Some general guidelines are:

  • Use private for implementation details that should remain inside the type.

  • Use public only when functionality needs to be exposed to consumers.

  • Use protected when derived classes need access to a member.

  • Use internal for functionality that should remain within the current assembly.

  • Use protected internal when both same-assembly access and derived-type access are required.

  • Use private protected when access should be limited to derived types within the same assembly.

  • Prefer encapsulation instead of exposing internal fields directly.

Conclusion

Access specifiers in C# provide a mechanism for controlling the accessibility and visibility of types and their members.

The six commonly used access modifiers are private, public, protected, internal, protected internal, and private protected.

Understanding the difference between these modifiers is important for designing classes with proper encapsulation and well-defined APIs.

For beginners, the easiest way to remember them is to focus on two boundaries: inheritance and assembly. Once these boundaries are clear, choosing the appropriate access modifier becomes much easier.

Best ASP.NET Core 10.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 10.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFEASP.NET, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.
Read More...

Tuesday, 1 September 2026

How to Use WebSockets and Socket to Implement Real-Time Features.IO?

Leave a Comment

Real-time communication is necessary for contemporary applications including chat apps, live alerts, online gaming, and stock trading platforms. This implies that data should change immediately without requiring a page refresh.



WebSockets and frameworks like Socket.IO make this feasible.

Let's take a step-by-step look at how real-time communication functions and how to put it into practice.

What Are WebSockets?

Simple Explanation

WebSockets provide a persistent connection between client and server.

This means:

  • Data can be sent anytime

  • No need to request again and again

Real-Life Example

In WhatsApp:

  • Messages appear instantly

  • No page refresh needed

This uses WebSockets.

What Is Socket.IO?

Simple Explanation

Socket.IO is a library built on top of WebSockets that makes real-time communication easier.

Why Use Socket.IO

  • Handles connection automatically

  • Supports fallback methods

  • Easy to use for developers

How WebSockets Work

Step 1: Connection Establishment

Client connects to server using WebSocket protocol.

Step 2: Persistent Connection

Connection stays open for continuous communication.

Step 3: Data Exchange

Client and server send data anytime.

Step 4: Real-Time Updates

Data updates instantly on UI.

Step-by-Step Implementation Guide

Step 1: Setup Server

Use Node.js with Socket.IO.

Step 2: Create Client Connection

Connect frontend to server using socket.

Step 3: Listen for Events

Server listens for events like messages.

Step 4: Emit Events

Send data between client and server.

Example:
User sends message → server receives → sends to other users

Step 5: Update UI in Real Time

Display updates instantly on screen.

Real-World Use Cases

Chat Applications

Real-time messaging between users.

Live Notifications

Instant alerts for users.

Online Gaming

Real-time player actions.

Stock Market Apps

Live price updates.

Advantages

  • Real-time communication

  • Faster user experience

  • Reduces server load compared to polling

  • Supports scalable applications

Disadvantages

  • Requires persistent connection management

  • More complex than traditional HTTP

  • Scaling can be challenging

Summary

WebSockets and Socket.IO are essential technologies for building real-time web applications. They allow instant communication between client and server without refreshing the page. For developers in India and globally, mastering real-time features helps build modern applications like chat apps, live dashboards, and gaming platforms with smooth user experience.

Best ASP.NET Core 10.0 Hosting in Europe with 15% OFF Discount!

One of the most important things when choosing a good ASP.NET Core 10.0 hosting in Europe is the feature and reliability. Led by a team with expert who are familiar on ASP.NET technologies, HostForLIFE offers an array of both basic and advanced ASP.NET Core 10.0 features in the package at the same time, such as:


All of their Windows & ASP.NET Core 10.0 Hosting servers are located in state of the art data center facilities that provide 24 hour monitoring and security. You can rest assured that while we do aim to provide cheap Windows and ASP.NET Core 10.0 hosting, we have invested a great deal of time and money to ensure you get excellent uptime and optimal performance. While there are several ASP.NET Core 10.0 Hosting providers many of them do not provide an infrastructure that you would expect to find in a reliable Windows platform.


Read More...

Wednesday, 26 August 2026

Form for ASP.NET Core 11 Testing Static SSR Under Concurrent Requests for Validation

Leave a Comment

One of the components of a web application that appears straightforward until actual users begin submitting them simultaneously is the form. While the program is operating, dozens, hundreds, or thousands of requests may be sent to a registration form, checkout form, help request, or profile editor. Under that load, the server must continue producing replies without needless effort, and the validation logic must stay accurate.


An intriguing paradigm for this situation is provided by ASP.NET Core static server-side rendering. After receiving the request, the server processes the form, verifies the information entered, and returns HTML.

This article looks at form validation in a static SSR application and focuses on a practical question: what happens when multiple users submit forms concurrently?

The goal is not to claim a particular throughput number. Performance depends heavily on the application, hardware, database, network, and validation rules. Instead, we will build a reproducible testing approach and identify the areas worth measuring.

Understanding Static SSR Form Submission

With static SSR, the browser initially receives HTML generated by the server.

A simplified form flow looks like this:

Browser
   |
   | GET /register
   v
ASP.NET Core
   |
   | Render form
   v
HTML response
   |
   v
Browser
   |
   | POST form
   v
ASP.NET Core
   |
   | Validate
   v
Success / Validation response

The server remains responsible for processing the submitted form.

This makes the server-side validation path especially important.

A well-designed application should not depend only on browser-side validation because client-side validation can be bypassed.

Creating a Simple Static SSR Form

Consider a registration model:

using System.ComponentModel.DataAnnotations;

public class RegistrationModel
{
    [Required]
    [StringLength(100)]
    public string Name { get; set; } = string.Empty;

    [Required]
    [EmailAddress]
    public string Email { get; set; } = string.Empty;

    [Required]
    [MinLength(8)]
    public string Password { get; set; } = string.Empty;
}

A Razor component can expose the form:

@page "/register"

<EditForm Model="model" OnValidSubmit="HandleSubmit">
    <DataAnnotationsValidator />

    <ValidationSummary />

    <div>
        <label>Name</label>
        <InputText @bind-Value="model.Name" />
        <ValidationMessage For="@(() => model.Name)" />
    </div>

    <div>
        <label>Email</label>
        <InputText @bind-Value="model.Email" />
        <ValidationMessage For="@(() => model.Email)" />
    </div>

    <div>
        <label>Password</label>
        <InputText type="password"
                   @bind-Value="model.Password" />
        <ValidationMessage For="@(() => model.Password)" />
    </div>

    <button type="submit">Create Account</button>
</EditForm>

@code {
    private RegistrationModel model = new();

    private void HandleSubmit()
    {
        // Process valid form submission.
    }
}
Razor C#

The exact form configuration depends on the Blazor rendering mode and application architecture, but the principle is straightforward: validate the submitted model on the server before performing the business operation.

Why Server-Side Validation Matters

Consider a registration endpoint that performs this sequence:

Receive request
     |
     v
Validate input
     |
     v
Check business rules
     |
     v
Check database
     |
     v
Create account

If validation happens after expensive database operations, invalid requests can consume unnecessary resources.

A better approach is to reject obviously invalid input as early as possible.

For example:

if (string.IsNullOrWhiteSpace(model.Email))
{
    return;
}

if (!new EmailAddressAttribute().IsValid(model.Email))
{
    return;
}

In a real application, use the validation system consistently rather than duplicating validation rules throughout handlers.

Validation and Business Rules Are Different

Data annotations are useful for basic input validation.

For example:

[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;

But business validation can be more complicated.

A registration process might require:

  • Email uniqueness

  • Account eligibility

  • Password policy

  • Organization membership

  • Invitation validation

Those checks usually require application or database access.

A useful validation pipeline is:

Input Validation
      |
      v
Business Validation
      |
      v
Database Validation
      |
      v
Business Operation

Keeping these stages separate makes the code easier to test and helps prevent unnecessary database calls.

Handling Concurrent Requests

Suppose 100 users submit the form at approximately the same time.

The server may process requests concurrently:

Request 1  ----\
Request 2  -----\
Request 3  ------> ASP.NET Core
Request 4  -----/
Request 5  ----/

The application should not store request-specific information in shared mutable state.

For example, this is dangerous:

public static RegistrationModel CurrentRegistration { get; set; }

Multiple requests can overwrite the same object.

Instead, keep request data local to the request:

public async Task ProcessRegistration(
    RegistrationModel model)
{
    // Work with this request's model.
}

This allows independent requests to be processed safely.

Avoiding Shared Mutable State

A common mistake in server-side applications is using a singleton service to hold data that belongs to an individual request.

For example:

builder.Services.AddSingleton<RegistrationState>();

If RegistrationState contains the current user's form data, concurrent requests can interfere with each other.

A better lifetime depends on what the service actually represents.

For request-specific work:

builder.Services.AddScoped<
    RegistrationService>();

The important rule is not simply "always use scoped."

It is:

Choose a service lifetime that matches the lifetime of the data it owns.

Testing Concurrent Form Submissions

A load-testing tool can generate concurrent HTTP requests.

For example, a simple HttpClient test can issue multiple requests:

var tasks = Enumerable.Range(0, 100)
    .Select(async index =>
    {
        var content = new FormUrlEncodedContent(
        [
            new("Name", $"User {index}"),
            new("Email", $"user{index}@example.com"),
            new("Password", "Password123")
        ]);

        return await client.PostAsync(
            "/register",
            content);
    });

var responses = await Task.WhenAll(tasks);

This is useful for a basic concurrency test, but it is not a replacement for a dedicated load-testing tool.

For serious performance testing, tools such as k6, JMeter, or another HTTP load-testing platform provide better control over concurrency, duration, ramp-up, and reporting.

Designing a Useful Load Test

Avoid immediately sending thousands of requests to an application.

Start with a small test and increase concurrency gradually.

For example:

10 concurrent requests
        |
        v
25 concurrent requests
        |
        v
50 concurrent requests
        |
        v
100 concurrent requests
        |
        v
Higher load if required

At each level, observe:

  • Response time

  • Error rate

  • CPU usage

  • Memory usage

  • Database activity

  • Request throughput

This helps identify where the application begins to struggle.

Testing Valid and Invalid Requests

A realistic test should not send only successful forms.

Include different request categories.

Request TypeExample
ValidComplete registration
Missing nameEmpty name
Invalid emailIncorrect format
Weak passwordToo short
Duplicate emailExisting account
Invalid business stateExpired invitation
Malformed requestUnexpected input

This is important because invalid requests should normally be cheaper to process than valid ones that reach database writes.

Testing Database Contention

Form validation frequently involves database queries.

For example:

var existingUser = await db.Users
    .SingleOrDefaultAsync(x => x.Email == model.Email);

if (existingUser is not null)
{
    // Return validation error.
}

Under concurrency, this query can become a bottleneck.

More importantly, checking for an existing email and then inserting a new user can introduce a race condition.

Two requests can perform:

Request A: Email does not exist
Request B: Email does not exist

Request A: Insert
Request B: Insert

Application-level validation alone does not guarantee uniqueness.

The database should enforce the actual invariant with a unique constraint or index.

For example:

CREATE UNIQUE INDEX ux_users_email
ON users (email);

The application can then handle a uniqueness violation gracefully.

Protecting Against Over-Validation

Validation itself can become expensive if every rule requires a database query.

Imagine a form with ten fields where each validator independently queries the database.

Under high concurrency, this can produce unnecessary database traffic.

Instead, group related checks where appropriate:

Request
  |
  +--> Basic validation
  |
  +--> One consolidated business validation stage
  |
  +--> Database operation

The goal is not to avoid database access completely.

The goal is to avoid repeated and unnecessary work.

Measuring Response Time

A load test should track multiple latency measurements.

For example:

Average response time
Median response time
95th percentile
99th percentile
Error rate
Requests per second

Percentiles are particularly useful.

An average response time can look healthy while a smaller group of requests experiences very long delays.

For example:

Most requests: fast
Some requests: very slow

The average can hide that difference.

Do not publish benchmark values unless they come from a controlled test environment.

Memory and CPU Under Load

Concurrent form submissions can increase both CPU and memory usage.

Monitor the application while increasing concurrency.

A simple test table might look like:

ConcurrencyRequestsError RateP95CPUMemory
10MeasureMeasureMeasureMeasureMeasure
25MeasureMeasureMeasureMeasureMeasure
50MeasureMeasureMeasureMeasureMeasure
100MeasureMeasureMeasureMeasureMeasure

The actual values depend entirely on the application and environment.

The purpose of the table is to make the test repeatable and easy to compare.

Common Mistakes

Trusting Client-Side Validation

Client-side validation improves user experience but should not be treated as a security boundary.

Always validate important input on the server.

Storing Request Data Globally

Shared mutable state can cause users' requests to interfere with each other.

Keep request-specific data scoped appropriately.

Relying Only on Application Checks

A "check then insert" operation is not enough to guarantee uniqueness under concurrency.

Use database constraints for database-level invariants.

Testing Only Successful Requests

Invalid requests can exercise completely different application paths.

Include both valid and invalid submissions.

Starting With Extreme Load

A huge concurrency test can make it difficult to understand where the problem started.

Increase load gradually.

Troubleshooting Slow Form Submissions

If response times increase as concurrency grows, investigate the entire request path.

Check:

  1. Validation logic.

  2. Database queries.

  3. Database connection pool usage.

  4. Lock contention.

  5. CPU utilization.

  6. Garbage collection.

  7. External service calls.

  8. Shared application state.

  9. Logging volume.

  10. Response generation.

If database time grows rapidly, inspect the SQL queries and database execution plans.

If CPU reaches saturation while database activity remains low, application-side processing may be the bottleneck.

If memory continually grows during the test, investigate object retention, caching, and resource disposal.

Best Practices

Validate Early

Reject invalid requests before performing expensive work.

Keep Request State Isolated

Do not use shared mutable state for user-specific form data.

Let the Database Enforce Invariants

Use unique constraints and other database constraints for rules that must remain true regardless of application behavior.

Test Realistic Workloads

Use representative form sizes, validation rules, database data, and concurrency levels.

Measure Percentiles

P95 and P99 latency often reveal problems that averages hide.

Monitor the Whole Stack

Application performance cannot be understood by looking only at the ASP.NET Core process.

Monitor the database and external dependencies as well.

Advantages

  • Static SSR provides a straightforward server-side request model.

  • Server-side validation keeps important business rules under application control.

  • Forms can be tested using standard HTTP load-testing tools.

  • Validation logic can be optimized independently from the UI.

  • Database constraints can protect important invariants under concurrent requests.

Disadvantages

  • Every submission requires server-side processing.

  • High concurrency can increase CPU, memory, and database pressure.

  • Expensive validation rules can become a bottleneck.

  • Incorrect service lifetimes can create concurrency problems.

  • Static SSR is not automatically faster simply because rendering happens on the server.

Conclusion

The real test starts when numerous users submit server-rendered forms at once, but static SSR provides ASP.NET Core apps with a simple paradigm for managing such forms.

A dependable solution allows the database to enforce important invariants like uniqueness, verifies input on the server, and isolates request-specific information.

Start with a low concurrency level and progressively raise it for performance testing. Instead of concentrating on just one statistic, measure response-time percentiles, error rates, CPU, memory, and database activities.

 Above all, test the application's real validation process. A form that does several database queries and external service requests behaves considerably differently from one that only has basic annotations.

Creating an impressive request-per-second figure is not the aim of concurrency testing. The goal is to pinpoint the precise area of the request pipeline that requires care and determine where the application begins to deteriorate.

Best ASP.NET Core 10.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 8.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE.eu, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFEASP.NET guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

 

Read More...