Skip to main content
AndanDo is built on Blazor Server, a .NET framework that enables interactive web applications with C# instead of JavaScript. The architecture follows a layered, service-oriented design with clear separation of concerns.

Architecture Overview

The system uses a monolithic Blazor Server architecture with the following key characteristics:

Interactive Components

Server-side rendering with real-time SignalR connection for UI updates

Service Layer

Dependency-injected services handling business logic and data access

SQL Server Database

Stored procedures for data operations with ADO.NET

External Integrations

PayPal, email services, geolocation APIs, and currency conversion

High-Level Architecture Diagram

Architectural Layers

1. Presentation Layer

The presentation layer consists of Razor Components organized by feature area:
Components/
├── Pages/                    # Routable page components
│   ├── Home.razor           # Landing page with tour cards
│   ├── Marketplace.razor    # Tour marketplace listing
│   ├── SearchResults.razor  # Search results with filters
│   ├── TourDetails.razor    # Tour detail view
│   ├── Login_Account.razor  # Authentication
│   ├── Register_Account.razor
│   ├── Invoice.razor        # Invoice generation
│   ├── Dashboard/           # Creator dashboard pages
│   │   ├── MyBookings.razor
│   │   ├── Reports.razor
│   │   └── ...
│   ├── Administrator/       # Admin pages
│   └── FormsServices/       # Tour creation/editing
├── Layout/                  # Layout components
└── Shared/                  # Reusable components
    ├── NavMenu.razor
    ├── Searchbar.razor
    ├── Sliders.razor
    ├── ValidateLogin.razor
    └── ImageHelper.cs
Key characteristics:
  • Interactive Server render mode (InteractiveServer)
  • Real-time UI updates via SignalR
  • Component-based architecture with data binding
  • Custom preloader with random loading messages

2. Service Layer

The service layer implements dependency injection with scoped services registered in Program.cs. Services are organized by domain:
// Tour management
builder.Services.AddScoped<ITourService, TourService>();

// Authentication & security
builder.Services.AddScoped<IJwtTokenService, JwtTokenService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IPasswordResetService, PasswordResetService>();
builder.Services.AddScoped<UserSession>();

// Email & notifications
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddScoped<IMailService, MailService>();
builder.Services.AddSingleton<MailNotificationDispatcher>();

// External integrations
builder.Services.AddHttpClient<IPaypalService, PaypalService>();
builder.Services.AddScoped<ICurrencyConversionService, CurrencyConversionService>();

// Utility services
builder.Services.AddScoped<IPostLikeService, PostLikeService>();
builder.Services.AddScoped<IReviewService, ReviewService>();
builder.Services.AddScoped<IFormularioTipoService, FormularioTipoService>();
builder.Services.AddScoped<ISettingsService, SettingsService>();
Service Lifetimes:
  • Scoped: Most services (one instance per HTTP request/SignalR connection)
  • Singleton: MailNotificationDispatcher (shared across application)
  • HttpClient: Factory pattern for external API calls

3. Data Access Layer

Data access is implemented using ADO.NET with SQL Server and stored procedures:
TourService.cs - Stored Procedure Call
public async Task<List<TourDto>> GetToursAsync(CancellationToken cancellationToken = default)
{
    var tours = new List<TourDto>();
    
    await using var connection = new SqlConnection(_connectionString);
    await connection.OpenAsync(cancellationToken);
    
    await using var command = new SqlCommand("sp_Tours_GetAll", connection)
    {
        CommandType = CommandType.StoredProcedure
    };
    
    await using var reader = await command.ExecuteReaderAsync(cancellationToken);
    while (await reader.ReadAsync(cancellationToken))
    {
        tours.Add(MapTourFromReader(reader));
    }
    
    return tours;
}
Key characteristics:
  • Direct ADO.NET for performance
  • Stored procedures for complex operations
  • Parameterized queries to prevent SQL injection
  • Async/await throughout

Authentication & Security

Authentication Flow

AndanDo implements a JWT-based authentication system with cookie storage:

Security Features

Password Hashing

PBKDF2 with 100,000 iterations, SHA256, 16-byte salt

JWT Tokens

Configurable expiration (default 120 minutes)

Protected Storage

ProtectedLocalStorage for secure client-side data

Role-Based Access

Role verification for admin and creator features
public string HashPassword(string password)
{
    var salt = RandomNumberGenerator.GetBytes(Pbkdf2SaltSize);
    var hash = Rfc2898DeriveBytes.Pbkdf2(
        password,
        salt,
        Pbkdf2Iterations,
        HashAlgorithmName.SHA256,
        Pbkdf2KeySize);
    
    return $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}";
}

public bool VerifyPassword(string password, string passwordHash)
{
    var parts = passwordHash.Split(':');
    if (parts.Length != 2) return false;
    
    var salt = Convert.FromBase64String(parts[0]);
    var storedHash = Convert.FromBase64String(parts[1]);
    
    var computed = Rfc2898DeriveBytes.Pbkdf2(
        password,
        salt,
        Pbkdf2Iterations,
        HashAlgorithmName.SHA256,
        Pbkdf2KeySize);
    
    return CryptographicOperations.FixedTimeEquals(storedHash, computed);
}

External Integrations

AndanDo integrates with several external services:

Payment Processing

  • Service: PaypalService with HttpClient factory
  • Configuration: Sandbox/production mode, client credentials
  • Implementation: REST API integration for order creation and capture
  • Location: Services/Paypal/PaypalService.cs

Email Notifications

  • Library: MailKit for SMTP
  • Features: HTML emails, password reset, booking confirmations
  • Configuration: SMTP host, port, TLS, credentials
  • Dispatcher: Singleton MailNotificationDispatcher for queued sending

Geolocation

  • Search: Nominatim API for address autocomplete
  • Maps: Google Maps JavaScript API for tour locations
  • Features: Geolocation with normalized search (removes accents/spaces)

Currency Conversion

  • Service: CurrencyConversionService with HttpClient
  • Purpose: Real-time exchange rates for international pricing
  • Location: Services/Utility/CurrencyConversionService.cs

Middleware Pipeline

The ASP.NET Core middleware pipeline is configured in Program.cs:47-67:
Program.cs
var app = builder.Build();

// Error handling
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

// Status code pages
app.UseStatusCodePagesWithReExecute("/not-found", 
    createScopeForStatusCodePages: true);

// Security & static files
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();

// Authentication
app.UseAuthentication();
app.UseAuthorization();

// Blazor routing
app.MapStaticAssets();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();
The pipeline order is critical: authentication must come before authorization, and antiforgery must be enabled for Blazor Server to protect against CSRF attacks.

State Management

AndanDo uses multiple state management strategies:

UserSession

Scoped service storing current user data during the SignalR connection

ProtectedLocalStorage

Encrypted browser storage for persisting JWT and session data

Component State

Local component state with @bind directives

Cascading Parameters

CascadingAuthenticationState for auth state propagation

Real-Time Communication

Blazor Server uses SignalR for real-time server-to-client communication:
  • Connection: Persistent WebSocket or long-polling connection
  • Render Mode: InteractiveServer on components
  • Reconnection: Built-in reconnection UI with <ReconnectModal />
  • Preloader: Custom loading screen during initial connection
Blazor Server’s SignalR connection means the application state lives on the server. This reduces client-side complexity but requires careful consideration of server resources and connection handling.

Performance Considerations

Optimization Strategies

  1. Scoped Services: One instance per connection reduces memory overhead
  2. Async/Await: All I/O operations are asynchronous
  3. Stored Procedures: Optimized database queries
  4. Static Assets: Mapped with fingerprinting for cache efficiency
  5. Image Uploads: Stored locally in wwwroot/uploads/

Scalability Notes

Blazor Server maintains a SignalR connection per user. For high-traffic scenarios, consider:
  • Sticky sessions for load balancing
  • Azure SignalR Service for connection offloading
  • Connection limits and timeout configuration
  • Monitoring connection counts and memory usage

Configuration Management

Configuration is managed through appsettings.json and environment-specific files:
appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=...;Database=AndandoDB;..."
  },
  "Jwt": {
    "Issuer": "AndanDo",
    "Audience": "AndanDo",
    "SecretKey": "...",
    "ExpirationMinutes": 120
  },
  "Smtp": {
    "Host": "smtp.gmail.com",
    "Port": 587,
    "UseStartTls": true
  },
  "PayPal": {
    "Mode": "sandbox",
    "ClientId": "...",
    "BaseUrl": "https://api-m.sandbox.paypal.com"
  }
}
Configuration sections are bound to strongly-typed options classes using builder.Services.Configure<T>() for type safety and IntelliSense support.

Next Steps

Tech Stack

Explore the technologies and NuGet packages used

Project Structure

Deep dive into code organization and file structure

Database Schema

Learn about the SQL Server database design

API Integration

External API integration patterns

Build docs developers (and LLMs) love