# Building WebSessionForge: A Multi-Session WebView2 Browser & Proxy Testing Platform in .NET 8

**C# · .NET 8 · WPF · Microsoft Edge WebView2 · Proxy Management · Browser Testing**

I wanted to build something that would let me experiment with **multiple isolated browser sessions**, while also giving me control over the network environment behind each session.

That led to **WebSessionForge** — a Windows desktop application built with **C# and .NET 8** that combines WebView2 browser sessions, proxy management, proxy health monitoring, proxy rotation, failure recovery, and structured runtime logging.

**GitHub:** https://github.com/shagarithvik/WebSessionForge

* * *

# What Is WebSessionForge?

WebSessionForge is a **browser-session and network testing platform**.

It provides a central desktop interface for creating and managing multiple WebView2 sessions while independently managing their network assignments.

The core idea is:

```text
                 WebSessionForge
                       │
          ┌────────────┼────────────┐
          │            │            │
          ▼            ▼            ▼
      Session 1    Session 2    Session N
          │            │            │
          ▼            ▼            ▼
       WebView2      WebView2      WebView2
          │            │            │
          ▼            ▼            ▼
       Proxy A      Proxy B      Proxy N
```

The interesting engineering challenge isn't simply opening multiple browsers.

It is coordinating:

*   Browser lifecycle
    
*   Session isolation
    
*   Proxy allocation
    
*   Proxy health
    
*   Proxy rotation
    
*   Failure recovery
    
*   Concurrent operations
    
*   Runtime observability
    

* * *

# Why I Built It

Browser automation becomes complicated when network infrastructure is part of the test.

A session can fail because:

*   A proxy becomes unavailable
    
*   A provider returns malformed data
    
*   A connection times out
    
*   A browser session encounters an error
    
*   Multiple sessions compete for the same proxy
    
*   Network conditions change during execution
    

I wanted these responsibilities separated instead of putting everything into one large automation class.

The resulting architecture became:

```text
Browser Sessions
       │
       ├── Session lifecycle
       │
       ├── Proxy assignment
       │
       ├── Proxy health
       │
       ├── Failure recovery
       │
       └── Logging
```

* * *

# Technology Stack

| Component | Technology |
| --- | --- |
| Language | C# |
| Framework | .NET 8 |
| Desktop UI | WPF |
| Browser | Microsoft Edge WebView2 |
| Configuration | JSON |
| Proxy input | CSV / Provider endpoints |
| Logging | JSONL |
| Platform | Windows 10/11 |

* * *

# Architecture

The application is organized around several major components:

```text
                         ┌─────────────────────┐
                         │   WebSessionForge   │
                         │     WPF Dashboard   │
                         └──────────┬──────────┘
                                    │
              ┌─────────────────────┼─────────────────────┐
              │                     │                     │
              ▼                     ▼                     ▼
       Session Manager        Proxy Manager           Logger
              │                     │                     │
              │              ┌──────┴──────┐              │
              │              │             │              │
              ▼              ▼             ▼              ▼
        WebView2        CSV Import    Providers       JSONL Logs
        Sessions             │             │
              │              └──────┬──────┘
              │                     │
              └──────────────┬──────┘
                             ▼
                       Proxy Pool
```

The main principle is **separation of responsibilities**.

* * *

# 🔧 Concrete Implementation Walkthrough

This is where the architecture becomes practical.

The following walkthrough shows how the major pieces fit together conceptually in C#.

The exact class names and implementation details may evolve as the project develops; the snippets illustrate the intended implementation pattern.

* * *

## 1\. Define a Proxy Model

Everything starts with a common representation for a proxy.

Instead of passing strings throughout the application, represent a proxy as an object.

```csharp
public sealed class ProxyInfo
{
    public string Host { get; init; } = string.Empty;
    public int Port { get; init; }

    public string Scheme { get; init; } = "http";

    public string? Username { get; init; }
    public string? Password { get; init; }

    public string? Provider { get; init; }

    public TimeSpan? Latency { get; set; }

    public bool IsHealthy { get; set; }

    public int FailureCount { get; set; }
}
```

Now every subsystem can work with the same structure.

For example:

```text
CSV
 │
 ▼
ProxyInfo
 │
 ├── Health Checker
 ├── Proxy Pool
 ├── Session Manager
 └── Logger
```

This is much cleaner than passing raw strings between services.

* * *

# 2\. Normalize Proxy Input

Different proxy sources can produce different formats.

For example:

```text
http://host:8080
```

or:

```text
host,8080,http,user,password
```

The importer converts these into the same internal model.

Conceptually:

```csharp
ProxyInfo ParseProxy(string value)
{
    // Parse scheme, host, port and optional credentials.
    // Normalize the result into ProxyInfo.
}
```

The important design principle is:

> **Normalize at the boundary.**

Once a proxy enters the application, the rest of the system should not care where it came from.

* * *

# 3\. Build the Proxy Pool

The proxy manager maintains the available proxies.

Conceptually:

```csharp
private readonly List<ProxyInfo> _proxies = new();
```

Adding a proxy becomes:

```csharp
public void Add(ProxyInfo proxy)
{
    if (!_proxies.Any(p =>
        p.Host == proxy.Host &&
        p.Port == proxy.Port &&
        p.Scheme == proxy.Scheme))
    {
        _proxies.Add(proxy);
    }
}
```

This gives the application a central location for:

*   Deduplication
    
*   Health state
    
*   Assignment
    
*   Release
    
*   Replacement
    

* * *

# 4\. Health Check the Proxy

A proxy shouldn't automatically become available just because it was parsed successfully.

The health-check layer tests connectivity.

A simplified conceptual implementation is:

```csharp
public async Task<bool> CheckAsync(
    ProxyInfo proxy,
    CancellationToken cancellationToken)
{
    try
    {
        using var client = CreateClient(proxy);

        using var response = await client.GetAsync(
            TestEndpoint,
            cancellationToken);

        proxy.IsHealthy = response.IsSuccessStatusCode;

        return proxy.IsHealthy;
    }
    catch
    {
        proxy.IsHealthy = false;
        proxy.FailureCount++;

        return false;
    }
}
```

The actual implementation needs to account for the proxy protocol and appropriate timeout behavior.

The important flow is:

```text
Proxy
  │
  ▼
Connectivity Test
  │
 ┌┴─────────┐
 ▼          ▼
Healthy    Failed
 │          │
 ▼          ▼
Pool       Remove/Retry
```

* * *

# 5\. Reserve a Proxy for a Session

Once healthy proxies exist, sessions need to obtain them safely.

A simple conceptual abstraction is:

```csharp
public ProxyInfo? Acquire()
{
    lock (_sync)
    {
        var proxy = _proxies
            .FirstOrDefault(p =>
                p.IsHealthy &&
                !IsReserved(p));

        if (proxy == null)
            return null;

        Reserve(proxy);

        return proxy;
    }
}
```

The important part here is synchronization.

Without it, two sessions could perform:

```text
Session A → find Proxy A
Session B → find Proxy A
```

before either session marks it as reserved.

With synchronized allocation:

```text
Proxy Pool
    │
    ├── Proxy A → Session 1
    ├── Proxy B → Session 2
    └── Proxy C → Available
```

This becomes particularly important as the number of concurrent sessions increases.

* * *

# 6\. Create a WebView2 Session

A session manager can then create a browser environment for the assigned session.

Conceptually:

```csharp
public sealed class BrowserSession
{
    public string Id { get; }
    public ProxyInfo? Proxy { get; }

    public BrowserSession(
        string id,
        ProxyInfo? proxy)
    {
        Id = id;
        Proxy = proxy;
    }
}
```

The lifecycle becomes:

```text
Create Session
      │
      ▼
Acquire Proxy
      │
      ▼
Create WebView2 Environment
      │
      ▼
Initialize Browser
      │
      ▼
Navigate
```

WebView2 itself then becomes the browser execution layer, while the session manager controls the higher-level lifecycle.

* * *

# 7\. Keep Session State Separate

Each session should have an identity.

For example:

```csharp
var sessionId = $"session-{index}";
```

The session can then be associated with:

```text
Session ID
Proxy
Browser Environment
Status
Created Time
Log File
```

That makes it possible to answer questions like:

> Which proxy was assigned to Session 4 when the failure occurred?

This becomes extremely useful during debugging.

* * *

# 8\. Start Multiple Sessions

The dashboard can coordinate multiple sessions.

Conceptually:

```csharp
for (int i = 0; i < sessionCount; i++)
{
    var session = await sessionManager.CreateAsync(
        $"session-{i + 1}");

    sessions.Add(session);
}
```

In a production implementation, these operations need proper asynchronous lifecycle management rather than blindly creating everything at once.

The overall flow becomes:

```text
Dashboard
    │
    ▼
Session Manager
    │
    ├── Session 1
    ├── Session 2
    ├── Session 3
    └── Session N
```

* * *

# 9\. Handle Proxy Failure

Suppose Session 2 is currently using Proxy B.

A network operation fails.

The session manager can perform:

```text
Session 2
   │
   ▼
Network Failure
   │
   ▼
Mark Proxy B Failed
   │
   ▼
Release Proxy B
   │
   ▼
Acquire Replacement
   │
   ▼
Proxy C
```

Conceptually:

```csharp
public async Task<bool> ReplaceProxyAsync(
    BrowserSession session)
{
    Release(session.Proxy);

    var replacement = Acquire();

    if (replacement == null)
        return false;

    session.SetProxy(replacement);

    return true;
}
```

The exact WebView2 reconfiguration strategy depends on the session lifecycle and browser environment implementation.

The important architectural idea is that **proxy failure is handled by the session/proxy layers rather than the UI**.

* * *

# 10\. Implement Rotation

Rotation can be represented as an asynchronous background operation.

Conceptually:

```csharp
while (!cancellationToken.IsCancellationRequested)
{
    await Task.Delay(rotationInterval, cancellationToken);

    await RotateSessionAsync(session);
}
```

The rotation process is:

```text
Wait
 │
 ▼
Rotation Due
 │
 ▼
Release Current Proxy
 │
 ▼
Acquire Replacement
 │
 ▼
Update Session
 │
 ▼
Log Event
 │
 ▼
Continue
```

A jitter value can be applied around the configured interval when the testing scenario requires variation.

* * *

# 11\. Structured Logging

Instead of writing arbitrary strings:

```text
"proxy failed"
```

the application can record structured events.

Conceptually:

```csharp
await logger.WriteAsync(new
{
    Timestamp = DateTimeOffset.UtcNow,
    Event = "ProxyFailure",
    SessionId = session.Id,
    Proxy = proxy.Host,
    Port = proxy.Port
});
```

The result can be stored as JSONL:

```json
{
  "timestamp": "2026-10-02T12:00:00Z",
  "event": "ProxyFailure",
  "sessionId": "session-2",
  "proxy": "proxy.example",
  "port": 8080
}
```

Now logs can be filtered programmatically.

For example:

```text
All failures
All events for Session 2
All proxy rotations
All health-check failures
```

* * *

# 12\. Connect the UI

The WPF dashboard acts as the orchestration layer.

Conceptually:

```text
MainWindow
    │
    ├── Start
    │      ↓
    │   SessionManager
    │
    ├── Stop
    │      ↓
    │   SessionManager
    │
    ├── Proxy Status
    │      ↓
    │   ProxyManager
    │
    ├── Browser Matrix
    │      ↓
    │   Active Sessions
    │
    └── Logs
           ↓
        Logger
```

The UI should display state rather than contain the business logic itself.

That distinction becomes increasingly important as the project grows.

* * *

# 13\. Configuration-Driven Behavior

Runtime settings belong in configuration rather than hardcoded constants.

For example:

```json
{
  "SessionCount": 6,
  "RotationMinutes": 5,
  "RotationJitterSeconds": 30,
  "ProxyHealthTimeoutSeconds": 10,
  "ProviderRefreshMinutes": 5,
  "ProviderUrls": [],
  "Headless": false
}
```

The application can load this during startup:

```csharp
var json = await File.ReadAllTextAsync(
    "config/config.json");

var config = JsonSerializer.Deserialize<AppConfig>(json);
```

The result is passed to the relevant services.

```text
Configuration
      │
      ├── Session Manager
      ├── Proxy Manager
      ├── Health Checker
      └── Provider Manager
```

This makes behavior reproducible and easier to modify.

* * *

# 14\. Complete Runtime Flow

Putting everything together:

```text
                    START
                      │
                      ▼
               Load Configuration
                      │
                      ▼
              Load Proxy Sources
                      │
                      ▼
              Normalize Proxies
                      │
                      ▼
                Health Checks
                      │
             ┌────────┴────────┐
             ▼                 ▼
          Healthy             Failed
             │                 │
             ▼                 ▼
        Proxy Pool          Excluded
             │
             ▼
       Create Session
             │
             ▼
       Acquire Proxy
             │
             ▼
       Create WebView2
             │
             ▼
          Navigate
             │
             ▼
           Monitor
             │
       ┌─────┴──────┐
       ▼            ▼
    Healthy       Failure
       │            │
       │            ▼
       │       Mark Proxy Failed
       │            │
       │            ▼
       │       Acquire Replacement
       │
       ▼
   Rotation Due
       │
       ▼
   Rotate Proxy
       │
       ▼
     Continue
```

This is the core of WebSessionForge.

* * *

# Proxy Provider Pipeline

Provider integration follows the same architecture.

```text
Provider URL
     │
     ▼
HTTP Request
     │
     ▼
Response
     │
     ▼
Format Detection
     │
 ┌───┼──────────┐
 ▼   ▼          ▼
JSON Text     Object
 │    │          │
 └────┴──────────┘
          │
          ▼
      Proxy Parser
          │
          ▼
      ProxyInfo[]
          │
          ▼
      Proxy Manager
```

The provider layer therefore doesn't need to know anything about browser sessions.

It simply supplies normalized proxy objects.

* * *

# Failure Isolation

One of the most important architectural goals is preventing one failure from cascading into everything else.

For example:

```text
Proxy A fails
     │
     ▼
Session 1 affected
     │
     ├── Proxy A marked failed
     ├── Proxy A removed
     └── Replacement requested
```

Instead of:

```text
Proxy A fails
     │
     ▼
Entire application crashes
```

The system treats failures as local events whenever possible.

* * *

# Browser Matrix Implementation Concept

The browser matrix is essentially a presentation layer over the active sessions.

Conceptually:

```text
Observable Session Collection
          │
          ▼
      Browser Matrix
          │
    ┌─────┼─────┐
    ▼     ▼     ▼
 Session Session Session
    1      2      3
```

Each visual container can host the corresponding WebView2 control.

This provides an easy way to inspect the state of multiple sessions without losing the central dashboard.

* * *

# Observability Architecture

The logging architecture can be visualized as:

```text
Session Manager ──────┐
                      │
Proxy Manager ────────┤
                      ├──► Logger ───► JSONL
Health Checker ───────┤
                      │
Provider Manager ─────┘
```

This is useful because logging remains centralized.

A session doesn't need to know where logs are stored.

It simply emits an event.

The logger handles persistence.

* * *

# Performance Considerations

Multiple WebView2 instances are considerably more expensive than simple HTTP clients.

Resource consumption depends on:

```text
Session Count
      +
Page Complexity
      +
JavaScript
      +
Network Activity
      +
Proxy Latency
      +
CPU
      +
RAM
```

For that reason, the recommended development workflow is:

```text
1 session
   ↓
2 sessions
   ↓
4 sessions
   ↓
6 sessions
   ↓
Scale gradually
```

This makes it easier to identify the point where resource consumption becomes problematic.

* * *

# Project Structure

The project follows a modular layout:

```text
WebSessionForge/
│
├── Models/
│
├── Providers/
│
├── Services/
│
├── config/
│
├── logs/
│
├── App.xaml
├── App.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── BrowserWindow.xaml
├── BrowserWindow.xaml.cs
├── WebSessionForge.csproj
├── DOCUMENTATION.md
├── README.md
└── LICENSE
```

The intended responsibility boundaries are:

| Directory | Responsibility |
| --- | --- |
| `Models/` | Domain/application models |
| `Providers/` | External proxy sources |
| `Services/` | Session, proxy, health and logging logic |
| `config/` | Runtime configuration |
| `logs/` | Runtime event data |
| `*.xaml` | WPF interface |

* * *

# Configuration

The main configuration file is:

```text
config/config.json
```

Example:

```json
{
  "SessionCount": 6,
  "RotationMinutes": 5,
  "RotationJitterSeconds": 30,
  "ProxyHealthTimeoutSeconds": 10,
  "ProviderRefreshMinutes": 5,
  "ProviderUrls": [],
  "Headless": false
}
```

Important options:

| Setting | Purpose |
| --- | --- |
| `SessionCount` | Number of browser sessions |
| `RotationMinutes` | Proxy rotation interval |
| `RotationJitterSeconds` | Rotation variation |
| `ProxyHealthTimeoutSeconds` | Health-check timeout |
| `ProviderRefreshMinutes` | Provider refresh |
| `ProviderUrls` | Provider endpoints |
| `Headless` | Headless configuration |

* * *

# Logging

WebSessionForge uses JSON Lines.

Application logs:

```text
logs/application-YYYY-MM-DD.jsonl
```

Session logs:

```text
logs/session-1-YYYY-MM-DD.jsonl
logs/session-2-YYYY-MM-DD.jsonl
logs/session-3-YYYY-MM-DD.jsonl
```

Events can include:

*   Session creation
    
*   Session termination
    
*   Proxy assignment
    
*   Proxy rotation
    
*   Health checks
    
*   Failures
    
*   Retries
    
*   Recovery
    
*   Runtime errors
    

Structured logs make concurrent browser testing much easier to investigate.

* * *

# Running the Project

Requirements:

*   Windows 10/11
    
*   .NET 8 SDK
    
*   Microsoft Edge WebView2 Runtime
    

Clone:

```bash
git clone https://github.com/shagarithvik/WebSessionForge.git
```

Build:

```bash
cd WebSessionForge
dotnet restore
dotnet build
```

Run:

```bash
dotnet run
```

Publish:

```bash
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None
```

* * *

# Testing Scenarios

WebSessionForge is useful for controlled testing of:

## Session Isolation

Verify independent browser state.

## Proxy Reliability

Observe behavior when a network endpoint becomes unavailable.

## Proxy Rotation

Test session behavior when network assignments change.

## Failure Recovery

Verify replacement and recovery workflows.

## Provider Reliability

Test malformed or unavailable provider responses.

## Observability

Reconstruct a test run from structured JSONL events.

* * *

# What I Learned

## Browser sessions are expensive

A browser environment is much heavier than an HTTP client.

## Network failures are normal

Proxy infrastructure should be designed around failure.

## Health checks are not guarantees

A successful check only tells us that a proxy worked at that particular moment.

## Logging matters

Concurrent systems become difficult to debug without structured events.

## Separation matters

Keeping the UI, session manager, proxy manager, providers, and logger independent makes the project easier to maintain.

* * *

# Responsible Use

WebSessionForge is intended for **authorized testing and experimentation**.

Use it with systems and infrastructure where you have permission.

It should not be used to:

*   Manipulate platform metrics
    
*   Generate artificial engagement
    
*   Circumvent access controls
    
*   Evade anti-bot systems
    
*   Bypass rate limits
    
*   Access systems without authorization
    
*   Abuse third-party infrastructure
    

The project is intended as a **browser-session and QA/testing platform**.

* * *

# Roadmap

### Architecture

*   \[ \] Further MVVM adoption
    
*   \[ \] Dependency injection
    
*   \[ \] More interfaces
    
*   \[ \] Improved testability
    

### Proxy System

*   \[ \] Provider health scoring
    
*   \[ \] Advanced proxy statistics
    
*   \[ \] Better failure classification
    
*   \[ \] Improved validation
    

### Browser Sessions

*   \[ \] Improved WebView2 lifecycle
    
*   \[ \] Configurable profiles
    
*   \[ \] Session telemetry
    
*   \[ \] Performance metrics
    

### Testing

*   \[ \] Unit tests
    
*   \[ \] Integration tests
    
*   \[ \] Mock proxy provider
    
*   \[ \] Local test infrastructure
    

### Developer Experience

*   \[ \] CI/CD
    
*   \[ \] Automated releases
    
*   \[ \] Configuration validation
    
*   \[ \] Exportable reports
    

* * *

# Why WebSessionForge?

The name reflects the direction of the project.

**WebSession** represents the primary unit of the platform.

**Forge** represents creating and managing controlled browser-session environments.

The project started around proxy-backed browser experimentation, but the architecture is broader:

```text
Browser Sessions
       +
Network Conditions
       +
Testing
       +
Observability
       +
Automation
```

* * *

# Final Thoughts

WebSessionForge started as an experiment around multiple browser sessions and proxy management.

It has evolved into a small platform for studying how:

*   Browser environments
    
*   Network conditions
    
*   Failure recovery
    
*   Concurrency
    
*   Observability
    

interact in a real desktop application.

The most important lesson has been simple:

> **Reliable automation isn't just about making something work. It's about making failures understandable and recoverable.**

That's the direction I want to continue taking WebSessionForge.

* * *

# 🔗 Project

**GitHub:** https://github.com/shagarithvik/WebSessionForge

**Author:** **Rithvik Shaga**

**Stack:** C# · .NET 8 · WPF · WebView2

**License:** MIT

* * *

## ⭐ Get Involved

The repository is open source.

Feedback and contributions around:

*   Architecture
    
*   WebView2
    
*   Session management
    
*   Proxy infrastructure
    
*   Reliability
    
*   Performance
    
*   Testing
    
*   Observability
    

are welcome.

**GitHub:** https://github.com/shagarithvik/WebSessionForge

* * *

### Built with C# • .NET 8 • WPF • WebView2
