DaemonProxyGenerators 1.0.0
dotnet add package DaemonProxyGenerators --version 1.0.0
NuGet\Install-Package DaemonProxyGenerators -Version 1.0.0
<PackageReference Include="DaemonProxyGenerators" Version="1.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="DaemonProxyGenerators" Version="1.0.0" />
<PackageReference Include="DaemonProxyGenerators"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add DaemonProxyGenerators --version 1.0.0
#r "nuget: DaemonProxyGenerators, 1.0.0"
#:package DaemonProxyGenerators@1.0.0
#addin nuget:?package=DaemonProxyGenerators&version=1.0.0
#tool nuget:?package=DaemonProxyGenerators&version=1.0.0
DaemonProxyGenerators
Automatic code generation for proxy patterns and SignalR daemon services - Write less boilerplate, focus on your business logic!
This package provides two powerful Roslyn source generators that create code at compile-time with zero runtime dependencies.
?? Quick Start
Installation
dotnet add package DaemonProxyGenerators
That's it! The generators automatically activate and start generating code during compilation.
?? What's Included
1. ProxyGenerator - Transparent Proxy Pattern Made Easy
Automatically wraps any type with a proxy class that delegates all members. Perfect for:
- Adding cross-cutting concerns (logging, validation, caching)
- Wrapping third-party libraries you can't modify
- Creating testable abstractions over concrete types
- Implementing the Decorator pattern without boilerplate
What it generates:
- Constructor accepting the wrapped instance
- All public properties (with get/set delegation)
- All public methods (with proper parameter handling including ref/out)
- All public events (with add/remove delegation)
- IDisposable implementation
- No runtime dependencies (attribute is compile-time only)
2. DaemonGenerator - SignalR Daemon Services Without the Plumbing
Transforms your proxy classes into full-featured remote services over SignalR. Perfect for:
- Remote hardware access (serial ports, USB devices, sensors)
- Background service communication
- Real-time data streaming
- Client-server architectures
What it generates:
- Complete SignalR Hub with async methods
- Strongly-typed client interface for callbacks
- Remote client proxy with connection management
- DI registration and endpoint mapping extensions
- Event streaming support
- Special handling for Stream properties
?? Tutorial: ProxyGenerator
Step 1: Create Your Proxy Class
Create a partial class and mark it with [ProxyGenerator(typeof(YourType))]:
using System.IO.Ports;
using ProxyGenerator;
[ProxyGenerator(typeof(SerialPort))]
public partial class SerialPortProxy
{
// The generator fills in all the implementation!
// No need to write any code here.
}
Step 2: Use Your Proxy
The generator creates a constructor that accepts the wrapped instance:
// Create the actual instance
using var serialPort = new SerialPort("COM1", 9600);
// Wrap it with your proxy
using var proxy = new SerialPortProxy(serialPort);
// Use it exactly like the original
proxy.PortName = "COM1";
proxy.BaudRate = 115200;
proxy.DataBits = 8;
proxy.Open();
// Subscribe to events
proxy.DataReceived += (sender, e) => {
var data = new byte[proxy.BytesToRead];
proxy.Read(data, 0, data.Length);
Console.WriteLine($"Received: {BitConverter.ToString(data)}");
};
// Access the wrapped instance if needed
SerialPort original = proxy.Inner;
What Gets Generated
For the SerialPortProxy above, the generator creates:
public partial class SerialPortProxy : IDisposable
{
private readonly System.IO.Ports.SerialPort _inner;
public SerialPortProxy(System.IO.Ports.SerialPort inner)
{
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
}
public System.IO.Ports.SerialPort Inner => _inner;
// All properties
public string PortName
{
get => _inner.PortName;
set => _inner.PortName = value;
}
public int BaudRate
{
get => _inner.BaudRate;
set => _inner.BaudRate = value;
}
// All methods
public void Open() => _inner.Open();
public void Close() => _inner.Close();
public int Read(byte[] buffer, int offset, int count)
=> _inner.Read(buffer, offset, count);
// All events
public event System.IO.Ports.SerialDataReceivedEventHandler? DataReceived
{
add => _inner.DataReceived += value;
remove => _inner.DataReceived -= value;
}
// IDisposable
public void Dispose() { /* ... */ }
protected virtual void Dispose(bool disposing) { /* ... */ }
}
??? Tutorial: DaemonGenerator
The DaemonGenerator takes your proxy class and exposes it as a remote service over SignalR.
Step 1: Create the Daemon Class
using DaemonGenerator;
[DaemonGenerator(typeof(SerialPortProxy), Route = "/serialport")]
public partial class SerialPortDaemon
{
// The generator creates everything for SignalR!
}
Step 2: Setup the Server (ASP.NET Core)
In your Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Register the daemon with a factory method
builder.Services.AddSerialPortDaemon(sp =>
{
var port = new SerialPort("COM1", 9600);
return new SerialPortProxy(port);
});
var app = builder.Build();
// Map the SignalR hub
app.MapSerialPortDaemon(); // Uses default route: /serialport
app.Run();
Step 3: Use the Client
Two client options are generated:
Option A: Remote Client (Full-Featured)
// Create the remote client
var client = new SerialPortDaemonClient("http://localhost:5000/serialport");
// Connect
await client.ConnectAsync();
// Subscribe to events (just like local!)
client.DataReceived += (sender, data) =>
{
Console.WriteLine($"Received: {BitConverter.ToString(data)}");
};
// Configure the remote serial port
await client.SetBaudRateAsync(115200);
await client.SetPortNameAsync("COM1");
// Enable event notifications
await client.EnableDataReceivedAsync();
// Open the port remotely
await client.OpenAsync();
// Write data
var dataToSend = Encoding.UTF8.GetBytes("Hello Device!");
await client.WriteAsync(dataToSend);
// Cleanup
await client.DisconnectAsync();
await client.DisposeAsync();
Option B: Transparent Proxy (Client-Side Proxy Pattern)
using DaemonGenerator;
[DaemonClientGenerator(typeof(SerialPort))]
public partial class SerialPortRemoteProxy
{
// Creates a client-side proxy that looks and feels like SerialPort
// but works over SignalR behind the scenes
}
Usage:
var proxy = new SerialPortRemoteProxy("http://localhost:5000/serialport");
// Use INotifyPropertyChanged for UI binding (WPF/MAUI)
proxy.PropertyChanged += (s, e) =>
Console.WriteLine($"Property {e.PropertyName} changed");
await proxy.ConnectAsync();
// Use it like a local SerialPort!
await proxy.SetBaudRateAsync(115200);
proxy.BaudRate = await proxy.GetBaudRateAsync(); // Cached locally
await proxy.OpenAsync();
What Gets Generated
For SerialPortDaemon, the generator creates:
SerialPortDaemonHub - SignalR Hub with methods like:
Task<int> GetBaudRateAsync()Task SetBaudRateAsync(int value)Task OpenAsync()Task CloseAsync()Task EnableDataReceivedAsync()
ISerialPortDaemonClient - Interface for server-to-client callbacks:
Task OnDataReceived(byte[] data)Task OnError(string message)Task OnStateChanged(string state)
SerialPortDaemonClient - Full-featured remote client
Extension Methods - For easy DI setup:
AddSerialPortDaemon()MapSerialPortDaemon()
????? Real-World Example: Remote Serial Port Access
Here's a complete example showing both generators working together:
Server (Daemon Service)
// 1. Create the proxy
using ProxyGenerator;
[ProxyGenerator(typeof(SerialPort))]
public partial class SerialPortProxy { }
// 2. Create the daemon
using DaemonGenerator;
[DaemonGenerator(typeof(SerialPortProxy), Route = "/serialport")]
public partial class SerialPortDaemon { }
// 3. Setup in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSerialPortDaemon(sp =>
{
var port = new SerialPort("COM1")
{
BaudRate = 9600,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One
};
return new SerialPortProxy(port);
});
var app = builder.Build();
app.MapSerialPortDaemon();
app.Run();
Client (WPF/WinForms/Console Application)
using System;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// Connect to the daemon
var client = new SerialPortDaemonClient("http://localhost:5000/serialport");
// Setup event handler
client.DataReceived += (sender, data) =>
{
var message = Encoding.UTF8.GetString(data);
Console.WriteLine($"[RX] {message}");
};
client.ErrorOccurred += (sender, error) =>
{
Console.WriteLine($"[ERROR] {error}");
};
// Connect and configure
await client.ConnectAsync();
Console.WriteLine("Connected to serial port daemon");
await client.SetBaudRateAsync(115200);
await client.EnableDataReceivedAsync();
await client.OpenAsync();
Console.WriteLine("Serial port opened remotely");
// Send data
var message = "Hello Serial Device!";
var bytes = Encoding.UTF8.GetBytes(message);
await client.WriteAsync(bytes);
Console.WriteLine($"[TX] {message}");
// Wait for responses
await Task.Delay(5000);
// Cleanup
await client.CloseAsync();
await client.DisconnectAsync();
await client.DisposeAsync();
}
}
??? Key Features Explained
ProxyGenerator Features
| Feature | Description | Example |
|---|---|---|
| Property Proxying | All public properties are delegated | proxy.BaudRate = 115200 |
| Method Proxying | All public methods including ref/out params | proxy.Read(buffer, 0, count) |
| Event Proxying | All public events with add/remove | proxy.DataReceived += Handler |
| IDisposable | Automatic disposal of wrapped instance | using var proxy = new MyProxy(...) |
| Inner Access | Access wrapped instance via Inner property |
var original = proxy.Inner |
| Null Safety | Constructor validates non-null instance | ArgumentNullException if null |
DaemonGenerator Features
| Feature | Description | Example |
|---|---|---|
| Hub Generation | Full SignalR Hub implementation | SerialPortDaemonHub |
| Client Interface | Strongly-typed callbacks | ISerialPortDaemonClient |
| Remote Client | Full-featured client with connection management | SerialPortDaemonClient |
| Event Control | Enable/disable remote events | EnableDataReceivedAsync() |
| Static Methods | Proxy static methods (like GetPortNames()) |
GetPortNamesAsync() |
| Stream Support | Special handling for Stream properties | StreamDataAsync() |
| DI Integration | Extension methods for easy setup | AddSerialPortDaemon() |
?? Advanced Configuration
Custom Route
[DaemonGenerator(typeof(MyProxy), Route = "/api/mydevice")]
public partial class MyDaemon { }
Multiple Daemons in One Service
// Daemon 1
[DaemonGenerator(typeof(SerialPortProxy), Route = "/serial")]
public partial class SerialPortDaemon { }
// Daemon 2
[DaemonGenerator(typeof(UsbDeviceProxy), Route = "/usb")]
public partial class UsbDeviceDaemon { }
// Program.cs
builder.Services.AddSerialPortDaemon(/* ... */);
builder.Services.AddUsbDeviceDaemon(/* ... */);
app.MapSerialPortDaemon();
app.MapUsbDeviceDaemon();
Scoped vs Singleton Services
// Singleton (default) - One instance shared by all clients
builder.Services.AddSerialPortDaemon(sp => new SerialPortProxy(...));
// Scoped - New instance per client connection
builder.Services.AddScoped<SerialPortProxy>(sp =>
{
var port = new SerialPort("COM1");
return new SerialPortProxy(port);
});
builder.Services.AddSignalR();
?? Troubleshooting
Generator Not Running
- Clean and Rebuild:
dotnet clean && dotnet build - Check .csproj: Ensure package is referenced correctly
- Restart IDE: Visual Studio may need restart to detect generators
- Check Build Output: Look for generator diagnostics in Output window
Generated Files Not Visible
Generated files are hidden by default but exist in obj/ folder:
- Look in:
obj/Debug/net8.0/generated/ - Use Visual Studio's Solution Explorer "Show All Files"
- Or check
obj/directory directly
IntelliSense Not Working
- Close and reopen the file
- Rebuild the project
- Restart Visual Studio
- Clear VS cache: Delete
.vsfolder
SignalR Connection Issues
// Enable detailed logging
var client = new SerialPortDaemonClient("http://localhost:5000/serialport");
// Add logging to HubConnection (before ConnectAsync)
// Access via reflection or create custom client if needed
?? Requirements
| Component | Requirement |
|---|---|
| .NET Version | .NET Standard 2.0+ (generators)<br/>.NET 6.0+ (for consuming projects) |
| C# Version | C# 7.3+ (minimum)<br/>C# 10+ (recommended for best experience) |
| IDE | Visual Studio 2022+, VS Code, Rider 2022+ |
| DaemonGenerator | ASP.NET Core 6.0+ for SignalR features |
?? Learn More
Complete Examples in Repository
Visit the GitHub repository for complete working examples:
- SerialPortProxy - Wrapping System.IO.Ports.SerialPort
- System.IO.Ports.SerialPort.Daemon - ASP.NET Core daemon service
- Example1.TransparentProxy - WPF client using transparent proxy pattern
- Example2.DirectClient - WPF client using direct SignalR client
Package Contents
This package includes both generators as Roslyn analyzers:
ProxyGenerator.dll- Located inanalyzers/dotnet/csDaemonGenerator.dll- Located inanalyzers/dotnet/cs
Both execute at compile-time with zero runtime dependencies.
Documentation
?? License
MIT License - Free for commercial and non-commercial use.
?? Contributing
Contributions are welcome! Please visit the GitHub repository to:
- Report bugs
- Request features
- Submit pull requests
- Improve documentation
Made with ?? by Carsten Knop | GitHub
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 438 | 12/10/2025 |