建立3个项目,分别是类库项目ConfigTool.WebSite、webapi项目ConfigTool.TestWebSite、webapi项目ConfigTool.WinService,目标框架均为.NET8。
其中控制台ConfigTool.TestWebSite方便开发调试,win服务ConfigTool.WinService作为宿主服务,类库ConfigTool.WebSite为自定义web服务器的主体代码。
一、项目框架
ConfigTool.WebSite引用nuget包:
Microsoft.Extensions.Hosting 8.0.0
Microsoft.Extensions.Hosting.Abstractions 8.0.0
Microsoft.NETCore.DotNetHost 8.0.6
Swashbuckle.AspNetCore 6.6.2
ConfigTool.WebSite的项目文件内容如下:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.NETCore.DotNetHost" Version="8.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<None Remove="wwwroot\index.html" />
</ItemGroup>
<ItemGroup>
<Content Include="wwwroot\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="wwwroot\images\1.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
ConfigTool.TestWebSite的项目文件内容如下:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ConfigTool.WebSite\ConfigTool.WebSite.csproj" />
</ItemGroup>
</Project>
ConfigTool.WinService引用nuget包:
Microsoft.Extensions.Hosting.WindowsServices 8.0.0
ConfigTool.WinService的项目文件内容如下:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>App.WindowsService</RootNamespace>
<OutputType>exe</OutputType>
<PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<UserSecretsId>dotnet-smart_sign-ef6ac77d-c996-482b-8f81-5d63d0c287a5</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ConfigTool.WebSite\ConfigTool.WebSite.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="install.bat">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="uninstall.bat">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
二、代码编写
2.1、ConfigTool.WebSite
在ConfigTool.WebSite中增加目录Controllers和wwwroot
using Microsoft.AspNetCore.Mvc;
namespace ConfigTool.WebSite.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[Route("Time")]
public string Time()
{
return DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
}
}
}
在wwwroot下放入index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
</head>
<body>
<p>
<input type="text" id="curtime" value="1"/>
<button id="submit">从api接口获取当前服务器时间</button>
</p>
<p>
<image src="images/1.jpg" alt="验证静态资源" width="300" height="300"/>
</p>
</body>
<script>
$("#submit").click(function(){
$.get("api/Values/Time",function(data,status){
$("#curtime").val(data);
});
});
</script>
</html>
新增WebServer类文件
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
namespace ConfigTool.WebSite
{
public class WebServer
{
readonly WebApplication app;
int? port = 8080;
string? url;
public WebServer()
{
var webBuilder = WebApplication.CreateBuilder();
webBuilder.Services.AddControllers();
app = webBuilder.Build();
app.MapControllers();
}
public void Init(int port)
{
this.port = port;
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot")),
RequestPath = ""
});
var host = "*";
url = $"http://{host}:{port}";
app.Urls.Add(url);
}
/// <summary>
/// 启动web服务,并阻塞调用进程直到程序退出,这里提供给控制台程序调用
/// </summary>
public void Run()
{
app.Run();
}
/// <summary>
/// 启动web服务,这里提供给服务程序调用
/// </summary>
public void Start()
{
app.Start();
}
/// <summary>
/// 停止web服务,这里提供给服务程序调用
/// </summary>
public void Stop()
{
app.StopAsync();
}
}
}
2.2、ConfigTool.TestWebSite
ConfigTool.TestWebSite的Program.cs代码如下:
using ConfigTool.WebSite;
namespace ConfigTool.TestWebSite
{
internal class Program
{
static void Main(string[] args)
{
WebServer server = new();
server.Init(8080);
server.Run();
Console.ReadLine();
}
}
}
可以用来调试业务代码。
2.3、ConfigTool.WinService
ConfigTool.WinService的的Program.cs代码如下:
using App.WindowsService;
using ConfigTool.WebSite;
namespace ConfigTool.WinService
{
public class Program
{
public static void Main(string[] args)
{
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "ConfigTool.WinService";
});
builder.Services.AddSingleton<WebServer>();
builder.Services.AddHostedService<WindowsBackgroundService>();
IHost host = builder.Build();
host.Run();
}
}
}
WindowsBackgroundService.cs代码如下:
using ConfigTool.WebSite;
namespace App.WindowsService;
public sealed class WindowsBackgroundService : BackgroundService
{
private readonly WebServer webServer;
private readonly ILogger<WindowsBackgroundService> logger;
private bool isRunning = false;
private static readonly object locker = new();
public WindowsBackgroundService(WebServer webServer, ILogger<WindowsBackgroundService> logger)
{
this.webServer = webServer;
this.logger = logger;
this.webServer.Init(5000);
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
if (isRunning)
{
return Task.CompletedTask;
}
if (!isRunning)
{
lock (locker)
{
if (isRunning)
{
return Task.CompletedTask;
}
isRunning = true;
}
}
this.webServer.Start();
return base.StartAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
if (!isRunning)
{
return Task.CompletedTask;
}
if (isRunning)
{
lock (locker)
{
if (!isRunning)
{
return Task.CompletedTask;
}
isRunning = false;
}
}
this.webServer.Stop();
return base.StopAsync(cancellationToken);
}
}
install.bat的内容如下:
echo "service installing......"
echo off
@sc create ConfigTool.WinService binpath="%~dp0ConfigTool.WinService.exe"
@sc config ConfigTool.WinService start=auto
@net start ConfigTool.WinService
echo on
echo "service started"
timeout /t 5
其中%~dp0表示bat脚本的当前目录的意思。
uninstall.bat的内容如下:
echo "uninstall service......"
echo off
@net stop ConfigTool.WinService
@sc delete ConfigTool.WinService
echo on
echo "service uninstalled"
timeout /t 5