DaemonProxyGenerators 1.0.0

dotnet add package DaemonProxyGenerators --version 1.0.0
                    
NuGet\Install-Package DaemonProxyGenerators -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DaemonProxyGenerators" Version="1.0.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DaemonProxyGenerators" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="DaemonProxyGenerators">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DaemonProxyGenerators --version 1.0.0
                    
#r "nuget: DaemonProxyGenerators, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DaemonProxyGenerators@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DaemonProxyGenerators&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=DaemonProxyGenerators&version=1.0.0
                    
Install as a Cake Tool

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:

// 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:

  1. SerialPortDaemonHub - SignalR Hub with methods like:

    • Task<int> GetBaudRateAsync()
    • Task SetBaudRateAsync(int value)
    • Task OpenAsync()
    • Task CloseAsync()
    • Task EnableDataReceivedAsync()
  2. ISerialPortDaemonClient - Interface for server-to-client callbacks:

    • Task OnDataReceived(byte[] data)
    • Task OnError(string message)
    • Task OnStateChanged(string state)
  3. SerialPortDaemonClient - Full-featured remote client

  4. 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

  1. Clean and Rebuild: dotnet clean && dotnet build
  2. Check .csproj: Ensure package is referenced correctly
  3. Restart IDE: Visual Studio may need restart to detect generators
  4. 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

  1. Close and reopen the file
  2. Rebuild the project
  3. Restart Visual Studio
  4. Clear VS cache: Delete .vs folder

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:

  1. SerialPortProxy - Wrapping System.IO.Ports.SerialPort
  2. System.IO.Ports.SerialPort.Daemon - ASP.NET Core daemon service
  3. Example1.TransparentProxy - WPF client using transparent proxy pattern
  4. Example2.DirectClient - WPF client using direct SignalR client

Package Contents

This package includes both generators as Roslyn analyzers:

  • ProxyGenerator.dll - Located in analyzers/dotnet/cs
  • DaemonGenerator.dll - Located in analyzers/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

There are no supported framework assets in this package.

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