NFoundation.Photino.NET.Extensions
1.0.0
See the version list below for details.
dotnet add package NFoundation.Photino.NET.Extensions --version 1.0.0
NuGet\Install-Package NFoundation.Photino.NET.Extensions -Version 1.0.0
<PackageReference Include="NFoundation.Photino.NET.Extensions" Version="1.0.0" />
<PackageVersion Include="NFoundation.Photino.NET.Extensions" Version="1.0.0" />
<PackageReference Include="NFoundation.Photino.NET.Extensions" />
paket add NFoundation.Photino.NET.Extensions --version 1.0.0
#r "nuget: NFoundation.Photino.NET.Extensions, 1.0.0"
#:package NFoundation.Photino.NET.Extensions@1.0.0
#addin nuget:?package=NFoundation.Photino.NET.Extensions&version=1.0.0
#tool nuget:?package=NFoundation.Photino.NET.Extensions&version=1.0.0
NFoundation.Photino.NET.Extensions
A powerful extension library for Photino.NET that adds typed messaging, automatic script injection, and enhanced logging capabilities.
Overview
NFoundation.Photino.NET.Extensions enhances the Photino.NET framework with a comprehensive set of utilities for building desktop applications with web UI. It provides a fluent API for setting up typed communication between .NET and JavaScript, automatic script injection, and seamless console logging integration.
Key Features
- 🚀 Typed Messaging System - Type-safe communication between .NET and JavaScript
- 📡 Request-Response Patterns - Async request handling with automatic response routing
- 📜 Automatic Script Injection - Embedded JavaScript library with auto-initialization
- 🪵 Console Logging Bridge - Forward JavaScript console messages to .NET ILogger
- 🔥 Hot Reload for Development - Automatic page refresh when source files change (DEBUG builds only)
- 🔍 Enhanced Photino Logging - Routes Photino log messages to ILogger instance, rather than the Console (not supported for AOT compiled apps)
Disclaimer
This project is an independent extension library for Photino.NET.
It is not affiliated with, endorsed by, or sponsored by the Photino.NET maintainers.
Photino.NET is a separate open-source project licensed under the Apache License 2.0.
Installation
Install the NuGet package:
dotnet add package NFoundation.Photino.NET.Extensions
Or via Package Manager Console:
Install-Package NFoundation.Photino.NET.Extensions
Prerequisites
- .NET 8.0 or later
- Photino.NET 3.2.3 or compatible version
Quick Start
Here's a minimal example to get you started:
using Microsoft.Extensions.Logging;
using Photino.NET;
using NFoundation.Photino.NET.Extensions;
// Set up logging
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger("MainWindow");
// Initialize Photino log patcher (optional but recommended - does not work for AOT compiled apps)
PhotinoWindowLogPatcher.Initialize();
var window = new PhotinoWindow()
.SetLogger(logger)
.SetTitle("My App")
.SetSize(new System.Drawing.Size(1200, 800))
// Register message handlers
.RegisterMessageHandler<string>("say-hello", (name) =>
{
logger.LogInformation("Hello from {Name}!", name);
})
// Register request handlers
.RegisterRequestHandler<UserRequest, UserResponse>("get-user", async (request) =>
{
// Your async logic here
return new UserResponse { Name = "John Doe" };
})
// Enable automatic script injection with console logging
.RegisterPhotinoScript(enablePhotinoDebugLogging: true, forwardConsoleMessagesToLogger: true)
// Load your HTML with hot reload support (automatically enabled in DEBUG builds)
.Load("wwwroot", "index.html");
window.WaitForClose();
In your HTML file:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<script src="photino://photinoWindow.js"></script>
<script>
// Send a one-way message
function sayHello() {
PhotinoWindow.sendMessage('say-hello', 'JavaScript');
}
// Send a request and handle the response
async function getUser() {
try {
const user = await PhotinoWindow.sendRequest('get-user', { id: 1 });
console.log('User:', user.name);
} catch (error) {
console.error('Error:', error.message);
}
}
</script>
</body>
</html>
Features in Detail
Typed Messaging System
The library provides a robust typed messaging system for communication between .NET and JavaScript.
One-Way Messages
Perfect for fire-and-forget scenarios like button clicks, notifications, or status updates.
.NET Side:
// Register a handler
window.RegisterMessageHandler<UserAction>("user-action", (action) =>
{
logger.LogInformation("User {UserId} performed {ActionType}", action.UserId, action.Type);
});
JavaScript Side:
// Send message
PhotinoWindow.sendMessage('user-action', {
userId: 123,
type: 'login',
timestamp: new Date().toISOString()
});
Request-Response Pattern
Ideal for data fetching, form validation, or any scenario requiring a response.
.NET Side:
// Register an async request handler
window.RegisterRequestHandler<GetUserRequest, UserResponse>("get-user", async (request) =>
{
var user = await userService.GetUserAsync(request.UserId);
return new UserResponse
{
Name = user.Name,
Email = user.Email
};
});
// Handle validation requests
window.RegisterRequestHandler<ValidateFormRequest, ValidationResult>("validate-form", async (request) =>
{
var result = await formValidator.ValidateAsync(request);
return result;
});
JavaScript Side:
// Make a request and handle the response
async function loadUser(userId) {
try {
const user = await PhotinoWindow.sendRequest('get-user', { userId: userId });
document.getElementById('userName').textContent = user.name;
document.getElementById('userEmail').textContent = user.email;
} catch (error) {
console.error('Failed to load user:', error.message);
}
}
// Validate a form with timeout
async function validateForm(formData) {
try {
const result = await PhotinoWindow.sendRequest('validate-form', formData, 5000); // 5s timeout
if (result.isValid) {
showSuccess('Form is valid!');
} else {
showErrors(result.errors);
}
} catch (error) {
showError('Validation failed: ' + error.message);
}
}
JavaScript Integration
Automatic Script Injection
The library includes an embedded JavaScript client (photinoWindow.js) that's automatically served via a custom scheme handler.
Configuration Options:
enablePhotinoDebugLogging: When true, enables debug logging from the Photino JavaScript framework itself in the browser console. This is useful for debugging message passing issues between JavaScript and .NET.forwardConsoleMessagesToLogger: When true (default), automatically forwards JavaScript console.log/warn/error messages to your .NET logger, allowing you to capture client-side logging in your server-side logs.
// Basic setup with auto-initialization
window.RegisterPhotinoScript();
// Advanced setup with options
window.RegisterPhotinoScript(
scheme: "photino", // Custom scheme name (default: "photino")
enablePhotinoDebugLogging: true, // Enable debug output for the Photino JavaScript framework (default: false)
forwardConsoleMessagesToLogger: true // Forward JS console messages to .NET logger (default: true)
);
In your HTML:
<script src="photino://photinoWindow.js"></script>
PhotinoWindow JavaScript API
Once loaded, the PhotinoWindow object provides a clean API:
// Check initialization status
const stats = PhotinoWindow.getStatus()
console.log(`Initialized ${stats.initialized}, Handler Count: {stats.messageHandlers}, Pending Requests: {stats.pendingRequests}`);
// Send message
PhotinoWindow.sendMessage(type, payload);
// Send request with optional timeout
PhotinoWindow.sendRequest(type, payload, timeout = 30000);
// Register message handler (from .NET to JS)
PhotinoWindow.onMessage(type, handler);
// Remove message handler
PhotinoWindow.offMessage(type);
// Clear all handlers
PhotinoWindow.clearHandlers();
// Get statistics
const stats = PhotinoWindow.getStats();
console.log(`Handlers: ${stats.messageHandlers}, Pending: ${stats.pendingRequests}`);
Console Logging Bridge
Forward JavaScript console output to your .NET logger for unified logging.
Setup
// Enable console logging bridge (enabled by default)
window.RegisterPhotinoScript(forwardConsoleMessagesToLogger: true);
Usage
All JavaScript console methods are automatically forwarded:
console.log('This appears in .NET logger as LogDebug');
console.info('This appears as LogInformation');
console.warn('This appears as LogWarning');
console.error('This appears as LogError');
console.debug('This appears as LogTrace');
// Complex objects are automatically serialized
console.log('User data:', { id: 1, name: 'John' });
.NET Output:
[12:34:56] DEBUG [JS Console] This appears in .NET logger as LogDebug
[12:34:56] INFO [JS Console] This appears as LogInformation
[12:34:56] WARN [JS Console] This appears as LogWarning
[12:34:56] ERROR [JS Console] This appears as LogError
[12:34:56] TRACE [JS Console] This appears as LogTrace
[12:34:56] DEBUG [JS Console] User data: {"id":1,"name":"John"}
Hot Reload for Development
The library provides automatic hot reload functionality that monitors your web files for changes and refreshes the application automatically during development.
Basic Usage
var window = new PhotinoWindow()
.SetLogger(logger)
.SetTitle("My App")
// Load with hot reload support - automatically enabled in DEBUG builds
.Load("wwwroot", "index.html");
window.WaitForClose();
How It Works
- Automatic Detection: Hot reload is enabled automatically in DEBUG builds and disabled in RELEASE builds
- Path Resolution: Always loads from source directory (not bin/output) to ensure hot reload works properly
- Multi-Window Support: Multiple windows can watch the same directory efficiently using shared file watchers
- URL Support: Also works with development servers while still monitoring local files
Supported Scenarios
Local Files:
// Watch wwwroot directory, load index.html from source
.Load("wwwroot", "index.html")
// Watch Resources/wwwroot, load admin.html
.Load("Resources/wwwroot", "admin.html")
Development Servers:
// Watch wwwroot for file changes, but load from development server
.Load("wwwroot", "http://localhost:3000")
// Watch Resources/wwwroot, load from HTTPS development server
.Load("Resources/wwwroot", "https://localhost:5001")
Advanced Configuration
// Custom hot reload configuration
.Load("wwwroot", "index.html", options =>
{
options.DebounceDelay = 500; // Wait 500ms after changes stop
options.FileFilter = "*.html,*.css,*.js"; // Only watch specific file types
options.IncludeSubdirectories = true; // Monitor subdirectories (default)
options.EnableOnlyInDebug = false; // Force enable in RELEASE builds
})
Path Resolution Logic
The hot reload system intelligently finds your source files:
- Project Root Detection: Searches up the directory tree for
.csprojfiles - Source Priority: Always prefers source directories over bin/output copies
- Embedded Resources: Supports
Resources/wwwrootpattern for embedded resource projects - Fallback Handling: Gracefully falls back to regular loading if source detection fails
Multi-Window Efficiency
When multiple windows watch the same directory:
// Both windows share a single file watcher for "wwwroot"
var window1 = new PhotinoWindow().Load("wwwroot", "index.html");
var window2 = new PhotinoWindow().Load("wwwroot", "admin.html");
// Automatic cleanup when windows are disposed
window1.Dispose(); // Watcher continues for window2
window2.Dispose(); // Watcher is automatically disposed
Debugging Hot Reload
Enable debug logging to troubleshoot hot reload issues:
var window = new PhotinoWindow()
.SetLogger(logger) // Hot reload uses this logger for debug output
.Load("wwwroot", "index.html");
Debug Output:
[12:34:56] DEBUG Hot reload monitoring source path: C:\MyProject\wwwroot
[12:34:57] INFO Hot reload triggered for path: C:\MyProject\wwwroot
[12:34:57] DEBUG Sent hot reload message to window
JavaScript Integration
The hot reload system works seamlessly with the included JavaScript library. When files change, a __hot_reload message is sent to all affected windows, triggering a page refresh:
// This happens automatically - no JavaScript code needed
// But you can listen for the reload event if desired
PhotinoWindow.onMessage('__hot_reload', () => {
console.log('Hot reload triggered - page will refresh');
// Custom cleanup logic before reload if needed
});
Advanced Features
Enhanced Photino Logging
The library includes a Harmony-based patcher that intercepts Photino.NET's internal logging and routes it through your ILogger.
// Initialize the log patcher at application startup
PhotinoWindowLogPatcher.Initialize();
// Now all Photino internal logs will use your configured logger
var window = new PhotinoWindow()
.SetLogger(logger) // This logger will receive both extension and Photino logs
// ... rest of configuration
Memory Management
The library uses ConditionalWeakTable<PhotinoWindow, PhotinoWindowData> for storing window-specific data, ensuring that:
- Window data is automatically garbage collected when windows are disposed
- No memory leaks from long-running applications
- Thread-safe access to window-specific configuration
JSON Serialization Configuration
Configure JSON serialization for your message payloads. The library starts with sensible defaults from JsonUtilities.GetSerializerOptions() and allows you to customize them:
// Optional
window.ConfigureJsonSerializerOptions(options =>
{
// Default options are: camelCase, not indented
// Included converters: JsonStringEnumConverter and JsonDateTimeConverter (microsecond precision)
// Add JSON source generators for AOT/trimming support
options.TypeInfoResolverChain.Add(MyJsonContext.Default);
// Add custom converters
options.Converters.Add(new JsonCustomConverter());
});
For AOT and trimming scenarios, create a JSON source generator context:
[JsonSerializable(typeof(MyRequestType))]
[JsonSerializable(typeof(MyResponseType))]
internal partial class MyJsonContext : JsonSerializerContext
{
}
API Reference
Extension Methods
| Method | Description |
|---|---|
SetLogger(ILogger) |
Configure logger for the window |
ConfigureJsonSerializerOptions(Action<JsonSerializerOptions>) |
Configure JSON serialization options |
RegisterMessageHandler<T>(string, Action<T>) |
Register one-way message handler |
UnregisterMessageHandler(string) |
Remove message handler |
RegisterRequestHandler<TReq, TRes>(string, Func<TReq, Task<TRes>>) |
Register async request handler |
UnregisterRequestHandler(string) |
Remove request handler |
SendMessage<T>(string, T) |
Send one-way message to JavaScript |
Load(string, string) |
Load content with automatic hot reload support (watchPath, htmlPath) |
Load(string, string, Action<HotReloadOptions>?) |
Load content with configurable hot reload options |
RegisterPhotinoScript(string, bool, bool) |
Enable script injection with options (scheme, enablePhotinoDebugLogging, forwardConsoleMessagesToLogger) |
ClearHandlers() |
Remove all registered handlers |
Static Classes
| Class | Purpose |
|---|---|
PhotinoWindowLogPatcher |
Harmony-based logging integration |
PhotinoWindowExtensions |
Main extension methods |
Example Project
The library includes a complete example application demonstrating all features:
Location: NFoundation.Templates.Photino.NET.App
To run the example:
cd src/NFoundation.Templates.Photino.NET.App
dotnet run
The example demonstrates:
- Typed messaging patterns
- Request-response handling
- Console logging bridge
- Error handling
- UI interactions
Dependencies
| Package | Version | Purpose |
|---|---|---|
| Photino.NET | 3.2.3+ | Core desktop framework |
| Microsoft.Extensions.Logging | 8.0.0+ | Logging abstraction |
| NFoundation.Json | 1.0.0+ | JSON utilities |
| Lib.Harmony | 2.3.3+ | Runtime method patching |
Requirements
- .NET 8.0 or later
- Windows, macOS, or Linux (Photino.NET requirements)
- Modern web browser engine (embedded in application)
License
This library is licensed under the Apache License 2.0.
It depends on:
- Photino.NET, which is also licensed under Apache License 2.0.
- Harmony, which is also licensed under MIT License.
See the NOTICE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Lib.Harmony (>= 2.3.3)
- Microsoft.Extensions.Logging (>= 8.0.0)
- Microsoft.Extensions.Logging.Console (>= 8.0.0)
- NFoundation.Json (>= 1.0.0)
- Photino.NET (>= 3.2.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.