首页 > 解决方案 > F# Host.CreateDefaultBuilder().ConfigureServices() 没有 HostBuilderContext

问题描述

我正在创建一个 WorkerService。
我有信心用 C# 做这件事,但我在 F# 版本上苦苦挣扎。

C# 程序.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)                
    .ConfigureServices((hostContext, services) =>
    {      
        // here I have access to the builded configuration
        // read stuff from configuration
        var interval = TimeSpan.Parse(hostContext.Configuration["Service:Run:interval"]);
        services.AddHostedService<Worker>();
    });

F# 程序.fs

let CreateHostBuilder (args) = 
    Host.CreateDefaultBuilder() 
        .ConfigureServices( 
            fun (services:IServiceCollection) -> //hostContext:HostBuilderContext,
            //fun (context:HostBuilderContext, services:IServiceCollection) ->  
                // where is the builded configuration ??? 
                //let configuration = ???
                services.AddHostedService<Worker>() |> ignore
            ) 

我不知道如何阅读配置来设置我需要注入的一些服务设置。

为什么 IHostBuilder.ConfigureServices 的签名不同?
C#
IHostBuilder ConfigureServices(Action configureDelegate);

F#
IHostBuilder 配置服务:configureDelegateAction -> IHostBuilder

HostBuilderContext被传递给.ConfigureContainer ()所以我尝试了这个:


open System
open System.Threading
open System.Threading.Tasks

type IMyWorker =
    abstract member Start: unit -> unit

type MyWorker(interval:TimeSpan) =
    inherit BackgroundService()

    override this.ExecuteAsync (step:CancellationToken):Task = Task.CompletedTask

    interface IMyWorker with
        member this.Start() = ()


let CreateHostBuilder (args) = 

    // 1. this is empty
    //let configuration = ConfigurationBuilder().Build()
    //let interval = TimeSpan.Parse(configuration.["Service:Run:interval"])

    let mutable loadedConfiguration:Option<IConfiguration> = None

    Host.CreateDefaultBuilder() 
        .ConfigureHostConfiguration( fun(confBuilder:IConfigurationBuilder) ->
            // it's ok here to read more settings, like "serilog.json" for example ?

            // 2. this is still empty
            //let configuration = confBuilder.Build()
            //let interval = TimeSpan.Parse(configuration.["Service:Run:interval"])
            // where to store the values ?  configuration.[""]
            ()
        )
        .ConfigureContainer( fun (hostContext:HostBuilderContext) -> 

            // 5. this is ok, it is builded properly
            // but ist is called after ConfigureServices()
            loadedConfiguration <- Some(hostContext.Configuration) // :> IConfigurationRoot
            let interval = TimeSpan.Parse(hostContext.Configuration.["Service:Run:interval"])
            ()
        )
        .ConfigureServices(
            fun(services:IServiceCollection) ->

                // 4. this is empty
                //let configuration = ConfigurationBuilder().Build()
                //let interval = TimeSpan.Parse(configuration.["Service:Run:interval"])

                if loadedConfiguration.IsNone then failwith "configuration not loqaded yet"

                let interval = TimeSpan.Parse(loadedConfiguration.Value.["Service:Run:interval"])

                //let interval = TimeSpan.Parse("00:01:00")
                let worker = new MyWorker(interval)
                services.AddSingleton<IMyWorker>(worker) |> ignore
        )

但它不起作用,因为ConfigureContainer()在ConfigureServices()之后调用,我认为应该有一种方法可以使用构建的配置而不将其存储在某处。

它应该如何工作?

标签: dependency-injectionf#.net-core-3.1

解决方案


这应该可以解决问题:

let createHostBuilder args =
    Host
        .CreateDefaultBuilder(args)
        .ConfigureServices (fun hostContext services ->
            let interval = TimeSpan.Parse(hostContext.Configuration.["Service:Run:interval"]);
            services.AddHostedService<Worker>() |> ignore
        )

推荐阅读