Archived
500 lines
33 KiB
HTML
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 "{query}"">
|
|
<meta name="loc:searchNoResults" content="No results for "{query}"">
|
|
<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<ProxyConfig></code> call also registers it through the options
|
|
system, but nothing resolves <code>IOptions<ProxyConfig></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
|
|
“everything else is the SPA” 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 “no limit” 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>"Infinity"</code>, <code>"-Infinity"</code> and <code>"NaN"</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>"NaN"</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>
|