Khaled Hikmat
Khaled Hikmat

Reputation: 289

How to make ASP.NET Core 3.1 running as a Windows Service see its appsettings file

I have a simple ASP.NET Core 3.1 running well locally. Trying to run it as a Windows Service, it starts and I am able to interact with controllers but it seems like it is unable to load the appsettings.json file.

Here is the output of my home controller when running locally:

Hello World running locally - Configuration Minutes: 1 - Server: server-xyz - HeartBeatRunnerClass: grp.csa.soi.soicat.runners.HeartBeatRunner - Env Name: Development - Application: grp.csa.soi.ads.web - Root Path: C:\****\grp.csa.soi.ads.web!!!

Here is the output of my home controller when running in Windows Service:

Hello World running as a Windows Service - Configuration Minutes: 0 - Server:  - HeartBeatRunnerClass:  - Env Name: Production - Application: grp.csa.soi.ads.web - Root Path: C:\WINDOWS\TEMP\.net\grp.csa.soi.ads.web\rr4fpdsz.c2n\!!!

Here is the relevant part of my Program file:

public static void Main(string[] args)
{
    var builder = CreateHostBuilder(args);
    if (WindowsServiceHelpers.IsWindowsService())
    {
        builder.ConfigureHostConfiguration(hostBuilder =>
        {
            hostBuilder.SetBasePath(System.AppDomain.CurrentDomain.BaseDirectory);
        });
    }

    builder.Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        .UseWindowsService();
}

Notes:

  1. I thought by using useWindowsService() extension method, it is supposed to setup the content root based on this.
  2. I have packaged my publish to place the appsettings.json file next to exe file.
  3. I have tried different combinations of app configuration based on other posts like this but nothing worked for me.

I appreciate any pointer.

Regards

Upvotes: 1

Views: 1539

Answers (3)

manon
manon

Reputation: 272

I had this problem,too. The clientApp folder with the javascript code was not in C:\WINDOWS\TEMP\xy, too,

In solved both with:

string pathToContentRoot = null;
        if (!Debugger.IsAttached) //isService
        {
            var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
            pathToContentRoot = Path.GetDirectoryName(pathToExe);  // to find appsettings.json and ClientApp dir when run as windows service
            //ausführung als service in: C:\Windows\Temp\.net\a
        }
        string logFile = pathToContentRoot != null ? $"{pathToContentRoot}\\Logs\\log.log" : "Logs\\log.log";
        Log.Logger = new LoggerConfiguration()
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .WriteTo.File(logFile, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true, fileSizeLimitBytes: 10000000) //10 mb, max 31 times
            .CreateLogger();

        Log.Information("Starting up");

        var host = Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .UseSerilog()
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
        if (pathToContentRoot != null)
        {
            host.UseContentRoot(pathToContentRoot);
        }
        host.Build().Run();

And here is my pubxml:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <WebPublishMethod>FileSystem</WebPublishMethod>
    <PublishProvider>FileSystem</PublishProvider>
    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>
    <SiteUrlToLaunchAfterPublish />
    <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
    <ExcludeApp_Data>False</ExcludeApp_Data>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PublishSingleFile>True</PublishSingleFile>
    <ProjectGuid>3cccecb5-d200-4a55-9b9e-6760f1f75fe4</ProjectGuid>
    <SelfContained>true</SelfContained>
    <publishUrl>bin\Release\netcoreapp3.1\publishResult\</publishUrl>
    <DeleteExistingFiles>True</DeleteExistingFiles>
  </PropertyGroup>
</Project>

Upvotes: 2

poke
poke

Reputation: 387617

I would suggest you to not use single-file publishing for your Windows services. There are apparently still some issues, e.g. this one, which will prevent the application from knowing exactly where its files are located. This means that the application is not able to locate the appsettings.json file but instead looks in the folder it was temporarily unpacked to.

Upvotes: 0

mrbitzilla
mrbitzilla

Reputation: 398

Here's my approach to this in the past on .NET Core:

  1. Create a POCO object that represents and holds the configuration data:

For example:

    public class Settings
    {
        public Timer Timer { get; set; }
        public Db Database { get; set; }
        public Logs Logs { get; set; }
        public InventoryFile InventoryFile { get; set; }
    }

    public class Timer
    {
        public int MinuteInterval { get; set; }
    }
    public class Db
    {
        public string Server { get; set; }
        public string Database { get; set; }
        public string User { get; set; }
        public string Password { get; set; }
        public string DebugMode { get; set; }
        public string EncryptionKey { get; set; }
    }
    public class Logs
    {
        public bool autoDelete { get; set; }
        public int autoDeleteMaxDays { get; set; }
        public int autoDeleteMaxSize { get; set; }
    }
    public struct InventoryFile
    {
        public string FileName { get; set; }
        public string FilePath { get; set; }
        public string FileExtension { get; set; }
    }

Then, read the appsettings.json configuration to the POCO object on the Program.cs file. Like:

public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .UseWindowsService()
            .ConfigureServices((hostContext, services) =>
            {
                IConfiguration configuration = hostContext.Configuration;
                Settings settings = configuration.GetSection("Settings").Get<Settings>();

                services.AddSingleton(settings);
                services.AddHostedService<Worker>();
            });

This would be my appsettings.json file:

  {
  "Settings": {
    "Timer": {
      "MinuteInterval": 1
    },
    "Database": {
      "Server": "192.168.XXX.XXX",
      "Database": "MyDbname",
      "User": "DbUser",
      "Password": "DbPassword",
      "DebugMode": true,
      "EncryptionKey": "encryptionKey"
    },
    "Logs": {
      "autoDelete": true,
      "autoDeleteMaxDays": 0,
      "autoDeleteMaxSize": 10
    },
    "InventoryFile": {
      "FileName": "Inventory",
      "FilePath": "Inventory/",
      "FileExtension": ".csv"
    }
  }
}

In order to use the POCO object with it's data it has to be passed on to the constructor of the controller or class you need it.

For example, if you are using a WorkerService from .NET Core here's an example on how it would be on the Worker.cs file:

 public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;
        private readonly Settings settings;

        public Worker(ILogger<Worker> logger, Settings settings)
        {
            _logger = logger;
            this.settings = settings;
        }

Good luck!

Upvotes: 0

Related Questions