Skip to main content

Package

Overview

A Port package is a C# class library that exposes device or service logic as typed API endpoints. Annotate a class with [Package], declare [API] properties for each entry point, and the framework generates REST endpoints, injects handlers, and manages the lifecycle automatically.

Port packages provide:

  • Declarative API generation via attributes
  • Unified logging and entry-property access through IPackageHandler
  • Validation gate with [Valid]
  • Seamless .page entry binding through property:{...} metadata

NuGet

PackageLanguagePackage ManagerOS
portdicC#NuGetWindows

Attribute Summary

AttributeTargetInjected TypeDescription
[Package]classRegisters the class as a Port-managed package
[Handler]propertyIPackageHandlerInjects handler — unified access to logging and entry property
[Preset]methodCalled once after injection; use for initialization
[API]property, fieldGenerates a REST API endpoint for the member
[Valid]methodValidation gate — false blocks the call with the error message
[Comment]propertyAttaches documentation text to an API property
[Command]methodRegisters a command endpoint

Attribute Reference

[Package]

Marks a class as a Port-managed package. Register it at startup via Port.Add<T>(name).

[Package]
public class Bulb
{
[Handler]
public IPackageHandler Handler { get; set; }

[Valid("Device not connected")]
public bool Valid() => serialPort.IsOpen;

[API(EntryDataType.Enum)]
public string OffOn { get; set; } = "Off";
}
Port.Add<Bulb>("Bulb1");
Port.Run();

[Handler]

Injects IPackageHandler — the single entry point for logging and entry-property access. Call SetLogger in [Preset] to enable file output; use Handler.Write and Handler.EntryProperty anywhere inside the package.

[Package]
public class Heater
{
[Handler]
public IPackageHandler Handler { get; set; }

[Preset]
public void Preset()
{
Handler.SetLogger(@"C:\Logs"); // writes to C:\Logs\Heater\
}

[API(EntryDataType.Num, PropertyFormat.Json, "Unit")]
public double Temp
{
get
{
if (Handler.EntryProperty.TryToGetValue("Unit", out string unit))
{
Handler.Write($"[INFO] Unit={unit}");
return unit == "F" ? 212.0 : 100.0;
}
return double.NaN;
}
}
}

IPackageHandler members:

MemberDescription
SetLogger(rootPath)Enable file logging; creates rootPath/packageName/
SetLogger(rootPath, conf)Same with rotation/retention via PortLogConfiguration
Write(message)Write a plain text log entry
Write(code, header, dict)Write a structured log entry (LogTypeCode, header, key-value pairs)
EntryPropertyCurrent IProperty for the active Get/Set call

Reading entry properties (property:{...} in .page):

// .page: RoomTemp f8 property:{"Unit":"C","Max":"300"}

if (Handler.EntryProperty.TryToGetValue("Unit", out string unit))
{
// unit == "C"
}
if (Handler.EntryProperty.TryToGetValue("Max", out string max))
{
double limit = double.Parse(max); // 300.0
}

[Preset]

Called once after all injections are complete. Use it to configure the handler or subscribe to events.

[Preset]
public void Preset()
{
Handler.SetLogger(@"C:\Logs");
}

[API]

Registers a property or field as a REST API endpoint.

[API]
public string Status { get; set; }

[API(EntryDataType.Enum)]
public string OffOn { get; set; }

[API(EntryDataType.Num, PropertyFormat.Json, "Unit")]
public double Temp { get; set; }

[API(EntryDataType.Char)]
public string Power { get; set; }

EntryDataType values:

ValueDescription
EntryDataType.TextText
EntryDataType.NumNumeric (double)
EntryDataType.CharASCII string
EntryDataType.EnumEnumeration
EntryDataType.ListList

[Valid]

Defines a validation gate. When the method returns false, the error message is surfaced and the call is blocked.

[Valid("Device not connected")]
public bool Valid()
{
return serialPort.IsOpen;
}

[Comment]

Attaches a documentation string to an API property, visible through the API metadata.

[API, Comment("Current temperature in Celsius")]
public double Temperature { get; set; }

[Command]

Registers a method as a command endpoint.

[Command("Reset")]
public void Reset(string value)
{
// handle reset command
}

Creating a Package

1. Create a class library project

dotnet new classlib -n HeaterLib
cd HeaterLib
dotnet add package portdic

2. Implement the package class

using Portdic;

[Package]
public class Heater
{
[Handler]
public IPackageHandler Handler { get; set; }

[Preset]
public void Preset()
{
Handler.SetLogger(@"C:\Logs");
}

[Valid("Connection not established")]
public bool Valid() => true;

[API]
public string Power { get; set; }

[API(EntryDataType.Num, PropertyFormat.Json, "Unit")]
public double Temp
{
get
{
if (Handler.EntryProperty.TryToGetValue("Unit", out string unit))
{
Handler.Write($"[INFO] Unit={unit}");
return unit == "F" ? 212.0 : 100.0;
}
return double.NaN;
}
}
}

3. Build and publish

dotnet publish -c Release -o ./publish

4. Pack for Port

cd ./publish
port pack HeaterLib.dll HeaterLib

Console output on success:

[PACK] Packing started at 2025-01-07T21:17:19+09:00
[PACK] load complete HeaterLib.dll : heaterlib
[PACK][GET][0] Power
[PACK][GET][1] Temp
[PACK][SET][0] Power
[CREATED][PACKAGE] ...\port\pkg\HeaterLib.pkg

5. Register and run

Port.Add<Heater>("Heater1");
Port.Add<Heater>("Heater2"); // multiple instances with independent state
Port.Run();

Publishing

Build commands

# Standard release
dotnet publish -c Release -o ./publish

# Platform-specific (no runtime bundled)
dotnet publish -c Release -r win-x64 --self-contained false

VS Code task automation

{
"version": "2.0.0",
"tasks": [
{
"label": "Publish",
"command": "dotnet",
"type": "process",
"args": ["publish", "-c", "Release", "-o", "./publish"],
"problemMatcher": "$msCompile"
}
]
}

  • attribute — Full attribute reference
  • Quick Start — End-to-end setup with .page files and Port.Run()