I often write windows services that will sit on a server and process jobs, my templated approach to a windows service will utlilise the following packages:
Thats quite a few different components, and in all honesty a lot of working parts. In most cases the logging will log to a stream like Azure Log Analytics or a text source. However sometimes it's necessary to create a web front end for a windows service or console application that lets you see a few different outputs of what the app is upto, this could be real time metrics, or performance counters, or help and support, or some kind of output using reflection.
For example, I like to output all the jobs that are configured for Quartz, and output them, along with when they last ran and their next fire time. Another example is that I have a variable amount of jobs that can also be triggered adhoc using Rabbit MQ / Mass Transit, and sometimes it's necessary to have a windows service dashboard that pulls together all the services and realtime analytics into a web front end.
Luckily, .Net has a facility called UseKestrel which resides in the Microsoft.AspNetCore namespace, it used to be in the Microsoft.AspNetCore.Hosting namespace. If you would like to read further info on Kestrel by Microsoft read their article here.
In order to use Kestrel functionality inside a windows service or console app, Add the following NuGet packages to your project:
Below are some code snippets for Net9 that will allow you to run a Kestrel web server inside a windows application.
using Microsoft.AspNetCore.Hosting;
IConfiguration config;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<WebServer>();
webBuilder.UseKestrel(options =>
{
// HTTP 5000
options.ListenLocalhost(5000);
// HTTPS 5001
options.ListenLocalhost(5001, builder =>
{
builder.UseHttps();
});
});
})
.UseWindowsService(options =>
{
options.ServiceName = "My Windows Service from OnlyIan.com";
})
}).Build();
await host.RunAsync();
There is also a class called WebServer which will define our web server functions and determines what content is rendered.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
namespace OnlyIan.Scheduler.Services
{
public class WebServer
{
public IConfiguration Configuration { get; }
public WebServer(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
var serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
var serviceProvider = app.ApplicationServices;
app.UseStaticFiles();
app.Run(async (context) =>
{
context.Response.ContentType = "text/html";
await context.Response
.WriteAsync("<!DOCTYPE html><html lang=\"en\"><head>" +
"<title>Hosted Web Front End</title>" +
"<style>" +
"body { font-family: Arial, sans-serif; margin: 20px; }" +
"table { border-collapse: collapse; width: 100%; }" +
"th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }" +
"th { background-color: #f2f2f2; }" +
"tr:nth-child(even) { background-color: #f9f9f9; }" +
"h1 { color: #333; }" +
"</style>" +
"</head><body>" +
"<h1>Hosted Front End</h1>");
if (serverAddressesFeature != null)
{
await context.Response
.WriteAsync("<p>Listening on the following addresses: " +
string.Join(", ", serverAddressesFeature.Addresses) +
"</p>");
}
await context.Response.WriteAsync("<p>Request URL: " +
$"{context.Request.GetDisplayUrl()}</p>");
await context.Response.WriteAsync("</table>");
await context.Response.WriteAsync("</body></html>");
});
}
}
}
There are other areas this can be improved, like using bootstrap cdn's rather than embedded css. Creating the HTML is rather manual, so what you could do instead is create a HTML page in the project and include that rendering in partial components.
The app.UseStaticFiles(); directive means that Kestrel will load files relative to the index file, so if you want to host a index file and load images, you would do so like this:
Replace the app.Run part in the WebServer class:
app.Run(async (context) =>
{
context.Response.ContentType = "text/html";
// Get the base directory
var baseDirectory = AppContext.BaseDirectory;
// Read the index.html file from the wwwroot directory
var indexHtmlPath = Path.Combine(baseDirectory, "wwwroot", "index.html");
var indexHtml = await File.ReadAllTextAsync(indexHtmlPath);
var username = Environment.UserName;
await context.Response.WriteAsync(indexHtml);
});
If you want to handle click events, or perform searches, this can be down with the app.UseRouting(); directive, the following code is an example of how to handle the search event in a Nav Bar.
The client side markup would need to be HTML compliant, so something like a basic form:
<form class="d-flex" role="search" action="/search">
<input class="form-control me-2" name="query" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
The code behind to handle the form post would like the following:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseRouting();
// Add the endpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/search", async context =>
{
var query = context.Request.Query["query"];
context.Response.Redirect("/?q="+ query);
});
});
app.Run(async (context) =>
{
context.Response.ContentType = "text/html";
// Get the base directory
var baseDirectory = AppContext.BaseDirectory;
// Read the index.html file from the wwwroot directory
var indexHtmlPath = Path.Combine(baseDirectory, "wwwroot", "index.html");
var indexHtml = await File.ReadAllTextAsync(indexHtmlPath);
await context.Response.WriteAsync(indexHtml);
});
}
The MapGet function tells the Kestrel web server to handle any GET Requests to /search and run the code, the code context.Request.Query["query"] simply reads the value of the variable search in the query string, and redirects back to the home page, passing q and the data that has been entered into the search box.
In summary this article, in a short 6 minute read showing you how to host your own webserver inside your .net 9 windows application, and render html from a local html file that can be embedded with your project.