Configure desktop host and deployment settings
Adds desktop hosting/browser launch configuration, app settings examples, project dependency/config updates, and ignores generated/local files.main
parent
fd1655947e
commit
267c4c8881
|
|
@ -9,6 +9,11 @@
|
|||
bin/
|
||||
obj/
|
||||
|
||||
# Publish artifacts
|
||||
publish/
|
||||
artifacts/
|
||||
artifacts/**
|
||||
|
||||
# .NET Core
|
||||
*.runtimeconfig.dev.json
|
||||
|
||||
|
|
@ -45,4 +50,7 @@ node_modules/
|
|||
*.temp
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env
|
||||
|
||||
# Local overrides (connection strings, machine-specific paths). Copy from appsettings.Example.json.
|
||||
appsettings.Local.json
|
||||
|
|
@ -6,6 +6,10 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AssemblyName>AVSCartonShipmentVerifier</AssemblyName>
|
||||
<RootNamespace>AVSCartonShipmentVerifier</RootNamespace>
|
||||
<UserSecretsId>AVSCartonShipmentVerifier-d4f8e9b2-3c1a-4f6e-9b7d-2a8e5c1f9d3b</UserSecretsId>
|
||||
<!-- Release / publish: hide console on Windows when double-clicking the exe. Debug: keep console for logs. -->
|
||||
<OutputType Condition="'$(Configuration)' == 'Release'">WinExe</OutputType>
|
||||
<OutputType Condition="'$(Configuration)' != 'Release'">Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -17,4 +21,9 @@
|
|||
<None Update="Resources\Sound\alert.wav" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Default SDK Content does not copy wwwroot to bin; static assets use a runtime manifest. Copy wwwroot into output so WebRootPath exists when running the built exe (silences StaticFileMiddleware warning; xcopy-friendly). -->
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
namespace AVSCartonShipmentVerifier.Configuration;
|
||||
|
||||
public sealed class DesktopHostOptions
|
||||
{
|
||||
public const string SectionName = "DesktopHost";
|
||||
|
||||
/// <summary>
|
||||
/// When true (default on Windows), opens the default browser after the server starts.
|
||||
/// </summary>
|
||||
public bool LaunchBrowser { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional URL to open. If empty, the first bound HTTP address from Kestrel is used.
|
||||
/// </summary>
|
||||
public string? BrowserUrl { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
using System.Diagnostics;
|
||||
using AVSCartonShipmentVerifier.Configuration;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace AVSCartonShipmentVerifier.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the default browser once the web server has started (Windows desktop-style launch).
|
||||
/// </summary>
|
||||
internal sealed class LaunchBrowserHostedService : IHostedService
|
||||
{
|
||||
private readonly IHostApplicationLifetime _lifetime;
|
||||
private readonly IOptions<DesktopHostOptions> _options;
|
||||
private readonly IServer _server;
|
||||
private readonly ILogger<LaunchBrowserHostedService> _logger;
|
||||
|
||||
public LaunchBrowserHostedService(
|
||||
IServer server,
|
||||
IHostApplicationLifetime lifetime,
|
||||
IOptions<DesktopHostOptions> options,
|
||||
ILogger<LaunchBrowserHostedService> logger)
|
||||
{
|
||||
_server = server;
|
||||
_lifetime = lifetime;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var opts = _options.Value;
|
||||
if (!opts.LaunchBrowser)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_lifetime.ApplicationStarted.Register(OpenBrowser);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OpenBrowser()
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = ResolveUrl();
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
_logger.LogWarning("Could not determine URL to open in the browser.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
Process.Start("open", url);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("xdg-open", url) { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to launch the default browser.");
|
||||
}
|
||||
}
|
||||
|
||||
private string? ResolveUrl()
|
||||
{
|
||||
var configured = _options.Value.BrowserUrl;
|
||||
if (!string.IsNullOrWhiteSpace(configured))
|
||||
return configured;
|
||||
|
||||
var addresses = _server.Features.Get<IServerAddressesFeature>()?.Addresses;
|
||||
if (addresses is null || addresses.Count == 0)
|
||||
return null;
|
||||
|
||||
return addresses.FirstOrDefault(static a => a.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
|
||||
?? addresses.FirstOrDefault(static a => a.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
?? addresses.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
27
Program.cs
27
Program.cs
|
|
@ -1,5 +1,6 @@
|
|||
using AVSCartonShipmentVerifier.Configuration;
|
||||
using AVSCartonShipmentVerifier.Data;
|
||||
using AVSCartonShipmentVerifier.Hosting;
|
||||
using AVSCartonShipmentVerifier.Repositories;
|
||||
using AVSCartonShipmentVerifier.ScanHistory;
|
||||
using AVSCartonShipmentVerifier.Services;
|
||||
|
|
@ -8,6 +9,8 @@ using Microsoft.Extensions.FileProviders;
|
|||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true);
|
||||
|
||||
builder.Services
|
||||
.AddOptions<CartonVerificationOptions>()
|
||||
.Bind(builder.Configuration.GetSection(CartonVerificationOptions.SectionName))
|
||||
|
|
@ -20,6 +23,12 @@ builder.Services
|
|||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services
|
||||
.AddOptions<DesktopHostOptions>()
|
||||
.Bind(builder.Configuration.GetSection(DesktopHostOptions.SectionName));
|
||||
|
||||
builder.Services.AddHostedService<LaunchBrowserHostedService>();
|
||||
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
builder.Services.AddDbContext<ScanHistoryDbContext>((serviceProvider, options) =>
|
||||
|
|
@ -40,6 +49,24 @@ using (var scope = app.Services.CreateScope())
|
|||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<ScanHistoryDbContext>();
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
// EnsureCreated does not add new tables to an existing SQLite file; create rejected-scan storage explicitly.
|
||||
db.Database.ExecuteSqlRaw(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS rejected_scan_entries (
|
||||
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
RawInputValue TEXT NOT NULL,
|
||||
ModelNumber TEXT NOT NULL,
|
||||
UniqueNumber TEXT NOT NULL,
|
||||
RejectionReason TEXT NOT NULL,
|
||||
ProcessedAtUtc TEXT NOT NULL
|
||||
);
|
||||
""");
|
||||
db.Database.ExecuteSqlRaw(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS IX_rejected_scan_entries_ProcessedAtUtc
|
||||
ON rejected_scan_entries (ProcessedAtUtc);
|
||||
""");
|
||||
}
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"VerificationDatabase": "Server=192.168.90.147;Port=3306;Database=UIND;User ID=utopia;Password=Utopia01;SslMode=None;Allow User Variables=True;"
|
||||
},
|
||||
"DesktopHost": {
|
||||
"LaunchBrowser": false
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"VerificationDatabase": ""
|
||||
},
|
||||
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://localhost:5000"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"DesktopHost": {
|
||||
"LaunchBrowser": true,
|
||||
"BrowserUrl": "http://localhost:5000"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"VerificationDatabase": ""
|
||||
"ConnectionStrings": {},
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://localhost:5000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DesktopHost": {
|
||||
"LaunchBrowser": true,
|
||||
"BrowserUrl": ""
|
||||
},
|
||||
"CartonVerification": {
|
||||
"TableName": "carton_verification_log",
|
||||
|
|
|
|||
Loading…
Reference in New Issue