This repository has been archived on 2026-10-11. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
HiNC-2025-win-desktop-Deploy/App/wwwroot/HiAPI-docsite/anatomy/platform/program-and-hosting.html
T
2026-09-09 17:35:23 +08:00

500 lines
33 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Program and Hosting | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Program and Hosting | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-ProgramAndHosting">
<h1 id="program-and-hosting">Program and Hosting</h1>
<p><code>Program.cs</code> is the entire host of the HiNC web service: one class whose <code>Main</code> builds an ASP.NET
Core application and runs it. It presents no screen of its own — it is the process that starts and
stops HiAPI, registers every service the controllers and hubs resolve, maps those controllers and
the eight SignalR hubs, serves the built Quasar SPA out of the web root, and falls back to the SPA
shell for every URL the Vue router owns. <code>BuildApp</code> is public and returns the built application
without starting it, so the build phase and the run phase are separable; <code>Main</code> calls it, writes one
startup line to the log, and runs.</p>
<h2 id="hiapi-lifetime">HiAPI Lifetime</h2>
<p>The host brackets its whole life in one pair of HiAPI calls.</p>
<p><a class="xref" href="../../api/Hi.HiNcKits.LocalApp.html#Hi_HiNcKits_LocalApp_AppBegin_">AppBegin</a><small>(API)</small> runs once during <code>BuildApp</code>, after the
service container is built and before the request pipeline is configured. It logs in every licence
<a class="xref" href="../../api/Hi.Licenses.License.html">License</a> holds, initialises the display engine (<a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a>), and
opens the SQLite step-cache database at the path it is given, publishing it as
<a class="xref" href="../../api/Hi.SqliteUtils.SqliteStepStorage.html">SqliteStepStorage</a>'s default instance. The overload the webservice calls takes a
logger and a cache-database path.</p>
<p><a class="xref" href="../../api/Hi.HiNcKits.LocalApp.html#Hi_HiNcKits_LocalApp_AppEnd_">AppEnd</a><small>(API)</small> unwinds the same set in reverse: it waits for
queued background CubeTree frees to drain so no native delete runs against a half-torn-down runtime,
disposes the step and identity storages, shuts the display engine down, and logs the licences out.</p>
<p>Three separate events can reach <code>AppEnd</code>, and a static latch guarantees it runs at most once:</p>
<ul>
<li><code>IHostApplicationLifetime.ApplicationStopping</code> — the ordinary graceful stop.</li>
<li><code>AppDomain.CurrentDomain.ProcessExit</code> — which still fires when a console window is closed outright.</li>
<li><code>Console.CancelKeyPress</code> — Ctrl+C and Ctrl+Break, which cancel the immediate termination and ask
the lifetime for a graceful stop instead, so the first path runs.</li>
</ul>
<p>The cache-database path is <code>Cache/{CacheDbId}.db</code> beneath the admin directory.
<a class="xref" href="../../api/Hi.HiNcKits.HiNcHost.html#Hi_HiNcKits_HiNcHost_CacheDbId">CacheDbId</a> is the <code>HiNC:CacheDbId</code> configuration value when one is set and
the hosting environment name otherwise. Supplying a path matters:
<a class="xref" href="../../api/Hi.SqliteUtils.SqliteStepStorage.html">SqliteStepStorage</a> given none falls back to a single per-user file, so several
instances on one account would otherwise share it. Two instances launched under different
<code>--environment</code> names get different cache files with no further configuration.</p>
<h2 id="startup-order">Startup Order</h2>
<p>Three registrations run before the builder exists, because each seeds a table that is read — or
frozen — the moment anything else touches it:</p>
<ul>
<li><code>LocalProjectService.Reg()</code> fills <a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a>'s default generator table with
every type the simulation pipeline may deserialise, which is what every project XML read resolves
against.</li>
<li><code>UserConfig.Reg()</code> adds the per-user preference file's own type to the same table; without it, a
persisted preference file cannot be read back and every <code>UserService</code> resolution fails once one
exists.</li>
<li><a class="xref" href="../../api/Hi.SessionCommands.Lang.html">Lang</a> is appended to <a class="xref" href="../../api/Hi.Common.StringLocalizer.html">StringLocalizer</a>'s extended type list, so
session-command titles resolve through that assembly's satellite resources. The type list is
static, but each localizer builds its own resource-manager list lazily on its first lookup and
keeps it, so a type added after a given localizer has been used never reaches that localizer.
Registering before the builder exists is what puts the addition ahead of every first lookup.</li>
</ul>
<p>After the container is built and before the pipeline is assembled, the host wires the native core's
log output into the application logger through <a class="xref" href="../../api/Hi.Common.CppLogUtil.html">CppLogUtil</a>, sets the cache
identifier, calls <code>AppBegin</code>, and then runs
<a class="xref" href="../../api/Hi.HiNcKits.ResourceSeeder.html#Hi_HiNcKits_ResourceSeeder_Seed_">Seed</a><small>(API)</small> over the admin <code>Resource</code> root. The
seeder copies in only the marked <code>.default</code> items, leaves unmarked items alone as user property, and
skips the whole pass when its version stamp already matches the shipped resource assembly.</p>
<h2 id="registered-services">Registered Services</h2>
<p>Every application service is registered as a <strong>singleton</strong>. The repository contains no <code>AddScoped</code>
and no <code>AddTransient</code> call, so no service this host registers has a per-request lifetime and any
state one of them holds is process-wide. Any shorter lifetime in the container is one of the
framework's own registrations rather than an application service.</p>
<p>Four registrations are worth reading closely:</p>
<ul>
<li><strong><code>UserService</code></strong> is built by a factory rather than by type, so that its configuration path is
assigned unconditionally — including when no preference file exists yet, which is the only way the
first save can create one. When the file does exist, the factory deserialises it through
<a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a>.</li>
<li><strong><code>ProxyConfig</code></strong> is bound by hand into a plain singleton from the <code>ProxyConfig</code> configuration
section. A separate <code>services.Configure&lt;ProxyConfig&gt;</code> call also registers it through the options
system, but nothing resolves <code>IOptions&lt;ProxyConfig&gt;</code>; the plain singleton is what
<a class="xref" href="../../api/Hi.MachiningProcs.ProxyProjectService.html">ProxyProjectService</a> and the startup code receive. That binding happens
once at startup and is not reloaded.</li>
<li><strong><a class="xref" href="../../api/Hi.MachiningProcs.ProxyProjectService.html">ProxyProjectService</a></strong> is registered twice — once under its own type and
once as <a class="xref" href="../../api/Hi.MachiningProcs.IProjectService.html">IProjectService</a> — so the container builds one instance per
registration. Both hold nothing but the same two injected singletons,
<a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> and <a class="xref" href="../../api/Hi.HiNcKits.ProxyConfig.html">ProxyConfig</a>, which is where all
the state lives.</li>
<li><strong><code>AuthConfig</code></strong> is bound from the <code>Auth</code> section and registered as an instance, so the
authentication controller and the pipeline decision below read the same object. See
<a class="xref" href="login-and-auth.html">Login and Authentication</a> for what it switches on.</li>
</ul>
<p>Six singletons are resolved eagerly at the end of <code>BuildApp</code> so they subscribe to their engine events
at startup rather than when the first client happens to connect: the CL-strip broadcast service, the
four per-sink message broadcast services, and the NC-program registry.</p>
<p>Kestrel is configured with <code>AllowSynchronousIO = true</code>, which permits handlers to write to the
response body synchronously.</p>
<h2 id="controllers">Controllers</h2>
<p>Controllers are added with <code>AddControllers()</code>, with the host's own assembly registered as an explicit
application part, and reached by <code>MapControllers()</code> at the end of the pipeline. Every controller in
the host assembly is attribute-routed, and every route template begins with <code>api/</code> — most as
<code>api/[controller]</code>, the rest as an explicit kebab-case path such as <code>api/mech/machine-tool</code> or
<code>api/execution/cl-strip</code>. No controller route sits outside <code>api/</code>, which is what makes the
&ldquo;everything else is the SPA&rdquo; fallback rule below safe.</p>
<p>One controller is exempt from the login gate: the authentication controller carries
<code>[AllowAnonymous]</code> so it stays reachable when the fallback policy locks everything else.</p>
<h2 id="signalr-hubs">SignalR Hubs</h2>
<p><code>AddSignalR()</code> is called without protocol configuration, and eight hub routes are mapped:</p>
<table>
<thead>
<tr>
<th>Route</th>
<th>Hub type</th>
<th>What it carries</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/renderingHub</code></td>
<td><code>RenderingHub</code></td>
<td>server-rendered canvas frames and the input that drives them, one display engine per connection</td>
</tr>
<tr>
<td><code>/shellMessageHub</code></td>
<td><code>ShellMessageHub</code></td>
<td>session-level routine and lifecycle messages</td>
</tr>
<tr>
<td><code>/ncDiagnosticHub</code></td>
<td><code>NcDiagnosticHub</code></td>
<td>NC-pipeline diagnostics</td>
</tr>
<tr>
<td><code>/stepDiagnosticHub</code></td>
<td><code>StepDiagnosticHub</code></td>
<td>step-anchored diagnostics</td>
</tr>
<tr>
<td><code>/ncManipulationDiagnosticHub</code></td>
<td><code>NcManipulationDiagnosticHub</code></td>
<td>NC-manipulation diagnostics — writeback conversion and optimisation</td>
</tr>
<tr>
<td><code>/executionStatusHub</code></td>
<td><code>ExecutionStatusHub</code></td>
<td><code>ExecutionStatusUpdated</code>, <code>SessionCursor</code> and <code>SessionStatusMessage</code>, broadcast to all clients; the client-callable <code>GetExecutionStatus()</code> answers the caller alone, on the same <code>ExecutionStatusUpdated</code> name</td>
</tr>
<tr>
<td><code>/clStripHub</code></td>
<td><code>ClStripHub</code></td>
<td>strip-chart display range, selected and entered step, and throttled data-update hints</td>
</tr>
<tr>
<td><code>/cleanupHub</code></td>
<td><code>CleanupHub</code></td>
<td>index keys, and optional follow-up action keys, that a client registers against its connection with <code>Add</code>; the disconnect sweep finds none of them — see below</td>
</tr>
</tbody>
</table>
<p>The four message hubs share one base class and one contract — the client calls <code>GetMessages(limit)</code>
and the server answers on <code>MessagesUpdate</code> — but they are deliberately separate routes rather than
one hub with a discriminator, so a client subscribes to exactly the sink it wants and an idle panel
means an idle hub rather than a lost connection.</p>
<p>The cleanup hub is the one whose registry does not outlive the call that fills it. SignalR builds a
fresh hub instance for every invocation, and the key dictionary <code>Add</code> writes to is an instance
member rather than shared state, so the dictionary the disconnect handler walks is a different,
empty one and releases nothing. Only <code>IndexService</code> — a singleton — is shared across those
instances. The client does not depend on the hub for the release: <code>useCleanupHub.ts</code> keeps its own
set of registered keys and posts each one to <code>/api/Index/Remove</code> before the component unmounts. That
is the path that actually frees an entry.</p>
<h2 id="json-serialisation">JSON Serialisation</h2>
<p>The controllers' serializer options carry two settings, both applied through <code>AddJsonOptions</code>.</p>
<p><strong><code>JsonNumberHandling.AllowNamedFloatingPointLiterals</code>.</strong> Several engine values use
<code>double.PositiveInfinity</code> as their &ldquo;no limit&rdquo; state — the optimizer's Max Feed Per Tooth and
Preferred Force among them — and the snapshots the SPA reads carry those doubles straight into the
response object. <code>System.Text.Json</code> refuses to write an infinite or NaN double as a JSON number, so
without this setting every such response would fault during serialisation instead of returning. With
it, those values travel as the JSON strings <code>&quot;Infinity&quot;</code>, <code>&quot;-Infinity&quot;</code> and <code>&quot;NaN&quot;</code>. The reading side
is not uniform: the numeric input widgets take all three spellings case-insensitively, while the
option-snapshot reader in the mission API recognises the two infinity spellings only and resolves
anything else non-numeric — <code>&quot;NaN&quot;</code> included — to the fallback its caller passed, which is positive
infinity for Max Feed Per Tooth and Preferred Force.
<a class="xref" href="../conventions/numeric-io-utilities.html">Numeric Input/Output</a> covers the client half of that boundary, and
<a class="xref" href="../execution/mission/NcOptOption-panel.html">NC Optimization Option Panel (NC Optimization Config)</a> the panel that leans hardest on it — including the two write
endpoints that take a string body so the same spelling survives the round trip.</p>
<p><strong><code>JsonStringEnumConverter</code>.</strong> Enums are read and written as their names rather than as integers,
which is what the API wrappers expect and what keeps the OpenAPI schema self-describing.</p>
<p>Both settings belong to the MVC controllers only. Hub payloads are serialised by SignalR's own
protocol, which is left at its defaults, so neither the named floating-point literals nor the string
enums extend to hub messages.</p>
<h2 id="the-request-pipeline">The Request Pipeline</h2>
<p>The order the middleware is added in is load-bearing, and the host sets it explicitly rather than
accepting the default arrangement:</p>
<ol>
<li><strong>Forwarded headers</strong> first, honouring <code>X-Forwarded-For</code> and <code>X-Forwarded-Proto</code> so that every
later stage sees the real client scheme. Both the known-proxy and the known-network lists are
cleared, so those headers are accepted from any caller.</li>
<li><strong>Swagger</strong> — the Swashbuckle document and its UI, both middleware. <code>MapOpenApi</code> beside them
registers the framework's own document as an <em>endpoint</em> instead, so that one is reached at
step 8 with everything else that is routed.</li>
<li><strong>Default files, then static files</strong>, serving the built SPA and everything else physically present
in the web root.</li>
<li><strong><code>UseRouting()</code> — placed here on purpose.</strong> An application that never calls it gets routing
inserted at the front of the pipeline instead, which would select the SPA fallback endpoint before
the static-file middleware ran; that middleware stands down once an endpoint is chosen, so a
directory URL under the web root would be answered by the Vue router's not-found view rather than
by the directory's own default document.</li>
<li><strong>CORS</strong>, applying the <code>AllowAll</code> policy described below.</li>
<li><strong>HTTPS redirection</strong>, skipped in the Development environment.</li>
<li><strong>Authentication, then authorization.</strong> Both are no-ops when the login gate is off, because no
scheme and no fallback policy are registered in that case.</li>
<li><strong>Endpoints</strong> — controllers, the eight hubs, and the two SPA fallbacks.</li>
</ol>
<p>A request answered before step 7 never reaches the authorization stage at all: the static-file
middleware and the Swagger middleware both sit ahead of it and short-circuit the request when they
match. The two SPA fallbacks are endpoints rather than middleware, so they do reach it, and both are
marked <code>AllowAnonymous</code> — which is what lets the SPA shell, carrying no data of its own, load and
show its own login view when the gate is on.</p>
<h3 id="the-spa-fallback">The SPA Fallback</h3>
<p>Two fallback registrations serve <code>index.html</code>:</p>
<ul>
<li><code>util/file-explorer/{**location}</code> — an explicit pattern with no file-name constraint.</li>
<li>The bare fallback, for everything else.</li>
</ul>
<p>The bare form's implicit route pattern carries a <code>nonfile</code> constraint that rejects any URL whose last
segment contains a dot. File Explorer deep links mirror real folder and file names into the URL, and
those names routinely contain dots, so reloading or pasting such a location needs the explicit
pattern. Genuine static assets cannot be shadowed by it, because the static-file middleware has
already run by the time either fallback is reached.</p>
<h2 id="serving-the-spa">Serving the SPA</h2>
<p>The Quasar build writes its output directly into the host's web root — the front end's build
configuration sets its distribution directory to the web service's <code>wwwroot</code> — so there is no copy
step between building the SPA and serving it. The router runs in history mode, which is what makes
the SPA fallback necessary: every client-side route is an extension-less URL the server has no
endpoint for.</p>
<p>The front-end build script runs the i18n census and glossary lint before invoking the Quasar build,
so a build that fails those checks produces no new bundle.</p>
<p>For front-end work the Quasar dev server runs on its own port and proxies the routes the backend owns
to the running service. That proxy list holds <code>/api</code>, <code>/swagger</code>, <code>/renderingHub</code>,
<code>/executionStatusHub</code>, <code>/clStripHub</code>, <code>/cleanupHub</code>, and <code>/sessionMessageHub</code> — a path the host maps
no hub on. The four per-sink message hubs are absent from it, so the Session Messages panels receive
no live messages through the dev server.</p>
<h2 id="openapi-and-swagger">OpenAPI and Swagger</h2>
<p>A single private constant holds the REST contract version. It is simultaneously the Swashbuckle
document group name — so it appears in the document's route — and the version the UI displays, which
is why it is defined in one place. It is raised by hand on a breaking REST change only, and is
deliberately not tied to the assembly version or to the project-file format version.</p>
<p>Both OpenAPI surfaces are exposed in every environment rather than behind a development gate, so an
automated caller always has a machine-readable contract. The Swashbuckle UI is served at <code>/swagger</code>
and its document at <code>/swagger/{version}/swagger.json</code>; the framework's own document is published
under <code>/openapi/</code>.</p>
<p>Schema ids are keyed on the full type name with the nested-type separator normalised. Without that,
two controllers' nested request types that share a simple name collide and the document generator
throws — which surfaces as an HTTP 500 from the document route rather than as a startup failure. XML
documentation comments are folded in from the host assembly and from every referenced assembly whose
name starts with <code>Hi</code>. Every read is skipped when its file is missing, and each referenced-assembly
read carries a catch-all of its own on top of that, so one whose file is present but unreadable is
ignored; the host assembly's read has no such catch-all.</p>
<h2 id="cors">CORS</h2>
<p>One policy, named <code>AllowAll</code>, permits any origin, any method and any header, and it is applied
globally. It does not allow credentials, so the response carries a wildcard origin and a cross-origin
browser call cannot use the session cookie. The SPA is served from the same origin as the API and the
hubs, so this constrains only callers hosted elsewhere.</p>
<h2 id="logging">Logging</h2>
<p>The default providers are cleared and two are added: a console provider, and a daily file provider
rooted at a <code>logs</code> folder beneath the process's current working directory. The file provider writes
one line per entry to <code>log-{yyyy-MM-dd}.txt</code> and swallows every I/O failure, on the principle that
logging must not fault a request or a startup.</p>
<p>The two providers are filtered independently. The console follows the <code>Logging:LogLevel</code> section of
configuration, which sets everything to Warning. The file provider carries its own code-set rules —
Information for the application's own categories, Warning for <code>Microsoft</code> and <code>System</code> — and
provider-specific rules take precedence over configuration's provider-neutral ones, so the file stays
useful while the console stays quiet.</p>
<p><code>Main</code> writes one Information line immediately after the application is built, so the file for the day
the service starts has content before the first request arrives. That guarantee stops at midnight: the
provider recomposes the file name from the clock on every append, and creates nothing ahead of time, so
a host left running into a new day has no file for it until the next entry is written.
<code>GET /api/project/logs</code> reads the same directory.</p>
<h2 id="configuration">Configuration</h2>
<p>Configuration is the standard chain: <code>appsettings.json</code>, then <code>appsettings.{Environment}.json</code>, then
environment variables, then command-line arguments. The keys this host reads are:</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>What it controls</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Logging:LogLevel</code></td>
<td>the console provider's levels</td>
</tr>
<tr>
<td><code>AllowedHosts</code></td>
<td>host filtering; shipped as a wildcard</td>
</tr>
<tr>
<td><code>Kestrel:Endpoints</code></td>
<td>the addresses the server binds</td>
</tr>
<tr>
<td><code>ProxyConfig:AdminDirectory</code></td>
<td>the admin working root, under which <code>Resource</code> and <code>Cache</code> live</td>
</tr>
<tr>
<td><code>Auth</code></td>
<td>the optional login gate</td>
</tr>
<tr>
<td><code>HiNC:CacheDbId</code></td>
<td>an explicit step-cache database id, overriding the environment name</td>
</tr>
</tbody>
</table>
<p>The Development overlay repeats the logging, admin-directory and authentication sections and declares
no <code>Kestrel</code> section, so the listening address is the same under either environment.</p>
<h3 id="kestrel-endpoints-win">Kestrel Endpoints Win</h3>
<p><code>Kestrel:Endpoints</code> is the authoritative source of the listening address. When it declares endpoints,
Kestrel binds those and <strong>ignores</strong> addresses supplied any other way — <code>ASPNETCORE_URLS</code>, <code>--urls</code>,
and the launch profile's <code>applicationUrl</code> alike — logging that it is overriding them. The shipped
configuration declares one HTTP endpoint on loopback port 5000. The <code>http</code> launch profile names the
same address, so the override is invisible there; the <code>https</code> profile also names an HTTPS address on
a second port, and that address is one of the ones Kestrel discards, so launching under it still
yields plain HTTP on 5000 and nothing else.</p>
<p>To move a local instance to another port, set the <code>Kestrel__Endpoints__Http__Url</code> environment
variable, which the environment-variable provider maps onto the same key. Setting <code>ASPNETCORE_URLS</code>
alone changes nothing.</p>
<p>The endpoint binds <code>localhost</code>, so the shipped configuration answers only on the machine it runs on.
Reaching it from elsewhere means either overriding that key or placing a reverse proxy in front,
which is the arrangement the forwarded-headers configuration handles.</p>
<h3 id="environment-and-working-directory">Environment and Working Directory</h3>
<p>The environment name reaches further here than usual. Both launch profiles set it to <code>Development</code>,
which selects the Development overlay, skips HTTPS redirection, and — unless <code>HiNC:CacheDbId</code>
overrides it — names the step-cache database file.</p>
<p>Two paths follow the process's current working directory rather than the content root: the <code>logs</code>
folder the file logger writes and the log endpoint reads, and the per-user preference file. An
instance launched from the project directory finds both where they are expected; one launched from
elsewhere puts them beside wherever it was started.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>Program.cs</code> — the whole subject of this page: the pre-builder registrations, every service
registration, the JSON and Swagger options, the CORS policy, the pipeline order, the eight
<code>MapHub</code> calls, the two SPA fallbacks, the <code>AppBegin</code> / <code>AppEnd</code> pair and the three shutdown
triggers.</li>
<li><code>appsettings.json</code> — the shipped listening address, the admin directory and the login section.</li>
<li><code>appsettings.Development.json</code> — the Development overlay, which declares no <code>Kestrel</code> section.</li>
<li><code>Properties/launchSettings.json</code> — the two launch profiles whose <code>applicationUrl</code> the <code>Kestrel</code>
section overrides.</li>
<li><code>Common/DailyFileLoggerProvider.cs</code> — the per-day file logger the log endpoint reads back.</li>
<li><code>Common/AuthConfig.cs</code> — the shape bound from the <code>Auth</code> section.</li>
<li><code>Common/AuthController.cs</code> — the anonymous carve-out that keeps login reachable under the fallback
policy.</li>
<li><code>Common/NamedRootResolver.cs</code> — resolves the admin, project and resource roots the seeder and the
file endpoints work against.</li>
<li><code>Common/CleanupHub.cs</code> — the cleanup hub, and the per-invocation dictionary its disconnect
handler walks.</li>
<li><code>Common/IndexController.cs</code> — the index remove endpoint the client calls before unmount.</li>
<li><code>Disp/RenderingHub.cs</code> — the canvas hub.</li>
<li><code>Execution/SessionSinkHub.cs</code> — the shared base and the four per-sink message hubs.</li>
<li><code>Execution/ExecutionStatusHub.cs</code> — the status, cursor and session-message hub.</li>
<li><code>Execution/ClStripHub.cs</code> — the strip-chart hub.</li>
<li><code>Environments/UserConfig.cs</code> — the preference type registered before the builder is created.</li>
<li><code>Environments/ProjectController.cs</code> — the log endpoints that read the daily file.</li>
<li><code>wwwroot-src/src/composables/useCleanupHub.ts</code> — the client's own key set and the index-remove
calls that release it.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — the option-snapshot reader that maps the named-literal strings
back to numbers, and the string-bodied setters that send them.</li>
<li><code>wwwroot-src/quasar.config.ts</code> — the build output directory that puts the bundle in the web root,
the history router mode, and the dev-server proxy list.</li>
<li><code>wwwroot-src/package.json</code> — the build script that runs the i18n lint before the Quasar build.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
<li><code>HiNc/HiNcKits/LocalApp.cs</code> — <code>AppBegin</code> and <code>AppEnd</code>: licence log-in and log-out, display-engine
start and finish, and the step-storage open and dispose.</li>
<li><code>HiNc/HiNcKits/HiNcHost.cs</code> — the cache-database identifier the host assigns at startup.</li>
<li><code>HiNc/HiNcKits/ProxyConfig.cs</code> — the admin-directory setting and its own default.</li>
<li><code>HiNc/HiNcKits/ResourceSeeder.cs</code> — the marked-defaults seeding pass and its version stamp.</li>
<li><code>HiNc/SqliteUtils/SqliteStepStorage.cs</code> — the step cache, and the per-user default path that makes
an explicit per-instance path necessary.</li>
<li><code>HiNc/MachiningProcs/ProxyProjectService.cs</code> — the project service registered under two service
types, holding only the singletons it is given.</li>
<li><code>HiGeom/Common/StringLocalizer.cs</code> — the static extended type list, and the per-instance
resource-manager list each localizer builds on its own first lookup.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="session-state.html">Session State</a> — what survives a project change inside the singletons this host
registers, and what is rebuilt</li>
<li><a class="xref" href="login-and-auth.html">Login and Authentication</a> — the <code>Auth</code> section this host binds, and the cookie scheme and
fallback policy it registers when the gate is on</li>
<li><a class="xref" href="../conventions/rendering-canvas-web-service.html">Rendering Canvas on Web Service Application</a> — the canvas hub this host maps, and the per-connection
display engine behind it</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>