Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript.NET Hosting
Axial.Hosting connects root Flow applications to .NET process and Generic Host lifecycle. It also adapts
Microsoft.Extensions.Logging to the explicit ILog service and provides fiber-defect logging.
dotnet add package Axial.Hosting
App works in console, desktop, test, and embedded applications without this package, Microsoft.Extensions.Hosting, or a dependency-injection container.
Standalone CLI or Script
Use DotNetApp.run when the application owns a console process but does not use Generic Host:
Show page setup
// Setup for the checked examples on this page.
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actualopen Axial
open Axial.Hosting
type AppError = | InvalidArguments of string
let describeError = function
| InvalidArguments message -> message
let application : Flow<string array, AppError, unit> =
flow {
let! args = Flow.env<string array, AppError>
if Array.isEmpty args then
return! Flow.fail (InvalidArguments "Supply at least one argument.")
}
// In a program, mark this [<EntryPoint>].
let main args =
(DotNetApp.run describeError args application).GetAwaiter().GetResult()
AxialHostingCore_12-platforms-and-hosting_06-dotnet.md_page.AppErrorInvalidArgumentsstringAn abbreviation for the CLI type . Basic Types
describeError: AppError -> stringmessage: stringapplication: Flow<string array,AppError,unit>Axial.Flow`3arraySingle dimensional, zero-based arrays, written int array, string array etc. Use the values in the module to manipulate values of this type, or the notation arr.[x] to get/set array values. Basic Types
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
args: string arrayAxial.Flowenv: Flow<'env,'error,'env>Reads the current environment as the successful flow value. Use this when the next step genuinely needs the whole environment value, for example when passing a request context to another helper. For a single dependency or configuration value, prefer Flow.envWith; it keeps the dependency local and makes the workflow easier to scan. A whose successful value is the current environment. let myFlow = Flow.env |> Flow.map (fun env -> env)
Microsoft.FSharp.Collections.ArrayModuleContains operations for working with arrays. See also F# Language Guide - Arrays.
isEmpty: 'T array -> boolReturns true if the given array is empty, otherwise false. The input array. True if the array is empty. Thrown when the input array is null. [| |] |> Array.isEmpty Evaluates to true [| "pear"; "banana" |] |> Array.isEmpty Evaluates to false This is an O(1) operation.
fail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
main: string array -> intGetAwaiter: unit -> Runtime.CompilerServices.TaskAwaiter<int>Gets an awaiter used to await this . An awaiter instance.
Axial.Hosting.DotNetAppStandalone .NET process integration for Flow applications that do not use Generic Host.
run: ('error -> string) -> 'env -> Flow<'env,'error,'value> -> Task<int>Runs a standalone application, translating Ctrl+C into coordinated stop and returning a process exit code.
GetResult: unit -> intEnds the wait for the completion of the asynchronous task. The object was not properly initialized. The task was canceled. The task completed in a state. The result of the completed task.
main [| "orders.csv" |] |> shouldEqual 0
main [||] |> shouldEqual 1
main: string array -> int(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
shouldEqual: 'a -> 'a -> unitDotNetApp.run installs a temporary Console.CancelKeyPress handler. Ctrl+C requests App.Stop(), waits for root
scope cleanup, removes the handler, and returns:
| Exit | Process code |
|---|---|
| Success | 0 |
| Typed failure only | 1 |
| Defect | 2 |
| Interruption | 130 |
A cleanup defect takes precedence when a cause also contains interruption.
It returns the code rather than calling Environment.Exit, so finally blocks and asynchronous finalizers are not
skipped.
Generic Host
Build the application as a Flow whose input is either IServiceProvider or an explicit environment constructed
from it:
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
open Axial
open Axial.Layers
open Axial.Hosting
type AppError =
| OrderError of string
static member describe = function OrderError message -> message
type IOrderRepository =
abstract ProcessPending: unit -> Result<unit, AppError>
type SqlOrderRepository() =
interface IOrderRepository with
member _.ProcessPending() = Ok()
type AppEnv =
{ Orders: IOrderRepository
Log: ILog }
let program : Flow<AppEnv, AppError, unit> =
flow {
let! orders = Flow.envWith _.Orders
return! orders.ProcessPending()
}
let builder = Host.CreateApplicationBuilder(args)
builder.Services.AddSingleton<IOrderRepository, SqlOrderRepository>()
|> ignore
// The layer turns the container into the explicit AppEnv the domain flow needs.
let appLayer : Layer<IServiceProvider, AppError, AppEnv> =
Layer.envWith (fun services ->
{ Orders = services.GetRequiredService<IOrderRepository>()
Log = services.GetRequiredService<ILoggerFactory>() |> MicrosoftLogging.fromFactory "App" })
let application : Flow<IServiceProvider, AppError, unit> =
program
|> Layer.provide appLayer
builder.Services
|> Hosting.addApp id AppError.describe application
|> ignore
builder.Build().Run()The registered FlowHostedService:
- Constructs the explicit environment when the host starts.
- Starts one root
App. - Connects
IHostApplicationLifetime.ApplicationStoppingto coordinated stop. - Logs success, typed failure, interruption, or defects through the host logger.
- Requests host shutdown when the finite root application completes.
- Waits for root cleanup from
StopAsync.
Use Hosting.addAppWith { StopHostOnCompletion = false } when the root Flow is one hosted participant and another
hosted service owns process completion.
IServiceProvider stays at the application edge. Prefer constructing an explicit record or providing a Layer before
domain workflows run. See Providing the environment.
Microsoft Logging as ILog
Create the explicit Axial logging service from an existing logger:
let axialLogFor (logger: Microsoft.Extensions.Logging.ILogger) : ILog =
MicrosoftLogging.create logger
axialLogFor: Extensions.Logging.ILogger -> ILoglogger: Extensions.Logging.ILoggerMicrosoftMicrosoft.Extensions.Logging.ILoggerRepresents a type used to perform logging. Aggregates most logging patterns to a single method.
ExtensionsLoggingAxial.PlatformService.ILogProvides synchronous access to workflow logging as an explicit service.
Axial.Hosting.MicrosoftLoggingAdapts Microsoft.Extensions.Logging loggers to Axial's explicit logging service.
create: Extensions.Logging.ILogger -> ILogCreates an Axial logger backed by a supplied Microsoft logger.
Or choose a category through a factory:
let axialLogFrom (loggerFactory: Microsoft.Extensions.Logging.ILoggerFactory) : ILog =
MicrosoftLogging.fromFactory "MyApp" loggerFactory
axialLogFrom: Extensions.Logging.ILoggerFactory -> ILogloggerFactory: Extensions.Logging.ILoggerFactoryMicrosoftMicrosoft.Extensions.Logging.ILoggerFactoryRepresents a type used to configure the logging system and create instances of from the registered s.
ExtensionsLoggingAxial.PlatformService.ILogProvides synchronous access to workflow logging as an explicit service.
Axial.Hosting.MicrosoftLoggingAdapts Microsoft.Extensions.Logging loggers to Axial's explicit logging service.
fromFactory: string -> Extensions.Logging.ILoggerFactory -> ILogCreates an Axial logger with an explicit Microsoft logging category.
MicrosoftLogging.layer "MyApp" provisions ILog from an ILoggerFactory layer input. All Axial log levels and
exception objects are preserved. The adapter never silently substitutes a no-op logger.
Fiber Defect Logging
Install the observer once around the root application:
let observed (logger: Microsoft.Extensions.Logging.ILogger) (application: Flow<unit, string, unit>) =
application
|> FiberLogging.observe logger
observed: Extensions.Logging.ILogger -> Flow<unit,string,unit> -> Flow<unit,string,unit>logger: Extensions.Logging.ILoggerMicrosoftMicrosoft.Extensions.Logging.ILoggerRepresents a type used to perform logging. Aggregates most logging patterns to a single method.
ExtensionsLoggingapplication: Flow<unit,string,unit>Axial.Flow`3unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
stringAn abbreviation for the CLI type . Basic Types
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.Hosting.FiberLoggingFiber-lifecycle logging through Microsoft.Extensions.Logging.
observe: Extensions.Logging.ILogger -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Installs fiber defect logging at the root application edge.
Fiber defects are errors; unobserved fiber defects are critical entries. Compose it with telemetry when both are required:
let observedWithSpans (logger: Microsoft.Extensions.Logging.ILogger) (application: Flow<unit, string, unit>) =
application
|> Flow.withFiberObserver
(FiberObserver.compose
FiberTelemetry.observerWithSpans
(FiberLogging.observer logger))Logging is an explicit application dependency; telemetry remains runtime instrumentation. See Observability.
Desktop and Embedded Applications
Desktop frameworks already own application lifetime. Start App after startup and await stop from the framework's
closing path:
let startDesktop (requestExit: unit -> unit) (application: Flow<unit, string, unit>) =
let running = App.start () application
let closeApplication () = async {
let! _ = running.Stop()
requestExit ()
}
closeApplication
startDesktop: (unit -> unit) -> Flow<unit,string,unit> -> unit -> Async<unit>requestExit: unit -> unitunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
application: Flow<unit,string,unit>Axial.Flow`3stringAn abbreviation for the CLI type . Basic Types
running: AppHandle<string,unit>Axial.AppStarts and controls root Flow applications without requiring an external hosting framework.
start: 'env -> Flow<'env,'error,'value> -> AppHandle<'error,'value>Starts a root workflow and returns a handle that owns its lifetime. The explicit environment supplied to the root workflow. The root workflow to start. A handle for observing completion or requesting coordinated stop. Fable compatible
closeApplication: unit -> Async<unit>async: AsyncBuilderBuilds an asynchronous workflow using computation expression syntax. let sleepExample() = async { printfn "sleeping" do! Async.Sleep 10 printfn "waking up" return 6 } sleepExample() |> Async.RunSynchronously
Stop: unit -> Async<Exit<unit,string>>Requests cooperative interruption and waits for the final application exit.
Do not block the UI thread on Completion. A framework-specific package is unnecessary for wiring one close event to Stop().
Other Runtimes
- Node hosting handles Node arguments,
process.env, signals, and exit status. - Browser hosting handles explicit UI ownership and
AbortSignal.

