C:\Users\Administrator2>dotnet --info
.NET SDK (反映任何 global.json):
Version: 7.0.100-preview.3.22179.4
Commit: c48f2c30ee
运行时环境:
OS Name: Windows
OS Version: 10.0.19044
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.100-preview.3.22179.4\
Host (useful for support):
Version: 7.0.0-preview.3.22175.4
Commit: 162f83657c
.NET SDKs installed:
6.0.202 [C:\Program Files\dotnet\sdk]
step 1: Creating a Default Web Application
We start with new default ASP.NET Core 6.x web application.
<Project Sdk="Microsoft.NET.Sdk.Web">
Step 2: Install the Microsoft.Extensions.Hosting.WindowsServices NuGet package
Install-Package Microsoft.Extensions.Hosting.WindowsServices
Install-Package Microsoft.Extensions.Hosting.Systemd
Install the Worker service on Linux
Step 3:Implementing this in .NET 6 with WebApplicationBuilder requires a workaround:
using Microsoft.Extensions.Hosting.WindowsServices;
var webApplicationOptions = new WebApplicationOptions()
{
ContentRootPath = AppContext.BaseDirectory,
Args = args,
ApplicationName = System.Diagnostics.Process.GetCurrentProcess().ProcessName
};
var builder = WebApplication.CreateBuilder(webApplicationOptions);
builder.Host.UseWindowsService(); //for windows Service
builder.Host.UseSystemd(); //for Linux Service
old
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
new
using Microsoft.Extensions.Hosting.WindowsServices;
var webApplicationOptions = new WebApplicationOptions()
{
ContentRootPath = AppContext.BaseDirectory,
Args = args,
ApplicationName = System.Diagnostics.Process.GetCurrentProcess().ProcessName
};
var builder = WebApplication.CreateBuilder(webApplicationOptions);
builder.Host.UseWindowsService();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
step 4:Make our app compile down to an EXE
The first thing we need to do is make our app compile down to an EXE. Well.. We don’t have to but it makes things a heck of a lot easier. To do that, we just need to edit our csproj and add the OutputType of exe. It might end up looking like so :
step 3:Install the following package into our Web App
Install-Package Microsoft.Extensions.Hosting.WindowsServices
Inside program.cs, you should have a “CreateHostBuilder” method. You might already have some custom configuration going on, you need to tack onto the end “UseWindowsServices()”.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
And that’s all the code changes required!
step 4.Deploying Our Service
Open a command prompt as an Administrator, and run the following command in your project folder to publish your project :
g:
cd G:\stock\ReadDZHRealTime\src\WinTimerTask\Stockso.WebHostService
dotnet publish -c Release
use standard Windows Service commands to install our EXE as a service. run something like so to install as a service :
you only need to run the .bat files from an administrator command prompt.
sc create Stockso.WebHostService binPath= G:\stock\ReadDZHRealTime\src\WinTimerTask\Stockso.WebHostService.exe
sc failure Stockso.WebHostService actions= restart/60000/restart/60000/""/60000 reset= 86400
sc start Stockso.WebHostService
sc config Stockso.WebHostService start=auto
uninstall.bat :
sc stop Stockso.WebHostService
timeout /t 5 /nobreak > NUL
sc delete Stockso.WebHostService
Doing A Self Contained Deploy
We talked about it earlier that the entire reason for running the Web App as a Windows Service is so that we don’t have to install additional tools on the machine. But that only works if we are doing what’s called a “self contained” deploy. That means we deploy everything that the app requires to run right there in the publish folder rather than having to install the .NET Core runtime on the target machine.
All we need to do is run our dotnet release command with a few extra flags :
This tells the .NET Core SDK that we want to release as self contained, and it’s for Windows.
Your output path will change from bin\Release\netcoreapp3.0\publish to \bin\Release\netcoreapp3.0\win-x64\publish
You’ll also note the huge amount of files in this new output directory and the size in general of the folder. But when you think about it, yeah, we are deploying the entire runtime so it should be this large.
Content Root
The fact that .NET Core is open source literally saves hours of debugging every single time I work on a greenfields project, and this time around is no different. I took a quick look at the actual source code of what the call to UseWindowsService does here. What I noticed is that it sets the content root specifically for when it’s running under a Windows Service. I wondered how this would work if I was reading a local file from disk inside my app, while running as a Windows Service. Normally I would just write something like :
But… Obviously there is something special when running under a Windows Service context. So I tried it out and my API bombed. I had to check the Event Viewer on my machine and I found :
OK. So it looks like when running as a Windows Service, the “root” of my app thinks it’s inside System32. Oof. But, again, looking at the source code from Microsoft gave me the solution. I can simply use the same way they set the content root to load my file from the correct location :
And we are back up and running!