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/
|
bin/
|
||||||
obj/
|
obj/
|
||||||
|
|
||||||
|
# Publish artifacts
|
||||||
|
publish/
|
||||||
|
artifacts/
|
||||||
|
artifacts/**
|
||||||
|
|
||||||
# .NET Core
|
# .NET Core
|
||||||
*.runtimeconfig.dev.json
|
*.runtimeconfig.dev.json
|
||||||
|
|
||||||
|
|
@ -46,3 +51,6 @@ node_modules/
|
||||||
|
|
||||||
# Environment variables
|
# 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>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<AssemblyName>AVSCartonShipmentVerifier</AssemblyName>
|
<AssemblyName>AVSCartonShipmentVerifier</AssemblyName>
|
||||||
<RootNamespace>AVSCartonShipmentVerifier</RootNamespace>
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -17,4 +21,9 @@
|
||||||
<None Update="Resources\Sound\alert.wav" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
<None Update="Resources\Sound\alert.wav" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</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>
|
</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.Configuration;
|
||||||
using AVSCartonShipmentVerifier.Data;
|
using AVSCartonShipmentVerifier.Data;
|
||||||
|
using AVSCartonShipmentVerifier.Hosting;
|
||||||
using AVSCartonShipmentVerifier.Repositories;
|
using AVSCartonShipmentVerifier.Repositories;
|
||||||
using AVSCartonShipmentVerifier.ScanHistory;
|
using AVSCartonShipmentVerifier.ScanHistory;
|
||||||
using AVSCartonShipmentVerifier.Services;
|
using AVSCartonShipmentVerifier.Services;
|
||||||
|
|
@ -8,6 +9,8 @@ using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
builder.Services
|
builder.Services
|
||||||
.AddOptions<CartonVerificationOptions>()
|
.AddOptions<CartonVerificationOptions>()
|
||||||
.Bind(builder.Configuration.GetSection(CartonVerificationOptions.SectionName))
|
.Bind(builder.Configuration.GetSection(CartonVerificationOptions.SectionName))
|
||||||
|
|
@ -20,6 +23,12 @@ builder.Services
|
||||||
.ValidateDataAnnotations()
|
.ValidateDataAnnotations()
|
||||||
.ValidateOnStart();
|
.ValidateOnStart();
|
||||||
|
|
||||||
|
builder.Services
|
||||||
|
.AddOptions<DesktopHostOptions>()
|
||||||
|
.Bind(builder.Configuration.GetSection(DesktopHostOptions.SectionName));
|
||||||
|
|
||||||
|
builder.Services.AddHostedService<LaunchBrowserHostedService>();
|
||||||
|
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
|
|
||||||
builder.Services.AddDbContext<ScanHistoryDbContext>((serviceProvider, options) =>
|
builder.Services.AddDbContext<ScanHistoryDbContext>((serviceProvider, options) =>
|
||||||
|
|
@ -40,6 +49,24 @@ using (var scope = app.Services.CreateScope())
|
||||||
{
|
{
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ScanHistoryDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<ScanHistoryDbContext>();
|
||||||
db.Database.EnsureCreated();
|
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())
|
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": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Debug",
|
"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": {
|
"ConnectionStrings": {},
|
||||||
"VerificationDatabase": ""
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://localhost:5000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DesktopHost": {
|
||||||
|
"LaunchBrowser": true,
|
||||||
|
"BrowserUrl": ""
|
||||||
},
|
},
|
||||||
"CartonVerification": {
|
"CartonVerification": {
|
||||||
"TableName": "carton_verification_log",
|
"TableName": "carton_verification_log",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue