Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
366eb27
chore(deps): update dependencies to latest versions
engineering87 May 24, 2025
4e98134
chore(ci): explicitly set read permission for GitHub Action
engineering87 May 25, 2025
66120f7
chore(deps): update dependencies to latest versions
engineering87 Jun 6, 2025
7f161fd
Merge branch 'develop' of https://github.com/engineering87/WART into …
engineering87 Jun 6, 2025
36bf999
fix(logging): sanitize structured logging to prevent log injection
engineering87 Jun 16, 2025
ee76e32
fix(logging): sanitize structured logging to prevent log injection
engineering87 Jun 16, 2025
d3ff4df
chore(deps): update dependencies to latest versions
engineering87 Jun 25, 2025
c79b78d
chore(deps): update dependencies to latest versions
engineering87 Jul 13, 2025
54890f0
docs: update README to include GitHub Sponsors badge and sponsorship …
engineering87 Aug 2, 2025
fac6df3
chore(deps): update dependencies to latest versions
engineering87 Aug 10, 2025
cbd9f0a
Merge branch 'develop' of https://github.com/engineering87/WART into …
engineering87 Aug 10, 2025
4b154f8
refactor(controller): use request snapshot from HttpContext.Items in …
engineering87 Aug 15, 2025
caad3ad
refactor(worker): improve WartEventWorker robustness and clarity
engineering87 Aug 15, 2025
fb6d3ab
refactor(middleware): fix pipeline order and consolidate endpoint map…
engineering87 Aug 15, 2025
afefa7e
perf(middleware): enable response compression and standardize pipelin…
engineering87 Aug 15, 2025
6d5854b
feat(wart-event): unify timestamps
engineering87 Aug 17, 2025
1ae1dc6
refactor(hubs,worker): partition connection tracking per hub and some…
engineering87 Aug 18, 2025
ba724ec
docs(readme): improve documentation
engineering87 Aug 23, 2025
dd4ac22
chore(core): upgrade to .NET 9 and apply various fixes and improvements
engineering87 Aug 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/commit-lint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Commit Lint

permissions:
contents: read
pull-requests: write

on:
pull_request:
branches:
Expand Down
166 changes: 91 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,57 @@
![NuGet Downloads](https://img.shields.io/nuget/dt/WART-Core)
[![issues - wart](https://img.shields.io/github/issues/engineering87/WART)](https://github.com/engineering87/WART/issues)
[![stars - wart](https://img.shields.io/github/stars/engineering87/WART?style=social)](https://github.com/engineering87/WART)
[![Sponsor me](https://img.shields.io/badge/Sponsor-❤-pink)](https://github.com/sponsors/engineering87)

<img src="https://github.com/engineering87/WART/blob/develop/wart_logo.jpg" width="300">

WART is a C# .NET library that enables you to extend any Web API controller and forward incoming calls directly to a SignalR hub. This hub then broadcasts notifications containing detailed information about the calls, including both the request and the response. Additionally, WART supports JWT authentication for secure communication with SignalR.

## Features
WART is a lightweight C# .NET library that extends your Web API controllers to forward incoming calls directly to a SignalR Hub.
The Hub broadcasts rich, structured events containing request and response details in **real-time**.
Supports **JWT** and **Cookie Authentication** for secure communication.

## 📑 Table of Contents
- [Features](#-features)
- [Installation](#-installation)
- [How It Works](#️-how-it-works)
- [Usage](#-usage)
- [Basic Setup](#basic-setup)
- [Using JWT Authentication](#using-jwt-authentication)
- [Custom Hub Names](#custom-hub-names)
- [Multiple Hubs](#multiple-hubs)
- [Client Example](#client-example)
- [Supported Authentication Modes](#-supported-authentication-modes)
- [Excluding APIs from Event Propagation](#-excluding-apis-from-event-propagation)
- [Group-based Event Dispatching](#-group-based-event-dispatching)
- [NuGet](#-nuget)
- [Contributing](#-contributing)
- [License](#-license)
- [Contact](#-contact)

## ✨ Features
- Converts REST API calls into SignalR events, enabling real-time communication.
- Provides controllers (`WartController`, `WartControllerJwt`, `WartControllerCookie`) for automatic SignalR event broadcasting.
- Supports JWT authentication for SignalR hub connections.
- Allows API exclusion from event broadcasting with `[ExcludeWart]` attribute.
- Enables group-specific event dispatching with `[GroupWart("group_name")]`.
- Configurable middleware (`AddWartMiddleware`) for flexible integration.

## Installation
You can install the library via the NuGet package manager with the following command:
## 📦 Installation
Install from **NuGet**

```bash
dotnet add package WART-Core
```

### How it works
WART implements a custom controller which overrides the `OnActionExecuting` and `OnActionExecuted` methods to retrieve the request and the response and encapsulates them in a **WartEvent** object which will be sent via SignalR on the **WartHub**.

### How to use it
### ⚙️ How it works
WART overrides `OnActionExecuting` and `OnActionExecuted` in a custom base controller.
For every API request/response:
1) Captures request and response data.
2) Wraps them in a `WartEvent`.
3) Publishes it through a SignalR Hub to all connected clients.

To use the WART library, each WebApi controller must extend the **WartController** controller:
## 🚀 Usage
### Basic Setup
Extend your API controllers from `WartController`:

```csharp
using WART_Core.Controllers;
Expand All @@ -39,104 +64,99 @@ using WART_Core.Hubs;
[ApiController]
[Route("api/[controller]")]
public class TestController : WartController
```

each controller must implement the following constructor, for example:

```csharp
public TestController(IHubContext<WartHub> messageHubContext,
ILogger<WartController> logger) : base(messageHubContext, logger)
{
public TestController(IHubContext<WartHub> hubContext, ILogger<WartController> logger)
: base(hubContext, logger) { }
}
```

WART support JWT bearer authentication on SignalR hub, if you want to use JWT authentication use the following controller extension:

```csharp
using WART_Core.Controllers;
using WART_Core.Hubs;

[ApiController]
[Route("api/[controller]")]
public class TestController : WartControllerJwt
```

You also need to enable SignalR in the WebAPI solution and map the **WartHub**.
To do this, add the following configurations in the Startup.cs class:
Register WART in `Startup.cs`:

```csharp
using WART_Core.Middleware;
```

In the ConfigureServices section add following:
public void ConfigureServices(IServiceCollection services)
{
services.AddWartMiddleware(); // No authentication
}

```csharp
services.AddWartMiddleware();
public void Configure(IApplicationBuilder app)
{
app.UseWartMiddleware();
}
```

or by specifying JWT authentication:

### Using JWT Authentication

```csharp
services.AddWartMiddleware(hubType:HubType.JwtAuthentication, tokenKey:"password_here");
services.AddWartMiddleware(hubType: HubType.JwtAuthentication, tokenKey: "your_secret_key");
app.UseWartMiddleware(HubType.JwtAuthentication);
```

In the Configure section add the following:
Extend from `WartControllerJwt`:

```csharp
app.UseWartMiddleware();
public class TestController : WartControllerJwt
{
public TestController(IHubContext<WartHubJwt> hubContext, ILogger<WartControllerJwt> logger)
: base(hubContext, logger) { }
}
```

or by specifying JWT authentication:
### Custom Hub Names
You can specify custom hub routes:

```csharp
app.UseWartMiddleware(HubType.JwtAuthentication);
app.UseWartMiddleware("customhub");
```

Alternatively, it is possible to specify a custom hub name:
### Multiple Hubs
You can configure multiple hubs at once by passing a list of hub names:

```csharp
app.UseWartMiddleware("hubname");
var hubs = new[] { "orders", "products", "notifications" };

app.UseWartMiddleware(hubs);
```

at this point it will be sufficient to connect via SignalR to the WartHub to receive notifications in real time of any call on the controller endpoints.
For example:
This is useful for separating traffic by domain.

### Client Example
#### Without authentication:

```csharp
var hubConnection = new HubConnectionBuilder()
.WithUrl("http://localhost:52086/warthub")
.WithUrl("http://localhost:5000/warthub")
.Build();
hubConnection.On<string>("Send", (data) =>

hubConnection.On<string>("Send", data =>
{
// data is the WartEvent JSON
// 'data' is a WartEvent JSON
});

await hubConnection.StartAsync();
```

or with JWT authentication:
#### With JWT authentication:

```csharp
var hubConnection = new HubConnectionBuilder()
.WithUrl($"http://localhost:51392/warthub", options =>
.WithUrl("http://localhost:5000/warthub", options =>
{
options.SkipNegotiation = true;
options.Transports = HttpTransportType.WebSockets;
options.AccessTokenProvider = () => Task.FromResult(GenerateToken());
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string>("Send", (data) =>

hubConnection.On<string>("Send", data =>
{
// data is the WartEvent JSON
// Handle WartEvent JSON
});
```

In the source code you can find a simple test client and WebApi project.

## Supported Authentication Modes
await hubConnection.StartAsync();
```

The project supports three authentication modes for accessing the SignalR Hub:
## 🔐 Supported Authentication Modes

| Mode | Description | Hub Class | Required Middleware |
|--------------------------|---------------------------------------------------------------------------|----------------------|---------------------------|
Expand All @@ -146,7 +166,7 @@ The project supports three authentication modes for accessing the SignalR Hub:

> ⚙️ Authentication mode is selected through the `HubType` configuration in the application startup.

### Excluding APIs from Event Propagation
### 🚫 Excluding APIs from Event Propagation
There might be scenarios where you want to exclude specific APIs from propagating events to connected clients. This can be particularly useful when certain endpoints should not trigger updates, notifications, or other real-time messages through SignalR. To achieve this, you can use a custom filter called `ExcludeWartAttribute`. By decorating the desired API endpoints with this attribute, you can prevent them from being included in the SignalR event propagation logic, for example:

```csharp
Expand All @@ -163,7 +183,7 @@ public ActionResult<TestEntity> Get(int id)
}
```

### SignalR Event Dispatching for Specific Groups
### 👥 Group-based Event Dispatching
WART enables sending API events to specific groups in SignalR by specifying the group name in the query string. This approach allows for flexible and targeted event broadcasting, ensuring that only the intended group of clients receives the event.
By decorating an API method with `[GroupWart("group_name")]`, it is possible to specify the SignalR group name to which the dispatch of specific events for that API is restricted. This ensures that only the clients subscribed to the specified group ("SampleGroupName") will receive the related events, allowing for targeted, group-based communication in a SignalR environment.

Expand All @@ -179,24 +199,20 @@ public ActionResult<TestEntity> Post([FromBody] TestEntity entity)

By appending `?WartGroup=group_name` to the URL, the library enables dispatching events from individual APIs to a specific SignalR group, identified by `group_name`. This allows for granular control over which clients receive the event, leveraging SignalR’s built-in group functionality.

### NuGet

The library is available on NuGet packetmanager.

https://www.nuget.org/packages/WART-Core/

### Contributing
Thank you for considering to help out with the source code!
If you'd like to contribute, please fork, fix, commit and send a pull request for the maintainers to review and merge into the main code base.
### 📦 NuGet
The library is available on [NuGet](https://www.nuget.org/packages/WART-Core/).

**Getting started with Git and GitHub**
### 🤝 Contributing
Contributions are welcome!
Steps to get started:

* [Setting up Git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git)
* [Fork the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo)
* [Open an issue](https://github.com/engineering87/WART/issues) if you encounter a bug or have a suggestion for improvements/features
* Submit a Pull Request.

### Licensee
### 📄 License
WART source code is available under MIT License, see license in the source.

### Contact
### 📬 Contact
Please contact at francesco.delre[at]protonmail.com for any details.
8 changes: 4 additions & 4 deletions src/WART-Client/WART-Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.5" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.10.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.8" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// (c) 2025 Francesco Del Re <francesco.delre.87@gmail.com>
// This code is licensed under MIT license (see LICENSE.txt for details)
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using WART_Core.Hubs;
using WART_Core.Services;

Expand Down Expand Up @@ -58,10 +61,10 @@ public static IServiceCollection AddCookieMiddleware(
});

// Register WART event queue service
services.AddSingleton<WartEventQueueService>();
services.TryAddSingleton<WartEventQueueService>();

// Register the WART event worker for the cookie-authenticated hub
services.AddHostedService<WartEventWorker<WartHubCookie>>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, WartEventWorker<WartHubCookie>>());

// SignalR configuration
services.AddSignalR(options =>
Expand All @@ -75,9 +78,13 @@ public static IServiceCollection AddCookieMiddleware(
// Compression for SignalR WebSocket/Binary transport
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
opts.EnableForHttps = true;
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]);
opts.Providers.Add<BrotliCompressionProvider>();
opts.Providers.Add<GzipCompressionProvider>();
});
services.Configure<BrotliCompressionProviderOptions>(o => o.Level = CompressionLevel.Fastest);
services.Configure<GzipCompressionProviderOptions>(o => o.Level = CompressionLevel.Fastest);

return services;
}
Expand Down
Loading