Table of Contents

Program and Hosting

Program.cs is the entire host of the HiNC web service: one class whose Main 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. BuildApp is public and returns the built application without starting it, and RunApp runs one, so the build phase and the run phase are separable. Main applies HINC_COREDUMP_FILTER on Linux when it is set, then calls BuildApp, AnnounceWhenStarted and RunApp; another project can host the application through the same members instead — see Hosting from another project.

HiAPI Lifetime

The host brackets its whole life in one pair of HiAPI calls.

AppBegin(API) runs once during BuildApp, after the service container is built and before the request pipeline is configured. It logs in every licence License holds, initialises the display engine (DispEngine), and opens the SQLite step-cache database at the path it is given, publishing it as SqliteStepStorage's default instance. The overload the webservice calls takes the library's host configuration, LocalAppConfig, with the cache-database path set in it, and a logger; the host passes a logger of category Hi.HiNcKits.LocalApp, so the library's own lines — licence, OpenGL availability, step storage — are filed under its name rather than the host's.

AppEnd(API) first closes every native machining pipeline still alive — a runner that was never disposed leaves its own behind — cancels the steps left in it and waits for its threads to finish before deleting it, so no pipeline thread is still inside the native core when the process exits. It then 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.

AppEnd runs last, once nothing that calls into the native core is left, and a static latch guarantees it runs at most once. Two paths reach it:

  • The end of RunApp, after app.Run() returns — the host has stopped serving and disposed every singleton (the display engines, the project service with its play and workpiece trees) — the ordinary graceful stop. The call sits in a finally, so a Run that throws, such as one whose port is already bound, still reaches it.
  • AppDomain.CurrentDomain.ProcessExit — the fallback for an exit that bypasses RunApp's tail, such as the licence watchdog's Environment.Exit; a second caller waits for the first teardown to finish.

IHostApplicationLifetime.ApplicationStopping only closes the rendering intake (RenderingService.BeginShutdown): it is the START of the stop, with clients still connected, so tearing the core down there would leave later native calls to run into a torn-down core. Console.CancelKeyPress (Ctrl+C, Ctrl+Break) cancels the immediate termination and asks the lifetime for a graceful stop, so the first path runs. On Windows, closing the console window raises none of these, ProcessExit included, so the host handles that signal itself: it asks for the same graceful stop and waits for RunApp's tail, at most four seconds from the close — Windows ends the process once the handler returns, and about five seconds after the close in any case.

The cache-database path is Cache/{CacheDbId}.db beneath the admin directory. CacheDbId is the HiNC:CacheDbId configuration value when one is set and the hosting environment name otherwise. Supplying a path matters: SqliteStepStorage 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 --environment names get different cache files with no further configuration.

Startup Order

Three registrations run before the builder exists, because each seeds a table that is read — or frozen — the moment anything else touches it:

  • LocalProjectService.Reg() fills XFactory's default generator table with every type the simulation pipeline may deserialise, which is what every project XML read resolves against.
  • UserConfig.Reg() 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 UserService resolution fails once one exists.
  • Lang is appended to StringLocalizer'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.

BuildApp then creates the builder — on ContentRootPath when that is set — and calls ConfigureBuilder, when set, before it reads a setting or registers a service. Both hooks are unset when the web service runs on its own; Hosting from another project covers them.

After the container is built and before the pipeline is assembled, the host subscribes the unhandled-exception and unobserved-task hooks, wires the native core's log output into the application logger through CppLogUtil, sets the cache identifier, calls AppBegin, and then runs Seed(API) over the admin Resource root. The seeder copies in only the marked .default items, leaves unmarked items alone as user property, and skips the whole pass when its version stamp already matches the shipped resource assembly.

Registered Services

Every application service is registered as a singleton. The repository contains no AddScoped and no AddTransient 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.

Four registrations are worth reading closely:

  • UserService 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 XFactory.
  • ProxyConfig is bound by hand into a plain singleton from the ProxyConfig configuration section. A separate services.Configure<ProxyConfig> call also registers it through the options system, but nothing resolves IOptions<ProxyConfig>; the plain singleton is what ProxyProjectService and the startup code receive. That binding happens once at startup and is not reloaded.
  • ProxyProjectService is registered twice — once under its own type and once as IProjectService — so the container builds one instance per registration. Both hold nothing but the same two injected singletons, LocalProjectService and ProxyConfig, which is where all the state lives.
  • AuthConfig is bound from the Auth section and registered as an instance, so the authentication controller and the pipeline decision below read the same object. See Login and Authentication for what it switches on.

Six singletons are resolved eagerly at the end of BuildApp 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.

Kestrel is configured with AllowSynchronousIO = true, which permits handlers to write to the response body synchronously.

Controllers

Controllers are added with AddControllers(), with the web service's own assembly registered as an explicit application part, and reached by MapControllers() at the end of the pipeline. Every controller in that assembly is attribute-routed, and every route template begins with api/ — most as api/[controller], the rest as an explicit kebab-case path such as api/mech/machine-tool or api/execution/cl-strip. No controller route sits outside api/, which is what makes the fallback rules below safe: a GET under api/ that no route serves answers 404, and everything else is the SPA. A project hosting the web service has its own controllers served beside these, since MVC takes the entry assembly as an application part by default; the rule holds for them only while their routes begin with api/ as well.

One controller is exempt from the login gate: the authentication controller carries [AllowAnonymous] so it stays reachable when the fallback policy locks everything else.

SignalR Hubs

AddSignalR() is called without protocol configuration, and eight hub routes are mapped:

Route Hub type What it carries
/renderingHub RenderingHub server-rendered canvas frames and the input that drives them, one display engine per connection
/shellMessageHub ShellMessageHub session-level routine and lifecycle messages
/ncDiagnosticHub NcDiagnosticHub NC-pipeline diagnostics
/stepDiagnosticHub StepDiagnosticHub step-anchored diagnostics
/ncManipulationDiagnosticHub NcManipulationDiagnosticHub NC-manipulation diagnostics — writeback conversion and optimisation
/executionStatusHub ExecutionStatusHub ExecutionStatusUpdated, SessionCursor and SessionStatusMessage, broadcast to all clients; the client-callable GetExecutionStatus() answers the caller alone, on the same ExecutionStatusUpdated name
/clStripHub ClStripHub strip-chart display range, selected and entered step, and throttled data-update hints
/cleanupHub CleanupHub index keys, and optional follow-up action keys, that a client registers against its connection with Add; 90 seconds after the connection drops, the sweep evicts the keys no live connection holds by then — see below

The four message hubs share one base class and one contract — the client calls GetMessages(limit) and the server answers on MessagesUpdate — 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.

The cleanup hub keeps its registry outside the hub instance. SignalR builds a fresh hub instance for every invocation, so the key dictionary Add writes to is a static registry bucketed by connection id. The disconnect handler removes its own connection's bucket and hands it to a sweep that runs 90 seconds later and evicts from IndexService every key in it that no live connection holds by then and that no later disconnect has claimed (removing a registered action key from the index too, and invoking its object when it is an Action); the grace exists because a reconnect is a new connection id, and the client registers its keys again when it reconnects. The client does not rely on the hub alone: useCleanupHub.ts keeps its own set of registered keys and posts each one to /api/Index/Remove before the component unmounts, then stops its connection — so a key replaced while the page stays up, or a host whose hub never connected, is still released.

JSON Serialisation

The controllers' serializer options carry two settings, both applied through AddJsonOptions.

JsonNumberHandling.AllowNamedFloatingPointLiterals. Several engine values use double.PositiveInfinity 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. System.Text.Json 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 "Infinity", "-Infinity" and "NaN". 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 — "NaN" included — to the fallback its caller passed, which is positive infinity for Max Feed Per Tooth and Preferred Force. Numeric Input/Output covers the client half of that boundary, and NC Optimization Option Panel (NC Optimization Config) 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.

JsonStringEnumConverter. 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.

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.

The Request Pipeline

The order the middleware is added in is load-bearing, and the host sets it explicitly rather than accepting the default arrangement:

  1. Forwarded headers first, honouring X-Forwarded-For and X-Forwarded-Proto 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. Right after them, repeated leading slashes in the path are merged into one: a base URL that ends in a slash, joined to /api/..., asks for //api/..., which no route matches, so every later stage — the cross-site guard included — reads the path a route is matched against.
  2. The cross-site guard, only while the login gate is off: a request its browser marks as coming from any page but the service's own — another site, or another port of the same host — is answered 403 here — see Login and Authentication.
  3. Swagger — the Swashbuckle document and its UI, both middleware. MapOpenApi beside them registers the framework's own document as an endpoint instead, so that one is reached at step 7 with everything else that is routed.
  4. Default files, then static files, serving the built SPA and everything else physically present in the web root. When the web root is not the build output's own wwwroot — a dotnet run from the project folder — the host layers that output folder beneath it, so the doc site HiNc-Resource ships there (/HiAPI-docsite/, the menu bar's Help → Documentation) is served in both layouts; the web root wins on any path both hold.
  5. UseRouting() — placed here on purpose. 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.
  6. Authentication, then authorization. Both are no-ops when the login gate is off, because no scheme and no fallback policy are registered in that case.
  7. Endpoints — controllers, the eight hubs, the /api fallback, and the two SPA fallbacks.

Ahead of the whole list, the framework's host filtering answers 400 to a request addressed to a host name AllowedHosts does not admit; see Configuration for what it admits. There is no CORS stage — see CORS — and no HTTPS-redirection stage, and no HSTS header is sent: a redirect to HTTPS belongs to the deployment's proxy, and an instance that must serve HTTPS only binds only an HTTPS endpoint.

A request answered before step 6 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 fallbacks are endpoints rather than middleware, so they do reach it. The two SPA fallbacks are marked AllowAnonymous — 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; the /api fallback is not, so with the gate on an unsigned call to a route that does not exist answers 401.

The SPA Fallback

A GET or HEAD under api/ that no route serves does not reach the shell: the api/{**rest} fallback answers it 404 with an application/problem+json body naming the method and the path. Like the SPA fallbacks it takes GET and HEAD only, so any other method there still finds no endpoint and answers 405. A fallback is chosen only when no route matches, and the literal api segment ranks this one ahead of the bare fallback.

Two fallback registrations serve index.html:

  • util/file-explorer/{**location} — an explicit pattern with no file-name constraint.
  • The bare fallback, for everything else outside api/.

The bare form's implicit route pattern carries a nonfile 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.

Serving the SPA

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 wwwroot — 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.

The front-end build script runs the i18n census, the glossary lint and the engine-message lint before invoking the Quasar build, so a build that fails any of those checks produces no new bundle.

The menu bar's version mark, the Help menu's items after the Log Viewer, the menu bar's cloud licence badge and the last boot file come from the module the build-time product alias resolves to: the web service's own, unless the build names another — see Hosting from another project.

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 /api, /swagger, /HiAPI-docsite, /renderingHub, /executionStatusHub, /clStripHub, /cleanupHub, and /sessionMessageHub — 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.

OpenAPI and Swagger

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.

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 /swagger and its document at /swagger/{version}/swagger.json — v3.1 at present, so the document is /swagger/v3.1/swagger.json, and the UI always opens the current one. The framework's own document is published at /openapi/v1.json.

Schema ids differ between the two documents. The Swashbuckle document keys them 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 its generator throws — which surfaces as an HTTP 500 from the document route rather than as a startup failure. The framework document is registered with no such option: it names schemas by simple type name and merges same-named types without an error, so a route whose request type shares a name with another controller's advertises the other body. The machine-tool, spindle-capability, workpiece-material and cutting-parameter loaders all show the object-management load body there, and the file rename shares its schema name with the equipment topology's { name } rename body, so one of those two routes advertises the other's. The Swashbuckle document is the one to read request shapes from; Driving the Web Service over HTTP lists the true bodies of the routes the framework document gets wrong.

The Swagger UI and its document are middleware ahead of authorization and answer without a session. The framework document is a routed endpoint and sits behind the login gate like any controller.

XML documentation comments are folded in, each file from the application's base directory: the web service's own assembly's, the entry assembly's when that is another one — a project hosting the web service — and those of every assembly the web service references whose name starts with Hi. 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 first two reads have no such catch-all.

CORS

The host registers no CORS policy, so no answer carries an Access-Control-Allow-Origin header and a browser lets no page from another origin read one. Nothing needs it: the SPA is served from the same origin as the API and the hubs, and the Quasar dev server reaches the backend through its own proxy. CORS governs reading, not sending — a page elsewhere can still send the requests a browser sends without a preflight, and SignalR's WebSocket handshake is not subject to CORS at all — so it is no defence on its own: the cross-site guard at step 2 refuses those requests while the login gate is off, and while it is on the Lax cookie stays off a cross-site page's requests.

Logging

The default providers are cleared and two are added: a console provider, and DailyFileLoggerProvider, which writes one file per instance and per day, log-{environment}-{yyyy-MM-dd}.txt, in the directory Logging:File:Directory names — logs by default, a relative path being taken from the process's current working directory — and deletes this instance's files older than Logging:File:RetainDays, 30 by default. The same provider instance is registered as a singleton, which is how the log endpoints read today's file through it. It swallows every I/O failure, on the principle that logging must not fault a request or a startup; Log Viewer Page covers its naming, its fallbacks and its writer.

The two providers are filtered independently. The file provider's alias is File, so a Logging:File:LogLevel section configures it alone, while the console follows the provider-neutral Logging:LogLevel section, which the shipped configuration sets to Warning. The host seeds five file rules in code — Information by default; Warning for Microsoft and System; Information for Microsoft.Hosting.Lifetime, so the host's start lines (listening address, environment, content root) reach the file; and Information for Microsoft.AspNetCore.HostFiltering, so a request that host filtering refuses with a bare 400 leaves the refused host name there — and it inserts them ahead of the rules built from configuration rather than adding them after. On a tie the later rule wins, so any configured Logging:File:LogLevel key overrides its seeded default, while a deployment whose configuration has no File section still gets the seeded levels rather than the console's. The shipped appsettings.json spells the first four of those levels out, and the Development overlay adds None for the development-time BrowserLink and browser-refresh middleware. The environment-variable provider reaches the same keys: Logging__File__LogLevel__Default=Warning leaves only warnings and worse in the file, apart from the host's Microsoft.Hosting.Lifetime lines and the host-filtering refusals, which their own, more specific Information rules still admit (set Logging__File__LogLevel__Microsoft.Hosting.Lifetime=Warning and Logging__File__LogLevel__Microsoft.AspNetCore.HostFiltering=Warning as well to drop them).

RunApp writes a startup summary line just before it runs the application — the instance (environment name), the build, the REST API version, the working directory, the admin directory and whether the login gate is on — so the file for the day the service starts has content before the first request arrives. That guarantee stops at midnight: the writer opens the file named for each entry's date 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.

Three more sources feed the log from the host itself. AppDomain.CurrentDomain.UnhandledException is logged Critical and TaskScheduler.UnobservedTaskException Error, so a crash on a background thread or a faulted task nobody awaited leaves a line. The native core's log function is routed into a logger of category Hi.Native, at a level read off each line's wording: a licence failure is Critical; a refused NC composition, no usable GL, a shader that will not build, an EGL display that will not open or initialise, and a GL debug message of high severity or error type are Error; Mesa software rasterisation, the picking-table ceiling, medium-severity GL messages, a line asked wider than the driver draws and a drawing skipped for its primitive mode are Warning; the OpenGL context record, the line width range and frame statistics stay Information. And AppBegin's logger, above, carries the library's licence abort line at Critical.

The host never disposes a provider registered as an instance, so the shutdown path handles this one itself: it flushes the queue just before AppEnd, so a crash inside the native teardown still leaves the line announcing it, and shuts the writer down after AppEnd — in RunApp's exit path and in the ProcessExit handler alike — after which lines are written synchronously.

Configuration

Configuration is the framework's default chain, its files read from the content root: appsettings.json, then appsettings.{Environment}.json, then {ApplicationName}.settings.json and {ApplicationName}.settings.{Environment}.json — named after the entry assembly; the web service ships no such file — then environment variables, then command-line arguments. A project hosting the web service can add sources of its own; see Hosting from another project. The keys this host reads are:

Key What it controls
Logging:LogLevel the console provider's levels
Logging:File:LogLevel the log file's levels, over the defaults the host seeds in code
Logging:File:Directory the log file's directory; logs under the working directory by default
Logging:File:RetainDays how many days of this instance's log files are kept; 30 by default, 0 keeps all
AllowedHosts host filtering; absent from the shipped file. While the login gate is off and the key is not set, the host admits only the loopback names localhost, 127.0.0.1 and [::1]; with the gate on, or once the key is set, the framework's own rule applies — the configured names, or any name when none is configured
Kestrel:Endpoints the addresses the server binds
ProxyConfig:AdminDirectory the admin working root, under which Resource and Cache live
Auth the optional login gate, and the name of its cookie, Auth:CookieName; off as shipped, on in the Development overlay
HiNC:CacheDbId an explicit step-cache database id, overriding the environment name
HiNC:LicenseCapsForTest <N>,<stepCap>: tightens the two licence caps once at boot — the resolution cap ratio and the step cap, a 0 leaving that cap as it is — so an acceptance instance runs under a cap. Tighten-only: it never loosens what the licence source set, and no endpoint sets a cap at runtime. The start-up log names the caps in force, at Warning when the switch took and at Error when it did not

The Development overlay repeats the logging and admin-directory sections — its logging section adding two Logging:File:LogLevel keys of its own — and turns the login gate on with one credential entry, where the shipped file leaves it off. Because that entry is public, it also sets AllowedHosts to the three loopback names, which the gate-on default would otherwise open to any name. It declares no Kestrel section, so the listening address is the same under either environment.

Kestrel Endpoints Win

Kestrel:Endpoints is the authoritative source of the listening address. When it declares endpoints, Kestrel binds those and ignores addresses supplied any other way — ASPNETCORE_URLS, --urls, and the launch profile's applicationUrl alike — logging that it is overriding them. The shipped configuration declares one HTTP endpoint on loopback port 5000. The http launch profile names the same address, so the override is invisible there; the https 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.

To move a local instance to another port, set the Kestrel__Endpoints__Http__Url environment variable, which the environment-variable provider maps onto the same key. Setting ASPNETCORE_URLS alone changes nothing.

The endpoint binds localhost, 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. Either way the login gate, off as shipped, has to be turned on. Until it is, the loopback-only host list answers 400 to a widened endpoint's callers from elsewhere, while a proxy on the same machine that forwards to localhost can pass every caller through with no session; turning the gate on lifts the host restriction unless the deployment sets AllowedHosts. A proxy has to mount the service at / — the SPA addresses the API, the hubs and its own assets from the root — and pass WebSocket upgrades on. An HTTPS endpoint that Kestrel serves itself needs its Certificate configured: without one, Kestrel falls back to the ASP.NET Core development certificate, which only a development machine holds, and otherwise cannot bind the endpoint, ending the process at start-up.

Environment and Working Directory

The environment name reaches further here than usual. Both launch profiles set it to Development, which selects the Development overlay, names the instance's log file, and — unless HiNC:CacheDbId overrides it — names the step-cache database file.

Two paths follow the process's current working directory rather than the content root: the file logger's directory, while Logging:File:Directory is relative or left at its logs default, 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. The web service never moves its working directory itself. A project hosting it that keeps them in a folder of its own moves the working directory before calling BuildApp, and sets ContentRootPath so that the settings files and wwwroot are still read from the program folder. The installed HiNC keeps both in a data folder of its own; see Installing HiNC on Windows.

Hosting from another project

Another project can host the web service instead of running its Main. It references the web service project, and its own Main sets the hooks it needs, then takes the steps this Main takes from BuildApp on. The class is HiNC_2025_webservice.Program; a hosting project whose own entry class is also named Program refers to it by that full name or through a using alias.

The two hooks are static properties, and both default to null, which is how the web service runs on its own:

  • ContentRootPath — the folder appsettings*.json and wwwroot are read from. null leaves the framework's default: the working directory, or --contentRoot on the command line. A value set here wins over --contentRoot, and the content root cannot change once the builder exists.
  • ConfigureBuilder — called with the WebApplicationBuilder right after BuildApp creates it, before BuildApp reads any setting or registers any service: the place for the hosting project's own configuration sources and services. A source added there comes after the command line, so it wins over every default source unless the environment variables and the command line are added again after it. A logging provider added there does not survive: BuildApp clears the providers right after the call and adds its console and file loggers.

Then, in order:

  1. Program.BuildApp(args) builds the application without starting it. What needs only the built application — middleware, a project to load, lifetime callbacks — goes between this call and the last; middleware added to it runs after step 6 of the request pipeline, ahead of the endpoints.
  2. Optionally, Program.AnnounceWhenStarted(app, version, settingsFile): once the server listens, it prints the given version and settings file, the addresses and the working directory to the console, with a line saying that closing the window stops HiNC, and on Windows puts the address in the console window's title. Under systemd — where INVOCATION_ID is set — it leaves out the addresses and that line, since the output goes to the journal, nobody opens the site at its local address there, and there is no window to close. It opens no browser. The web service's own Main passes the HiNc assembly version and appsettings.json — or, where the content root has none, appsettings.{Environment}.json.
  3. Program.RunApp(app), rather than app.Run(): it writes the startup summary line, runs, then ends the native core and closes the log file. Without it the core is ended only when the process exits normally, which an exception out of Run skips, leaving the licence seat taken.

The hosting project's own controllers are served beside the web service's, because MVC takes the entry assembly as an application part. BuildApp also reads the entry assembly's XML file, so their documentation shows in Swagger when the project builds with GenerateDocumentationFile. A settings file named after the entry assembly, {ApplicationName}.settings.json in the content root, joins the configuration chain after the web service's appsettings files with no code at all. ExecutionStatusService.StatusRunning and ExecutionStatusService.StatusPaused are public constants: a hosting project that must not act while a run is in progress or paused compares against them, so a rename breaks its build rather than its check.

The front end's counterpart is the build-time alias product. It resolves to the web service's own module — the menu bar's version mark, the items the Help menu appends after the Log Viewer (none), the menu bar's cloud licence badge (none) and a last boot file (empty) — unless the SPA is built with HINC_PRODUCT_SPA set to the absolute path of a folder with the same exports, VersionBadge, HelpMenuItems and CloudStatusBadge; see Web Service SPA Source Tree.

The installed HiNC on Windows is the web service hosted this way: its host adds the installation's data folder, its self-update and a second licence source — a cloud account, signed in on a page of its own before the service starts, in place of the Sentinel key the web service uses on its own — see Installing HiNC on Windows, Updating HiNC on Windows and Signing In with a Cloud Account.

Source Code Path

See HiNC App Anatomy for git repository links.

Web Application

HiNC-2025-webservice (Quasar CLI SPA):

  • Program.cs — the whole subject of this page: Main, the ContentRootPath and ConfigureBuilder hooks, AnnounceWhenStarted and RunApp, the pre-builder registrations, every service registration, the JSON and Swagger options with the entry assembly's XML read, the host-filtering default that follows the login gate, the pipeline order with the build output's wwwroot layered under the web root, the eight MapHub calls, the /api fallback and the two SPA fallbacks, the AppBegin / AppEnd pair and the four shutdown triggers; also the file log provider's creation, its seeded default rules, the startup summary line, the two exception hooks, the Hi.Native level mapping and the flush and shutdown around the native teardown.
  • appsettings.json — the shipped listening address, the admin directory, the login section (off, no account) and the Logging:File section; no AllowedHosts key.
  • appsettings.Development.json — the Development overlay, which declares no Kestrel section, its two Logging:File:LogLevel keys, and the login section that turns the gate on with one account.
  • Properties/launchSettings.json — the two launch profiles whose applicationUrl the Kestrel section overrides.
  • Common/DailyFileLoggerProvider.cs — the per-instance, per-day file logger under the File alias, which the log endpoints read back through.
  • Common/AuthConfig.cs — the shape bound from the Auth section.
  • Common/AuthController.cs — the anonymous carve-out that keeps login reachable under the fallback policy.
  • Common/CrossSiteRequestGuard.cs — pipeline step 2, added only while the login gate is off.
  • Common/NamedRootResolver.cs — resolves the admin, project and resource roots the seeder and the file endpoints work against.
  • Common/CleanupHub.cs — the cleanup hub, and the per-connection registry whose buckets its disconnect handler hands to the deferred sweep.
  • Common/IndexController.cs — the index remove endpoint the client calls before unmount.
  • Disp/RenderingHub.cs — the canvas hub.
  • Execution/SessionSinkHub.cs — the shared base and the four per-sink message hubs.
  • Execution/ExecutionStatusHub.cs — the status, cursor and session-message hub.
  • Execution/ClStripHub.cs — the strip-chart hub.
  • Execution/ExecutionStatusService.cs — the public StatusRunning and StatusPaused constants a hosting project compares a run's state against.
  • Environments/UserConfig.cs — the preference type registered before the builder is created.
  • Environments/ProjectController.cs — the log endpoints that read today's file through the provider.
  • wwwroot-src/src/composables/useCleanupHub.ts — the client's own key set and the index-remove calls that release it.
  • wwwroot-src/src/api/mission.ts — the option-snapshot reader that maps the named-literal strings back to numbers, and the string-bodied setters that send them.
  • wwwroot-src/quasar.config.ts — the build output directory that puts the bundle in the web root, the history router mode, the product alias with its HINC_PRODUCT_SPA override, and the dev-server proxy list.
  • wwwroot-src/src/product/index.ts — the product module's exports, each the web service's own choice when it runs on its own.
  • wwwroot-src/package.json — the build script that runs the i18n lint before the Quasar build.

HiAPI Engine

  • HiNc/HiNcKits/LocalApp.cs — AppBegin and AppEnd: licence log-in and log-out, display-engine start and finish, the step-storage open and dispose, and the close of the machining pipelines left alive that AppEnd runs first; also the licence abort line it logs at Critical on the logger it is given.
  • HiMech/MachiningProcs/MachiningParallelProc.cs — the native machining pipeline a runner plays through, and the close, drain and delete of every pipeline still alive that AppEnd calls.
  • HiNc/HiNcKits/HiNcHost.cs — the cache-database identifier the host assigns at startup.
  • HiNc/HiNcKits/ProxyConfig.cs — the admin-directory setting and its own default.
  • HiNc/HiNcKits/ResourceSeeder.cs — the marked-defaults seeding pass and its version stamp.
  • HiNc/SqliteUtils/SqliteStepStorage.cs — the step cache, and the per-user default path that makes an explicit per-instance path necessary.
  • HiNc/MachiningProcs/ProxyProjectService.cs — the project service registered under two service types, holding only the singletons it is given.
  • HiGeom/Common/StringLocalizer.cs — the static extended type list, and the per-instance resource-manager list each localizer builds on its own first lookup.

See Also

  • Session State — what survives a project change inside the singletons this host registers, and what is rebuilt
  • Login and Authentication — the Auth section this host binds, and the cookie scheme and fallback policy it registers when the gate is on
  • Rendering Canvas on Web Service Application — the canvas hub this host maps, and the per-connection display engine behind it
  • Log Viewer Page — the page that reads the file this host's log provider writes, and what that file holds
  • Main Panel — the menu bar whose version mark, Help items after the Log Viewer and cloud licence badge come from the product module, which a hosting project can replace with its own
  • Driving the Web Service over HTTP — the contract this host publishes, read from a script's side: sign-in, the reply envelope and the known schema errors
  • Installing HiNC on Windows — the installed HiNC, this web service hosted with the installation's data folder and self-update, from the operator's side