![]() |
VOOZH | about |
dotnet add package portdic --version 1.1.99
NuGet\Install-Package portdic -Version 1.1.99
<PackageReference Include="portdic" Version="1.1.99" />
<PackageVersion Include="portdic" Version="1.1.99" />Directory.Packages.props
<PackageReference Include="portdic" />Project file
paket add portdic --version 1.1.99
#r "nuget: portdic, 1.1.99"
#:package portdic@1.1.99
#addin nuget:?package=portdic&version=1.1.99Install as a Cake Addin
#tool nuget:?package=portdic&version=1.1.99Install as a Cake Tool
A step-by-step guide to building a .NET equipment application with Port.
A Page is the fundamental data definition unit in the Port system.
A single .page file is a collection of related Entries.
Once pushed to the Port server, those entries are created in the in-memory data store.
Page = a list of Entries belonging to one functional unit (e.g. IO, Sensor, Motor)
Pages can be generated automatically from external documents (.docx, .xlsx, .csv),
or defined directly in C# using the [Page] attribute.
.page File Syntax[EntryKey] [DataType] [pkg:PackageName] [property:{...}]
[EntryKey] [DataType[N]] [pkg:PackageName] [property:{...}]
Appending [N] to the DataType expands into N indexed entries (EntryKey[1] … EntryKey[N]).
| Field | Required | Description |
|---|---|---|
EntryKey |
✅ | Unique identifier for the entry |
DataType |
✅ | Data type (f8, Enum.OnOff, char, etc.) |
DataType[N] |
— | Array form: expands to N entries named EntryKey[1]…EntryKey[N] |
pkg |
optional | Package API to bind |
property |
optional | Per-entry user property (JSON) |
Example (io.page):
Bulb1OnOff Enum.OnOff pkg:IODevice.DI property:{"IO.No":"D0.01","Model":"IODevice"}
Bulb2OnOff Enum.OnOff pkg:IODevice.DI property:{"IO.No":"D0.02","Model":"IODevice"}
Bulb1Temp f8 pkg:IODevice.AI property:{"IO.No":"A0.01","Model":"IODevice"}
Bulb2Temp f8 pkg:IODevice.AI property:{"IO.No":"A0.02","Model":"IODevice"}
# Array: SlotMap[1] … SlotMap[25] are created as separate entries
SlotMap Enum.OnOff[25]
An Entry is the smallest data unit in the Port system. Each entry has:
category.entryName, e.g. Bulb1.OnOff)Data Types:
| Type | Description |
|---|---|
f4 / f8 |
Floating point (4 / 8 bytes) |
i1 ~ i8 |
Signed integer (1 – 8 bytes) |
u1 ~ u8 |
Unsigned integer (1 – 8 bytes) |
A / A(n) |
ASCII string (default 1 byte; A(n) sets max length to n) |
B |
Binary byte |
bool |
Boolean |
L |
List — metadata-only; no memory allocation |
Num |
Generic numeric (8 bytes) |
Char(n) |
Fixed-length character buffer (n bytes, default 255) |
string / str(n) |
Variable-length UTF-8 string (default max 255 bytes) |
Enum.XXX |
Enumeration referencing a pre-defined enum |
DEF |
Metadata-only declaration (CEID / ALID; no memory allocation) |
Logical setpoints with no corresponding hardware document entry
(e.g. a target temperature) are defined in a separate .page file
inside the category folder:
bulb1/.page:
TargetTemp f8
The framework translates specification tables into structured data models.
Assume a table exists in C:\Users\admin\Documents\IO.docx:
| IO.No | Description | Model |
|---|---|---|
| D0.01 | Bulb1.OnOff | IODevice |
| D0.02 | Bulb2.OnOff | IODevice |
| A0.01 | Bulb1.Temp | IODevice |
| A0.02 | Bulb2.Temp | IODevice |
Map the table columns to a C# class using attributes.
public class IOModel
{
[ColumnHeader("IO.No"), EntryProperty]
public string IONo { get; set; } = null!;
[ColumnHeader("Description"), EntryKey]
public string Description { get; set; } = null!;
[ColumnHeader("Model"), EntryProperty]
public string Model { get; set; } = null!;
}
| Attribute | Role |
|---|---|
[ColumnHeader] |
Maps the property to an Excel/Word column by name |
[EntryKey] |
Designates this column as the entry name |
[EntryProperty] |
Includes this column in the property:{...} JSON |
.cs FilePort.Document<T> writes a constants class alongside the .page file.
// Auto-generated by Port. Do not edit manually.
namespace sample
{
public static class Io
{
public const string Bulb1OnOff = "Bulb1OnOff";
public const string Bulb2OnOff = "Bulb2OnOff";
public const string Bulb1Temp = "Bulb1Temp";
public const string Bulb2Temp = "Bulb2Temp";
}
}
For entries that don't come from an external document (e.g. EFEM I/O signals),
define them directly in a C# class decorated with [Page].
Port.Pull)Port.Pull writes a partial class file (e.g. entry.cs) containing every pulled entry
as a const string. This file is regenerated on every pull — do not edit it manually.
entry.cs also includes a Defined class that contains all enum definitions stored in the
database, generated automatically alongside the entry constants.
// Auto-generated by Port.Pull — do not edit manually.
namespace Portdic
{
public partial class EFEM
{
public const string LP1_Main_Air_i = "EFEM.LP1_Main_Air_i";
public const string LP2_Cont_o = "EFEM.LP2_Cont_o";
// ... all pulled entries ...
}
public partial class Defined
{
public enum OffOn : int
{
Off = 0,
On = 1,
}
public enum UnkOffOn : int
{
Unknown = 0,
Off = 1,
On = 2,
}
// ... all enums from the database ...
}
}
CustomEFEM)Add entries and enum types not yet in the pulled class by creating a separate
[Page]-decorated partial class.
Rules:
EnumName on [PageEntry] must match it exactly.[PageEnum] fields are pushed before [PageEntry] fields so enum references always resolve.app/.enum so port pull keeps them in the right file.using Portdic;
using Portdic.SECS;
namespace sample.Controller
{
[Page("EFEM")]
public partial class CustomEFEM
{
// ── Enum declarations ─────────────────────────────────────────
[PageEnum("Unknown", "Off", "On")]
public const string UnkOffOn = "UnkOffOn";
[PageEnum("Unknown", "TurnOff", "TrunOn")]
public const string UnkTurnOffOn = "UnkTurnOffOn";
// ── Entry declarations ────────────────────────────────────────
[PageEntry(PortDataType.Char)]
public const string LP1_Cont1_o = "EFEM.LP1_Cont1_o";
[PageEntry(PortDataType.Enum, EnumName = UnkTurnOffOn)]
public const string LP1_OffOn_o = "EFEM.LP1_OffOn_o";
// ── Package & Property binding ────────────────────────────────
// Package: "Name.PropertyName" → pushed as pkg:Name.PropertyName
// Property: raw JSON string → pushed as property:{...}
[PageEntry(PortDataType.Enum, EnumName = "OffOn",
Package = "Bulb1.OffOn",
Property = "{\"MIN\":0,\"MAX\":1}")]
public const string LP1_BulbOnOff_o = "EFEM.LP1_BulbOnOff_o";
}
}
Push the class instance before starting the port server:
Port.Push("sample", new CustomEFEM());
Three overloads are available depending on the source of the entry data:
| Overload | Use case |
|---|---|
Push(reponame, obj) |
Push from a [Page]-decorated class instance |
Push(reponame, page) |
Push from a Page returned by Document<T>.NewPage() |
Push(repo) |
Push an entire directory via the port REST API (RepositoryInfo) |
// From a [Page]-decorated class (enums + entries)
Port.Push("sample", new CustomEFEM());
// From a document-derived Page (entries only)
Port.Push("sample", ioDoc.NewPage("Device"));
Port.Pull calls port pull {reponame} via the CLI and writes reconstructed .page,
.enum, and related files into a port/ subfolder inside the specified root directory.
// Syntax
Port.Pull(string reponame, string root);
// Example: writes files to D:\sample\Repo\pull\port\
Port.Pull("sample", @"D:\sample\Repo\pull\");
port.exe is resolved from the parent of %PortPath% first; if not found, it falls back
to the system PATH.
#if DEBUG)The recommended pattern in a #if DEBUG block synchronizes the DB with your latest
class definitions before loading the repository at runtime:
#if DEBUG
// 1. Ensure project root exists
Port.Repository.New(@"D:\sample\Repo\pull\", "sample");
// 2. Convert external document to entries
var ioDoc = Port.Document<IOModel>(@"C:\Users\admin\Documents\IO.docx");
ioDoc.Where(v => v.Key.Contains("OnOff")).ToList()
.ForEach(v => v.DataType = "Enum.OnOff");
ioDoc.Where(v => v.Key.Contains("Temp")).ToList()
.ForEach(v => v.DataType = "f8");
if (ioDoc.Count > 0)
{
ioDoc.New(@"C:\Users\admin\Documents\sample\.page\io.page");
ioDoc.New(@"C:\Users\admin\Documents\sample\.net\io.cs");
}
// 3. Push inline-defined entries (enums + EFEM signals)
Port.Push("sample", new CustomEFEM());
// 4. Push document-derived entries
Port.Push("sample", ioDoc.NewPage("Device"));
// 5. Reconstruct .page/.enum files from DB
Port.Pull("sample", @"D:\sample\Repo\pull\");
#endif
A Model is the data-binding layer that connects Port Entries to C# properties.
[Model] attribute.[ModelBinding]..page file (Entry definitions)
↓ Push
Port in-memory DB (Entry values)
↕ ModelBinding
Model (C# property ↔ Entry mapping)
↕
Controller / Flow (business logic)
The Page is the Single Source of Truth; the Model is a type-safe view of that data in code.
Use the [ModelBinding(instanceKey, entryKey)] attribute.
"Bulb1", "LP1").cs file[Model]
public class BulbModel
{
// Both "Bulb1" and "Bulb2" instances share the same property structure
[ModelBinding("Bulb1", Io.Bulb1OnOff)]
[ModelBinding("Bulb2", Io.Bulb2OnOff)]
public Entry OnOff { get; set; }
[ModelBinding("Bulb1", Io.Bulb1Temp)]
[ModelBinding("Bulb2", Io.Bulb2Temp)]
public Entry Temp { get; set; }
[ModelBinding("Bulb1", Io.Bulb1TargetTemp)]
[ModelBinding("Bulb2", Io.Bulb2TargetTemp)]
public Entry TargetTemp { get; set; }
}
[ModelBinding] attributes can freely mix entries from the auto-generated class and
a user-defined [Page] class:
[Model]
public class LoadportModel
{
// Entry added via CustomEFEM
[ModelBinding("LP1", CustomEFEM.LP1_Cont1_o)]
// Entry from the auto-generated EFEM class
[ModelBinding("LP2", EFEM.LP2_Cont_o)]
public Entry LP_Cont_o { get; set; }
[ModelBinding("LP1", CustomEFEM.LP1_OffOn_o)]
public Entry LP_OffOn_o { get; set; }
}
A Controller is a logic container that holds one or more Flows.
[Controller] attribute.Port.Add<TController, TModel>(instanceKey).Port.Add<BulbController, BulbModel>("Bulb1");
Port.Add<BulbController, BulbModel>("Bulb2");
Port.Run();
A Flow is a sequential workflow defined inside a Controller.
[Flow("FlowName")].[FlowStep(order)].Port.Set("Bulb1", FlowAction.Executing).Receive the Model directly as a method parameter to access Entry values:
[Controller]
public class BulbController
{
[Flow("BulbOn")]
public class BulbOn
{
[Handler]
public IFlowHandler handler { get; set; } = null!;
[FlowStep(0)] // Validation Step
public void CheckInitialState(BulbModel model)
{
if (model.Temp.Value <= 100)
handler?.Next();
}
[FlowStep(1)] // Action Step
public void TurnOn(BulbModel model)
{
model.OnOff.Set("On");
handler?.Next();
}
[FlowStep(2)] // Monitoring Step
public void MonitorTemperature(BulbModel model)
{
if (model.Temp.Value >= model.TargetTemp.Value)
{
model.OnOff.Set("Off");
handler?.Next(); // Marks Flow as Completed
}
}
}
}
Start / cancel a Flow:
Port.Set("Bulb1", FlowAction.Executing); // Start BulbOn Flow
Port.Set("Bulb1", FlowAction.Canceled); // Cancel
Port.App<T>)Port.App<T>() is the mandatory first call when using the [Port] attribute-based
initialization style. Decorate your application class with [Port] to declare the
repository name and pull path, then call Port.App<T>() before any other Port API.
Port.App<T>() reads the [Port] attribute on T, creates the repository via
Port.Repository.New(pullPath, reponame), and starts the PortDic instance.
All subsequent Port.Push, Port.Pull, and Port.Repository.Load calls depend on
this initialization being completed.
| Signature | Description |
|---|---|
Port.App<T>() |
Initializes the Port. Subscribe to Port.OnReady separately to run code when ready. |
Port.App<T>(Action onReady) |
Initializes the Port and registers onReady to fire automatically when the port reaches the Synchronized state. No manual Port.OnReady += needed. |
Port.App<T>(Action onReady)Pass the UI initialization callback directly to Port.App<T>().
The callback is invoked automatically on Synchronized, so there is no need to subscribe to Port.OnReady separately.
[Port("sample")]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
try
{
Port.App<MainWindow>(() => Dispatcher.Invoke(() =>
{
// Runs automatically when Port reaches Synchronized state
vm.StartPolling();
RefreshDisplay();
}));
Port.Add<LoadportController, LoadportModel>("LP1");
Port.Add<LoadportController, LoadportModel>("LP2");
Port.Run();
}
catch (Exception ex)
{
MessageBox.Show($"{ex.Message}");
}
}
}
Port.App<T>() + manual Port.OnReadyUse this form when you need to store the handler reference (e.g. to unsubscribe on close).
[Port("sample")]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
try
{
Port.App<MainWindow>();
#if DEBUG
var ioDoc = Port.Document<IOModel>(@"C:\Users\admin\Documents\IO.docx");
ioDoc.Where(v => v.Key.Contains("OnOff")).ToList()
.ForEach(v => v.DataType = "Enum.OnOff");
ioDoc.Where(v => v.Key.Contains("Temp")).ToList()
.ForEach(v => v.DataType = "f8");
if (ioDoc.Count > 0)
{
ioDoc.New(@"C:\Users\admin\Documents\sample\.page\io.page");
ioDoc.New(@"C:\Users\admin\Documents\sample\.net\io.cs");
}
Port.Push("sample", new CustomEFEM());
Port.Push("sample", ioDoc.NewPage("Device"));
Port.Pull("sample", @"D:\PORT\SampleArduinoLib\sample\Repo\pull\");
#endif
Port.Add<LoadportController, LoadportModel>("LP1");
Port.Add<LoadportController, LoadportModel>("LP2");
Port.OnReady += Port_OnReady;
Port.Run();
}
catch (Exception ex)
{
MessageBox.Show(
$"{ex.Message}\n\nInner: {ex.InnerException?.Message}\n\nStack: {ex.StackTrace}");
}
}
private void Port_OnReady(object? sender, EventArgs e)
{
Dispatcher.Invoke(() =>
{
vm.StartPolling();
RefreshDisplay();
});
}
}
| Interface | Purpose |
|---|---|
IFlowHandler |
Basic Flow progression control (Next()) |
IFlowWithModelHandler<T> |
Flow event subscriptions that carry the Model |
ISchedulerHandler<T> |
Transfer-completion scheduling |
[Handler]
public IFlowHandler handler { get; set; } = null!;
handler.Next(); // Advance to the next FlowStep
handler.Done(); // Synchronously force the Flow to Idle
IFlowCACD<T> is the recommended interface for equipment transfer flows.
Declare the handler as IFlowWithModelHandler<T> to gain access to flow lifecycle
events that carry the model. Subscribe in [Preset] — subscriptions survive across
all executions of the same flow key.
[Controller]
internal class WTRController
{
[Flow("Pick")]
public class Pick : IFlowCACD<WTRCommModel>
{
[Handler]
public IFlowWithModelHandler<WTRCommModel> handler { set; get; } = null!;
[Handler]
public ISchedulerHandler<DualArmActionArgs> scheduler { set; get; } = null!;
// Preset() runs once at registration time.
// Event subscriptions made here are preserved for every subsequent execution.
[Preset]
public void Preset()
{
handler.SetLogger(@"D:\log");
handler.OnFlowFinished += (s, e) =>
scheduler.TransferCompleted(e.Model.SelectedArm);
}
public void CheckStatus(WTRCommModel m) { Task.Delay(300).Wait(); handler.Next(); }
public void Action(WTRCommModel m) { handler.Next(); }
public void CheckAction(WTRCommModel m) { handler.Next(); }
public void Done(WTRCommModel m) { handler.Done(); }
}
}
| Method | Behaviour |
|---|---|
handler.Next() |
Advances to the next step via the normal state machine |
handler.Done() |
Synchronously forces the flow to Idle before returning, then fires OnFlowFinished |
Use handler.Done() in the last step when a sibling flow must start inside the
OnFlowFinished callback. Using handler.Next() there risks an
AlreadyExecutingFlowException because the flow has not yet transitioned to Idle
when the callback fires.
The recommended interface for equipment transfer flows. Step order is fixed:
| Step | Method | Description |
|---|---|---|
| 0 | CheckStatus |
Verify preconditions before acting |
| 1 | Action |
Execute the physical operation |
| 2 | CheckAction |
Confirm the operation completed correctly |
| 3 | Done |
Finalize — call handler.Done() to close the flow |
| Event | When Fired | Args |
|---|---|---|
OnFlowFinished |
Flow reached the Done step and completed | FlowFinishedWithModelArgs<T> — Model, timing, step records |
OnFlowOccurred |
A step transition occurred | PortFlowOccurredWithModelArgs<T> — Model, step status |
OnFlowIssue |
Flow stopped due to an alarm | PortFlowIssueWithModelArgs<T> — Model, alarm code |
All event args expose a Model property that is the singleton model instance bound to
the flow key, so reading e.Model.Target.Value always reflects the current
shared-memory value.
A Package is a reusable device-driver module that bridges a physical device (Bulb, Heater, IO card, etc.) with Port Entries.
.page file using the pkg:PackageName.PropertyName syntax.[Package] attribute..page FileBulb1OnOff Enum.OnOff pkg:Bulb.OffOn property:{"IO.No":"D0.01"}
Bulb1Temp f8 pkg:Bulb.Temp property:{"IO.No":"A0.01"}
[Page] ClassUse the Package parameter of the [PageEntry] attribute:
[Page("EFEM")]
public partial class CustomEFEM
{
// Package: "Name.PropertyName" → pushed as pkg:Name.PropertyName
// Property: raw JSON string → pushed as property:{...}
[PageEntry(PortDataType.Enum, EnumName = "OffOn",
Package = "Bulb1.OffOn",
Property = "{\"MIN\":0,\"MAX\":1}")]
public const string LP1_BulbOnOff_o = "EFEM.LP1_BulbOnOff_o";
}
[Package]
public class Bulb
{
[Logger]
public ILogger Logger { get; set; }
[Property]
public IProperty Property { get; set; }
[Valid("Device not connected")]
public bool Valid() => serialPort.IsOpen;
// Setter is called automatically when the Entry value changes
[API(EntryDataType.Enum)]
public string OffOn
{
set { Logger.Write($"[INFO] Bulb OffOn → {value}"); }
get => _offOn;
}
private string _offOn = "Off";
private SerialPort serialPort = new SerialPort();
}
Push the [Page] class before Port.Run():
Port.Push("sample", new CustomEFEM());
Port.Set writes a value to the specified Entry in the in-memory DB immediately.
If a Package is bound to that Entry, its setter is invoked, triggering the physical
operation.
// Write an Entry value
Port.Set("Bulb1.OnOff", "On");
Port.Set("Bulb1.TargetTemp", "85.0");
// Trigger / cancel a Flow
Port.Set("Bulb1", FlowAction.Executing);
Port.Set("Bulb1", FlowAction.Canceled);
// Inside a Flow — set directly through the model parameter
model.OnOff.Set("On");
model.TargetTemp.Set("80.0");
@ Binding)[FlowModel]
public IFlowModel model { get; set; }
model.Set("@OnOff", "On"); // @ prefix references a ModelBinding key
Port.Get reads the current value of the specified Entry from the in-memory DB.
It always returns the latest value and supports high-performance in-memory access.
// Read an Entry value
string onOff = Port.Get("Bulb1.OnOff"); // → "On" or "Off"
if (Port.Get("Bulb1.OnOff") == "On")
{
Port.Set("Bulb1.OnOff", "Off");
}
// Access the current value through the Entry.Value property
double temp = (double)model.Temp.Value;
string status = (string)model.OnOff.Value;
@ Binding)var value = model.Get("@Temp"); // Reads the Entry bound to the ModelBinding key
A Rule Script defines write guards and periodic automation using .rule files
stored in the app/ directory (e.g. app/.rule).
Two rule types are available: set and get.
set Rule — Write GuardA set rule is evaluated synchronously whenever Port.Set is called on the
matching key. It acts as a gate: if the action condition is not satisfied, the
write is blocked and an error is returned.
set("write condition", "allow condition")
| Argument | Role |
|---|---|
| First — write condition | Specifies which write attempt triggers this rule.<br>Bare key (Bulb1.OnOff) matches any write;<br>key+op+value (Bulb1.OnOff == Off) matches only that value. |
| Second — allow condition | Boolean expression evaluated against the current memory state.<br>If true → write is allowed. If false → write is blocked. |
// Allow turning Bulb1 off only when temperature is safe (>= 80)
set("Bulb1.OnOff == Off", "Bulb1.Temp >= 80")
// Block any write to Bulb2.OnOff while Bulb1 is still On
set("Bulb2.OnOff", "Bulb1.OnOff == Off")
setrules evaluate the allow condition against the state before the write. A write is blocked if any matching rule's allow condition is false.
get Rule — Periodic AutomationA get rule runs in the background every second. When the condition becomes
true, the listed assignments execute once (one-shot). They will not re-trigger
until the condition first goes false and then becomes true again.
get("condition", "key1=value1; key2=value2; ...")
| Argument | Role |
|---|---|
| First — condition | Boolean expression evaluated against current memory state |
| Second — assignments | Semicolon-separated key=value pairs to write when condition is true |
// When Bulb1 temperature reaches 80 or above, turn it off automatically
get("Bulb1.Temp >= 80", "Bulb1.OnOff=Off")
// When both temperatures are in range, reset both bulbs
get("(Bulb1.Temp >= 0) && (Bulb2.Temp >= 0)", "Bulb1.OnOff=Off; Bulb2.OnOff=Off")
The condition must transition false → true to re-execute. Once fired, assignments will not repeat until the condition resets.
Comparison operators (both rule types):
| Operator | Description |
|---|---|
== |
Equal (string or numeric) |
> |
Greater than |
< |
Less than |
>= |
Greater than or equal |
<= |
Less than or equal |
Logical operators (combine conditions with parentheses):
| Operator | Description |
|---|---|
&& |
Logical AND |
\|\| |
Logical OR |
Always wrap each sub-condition in parentheses when combining:
(Bulb1.Temp >= 80) && (Bulb2.Temp >= 80)
(Bulb1.OnOff == Off) || (Bulb2.OnOff == Off)
.rule File Example// ── SET rules (write guards) ──────────────────────────────────────────
// Block turning Bulb1 off unless temperature has reached the target
set("Bulb1.OnOff == Off", "Bulb1.Temp >= 80")
// Block turning Bulb2 off unless both bulbs are warm enough
set("Bulb2.OnOff == Off", "(Bulb1.Temp >= 80) && (Bulb2.Temp >= 80)")
// ── GET rules (periodic automation) ──────────────────────────────────
// Auto-turn off when temperature exceeds limit
get("Bulb1.Temp >= 100", "Bulb1.OnOff=Off")
// Reset both bulbs once temperatures are both safe
get("(Bulb1.Temp >= 0) && (Bulb2.Temp >= 0)", "Bulb1.OnOff=Off; Bulb2.OnOff=Off")
For logic that depends on flow state, express conditions directly inside Flow steps instead of a rule file:
[FlowStep(2)]
public void MonitorTemperature(BulbModel model)
{
if (model.Temp.Value >= model.TargetTemp.Value)
{
model.OnOff.Set("Off");
handler?.Next();
}
}
Port.Set("Bulb1.OnOff", "Off") called
↓
set rule evaluated (write guard)
├─ allow condition true → write proceeds → Package setter called → Hardware responds
└─ allow condition false → write BLOCKED, error returned
Every 1 second (background)
↓
get rule condition evaluated
├─ condition true (first time) → assignments executed → Port.Set called internally
└─ condition false or already fired → no action
Rule Scripts operate independently of Flows. Conditions can be modified at any time by editing the
.rulefile — no code recompilation required.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 1 NuGet packages that depend on portdic:
| Package | Downloads |
|---|---|
|
Port.SDK
Package Description |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.99 | 93 | 6/14/2026 |
| 1.1.98 | 97 | 6/9/2026 |
| 1.1.97 | 102 | 5/25/2026 |
| 1.1.96 | 118 | 4/26/2026 |
| 1.1.95 | 115 | 4/16/2026 |
| 1.1.94 | 122 | 4/14/2026 |
| 1.1.93 | 105 | 4/12/2026 |
| 1.1.92 | 116 | 4/9/2026 |
| 1.1.91 | 118 | 4/8/2026 |
| 1.1.90 | 121 | 4/6/2026 |
| 1.1.89 | 109 | 4/5/2026 |
| 1.1.88 | 116 | 4/1/2026 |
| 1.1.87 | 116 | 3/31/2026 |
| 1.1.86 | 118 | 3/29/2026 |
| 1.1.85 | 113 | 3/24/2026 |
| 1.1.84 | 113 | 3/23/2026 |
| 1.1.83 | 109 | 3/18/2026 |
| 1.1.82 | 109 | 3/17/2026 |
| 1.1.81 | 112 | 3/15/2026 |
| 1.1.80 | 120 | 3/10/2026 |