feat(config): embed credentials for ClickOnce and improve HRMS error messages

Adds embedded AppSecrets configuration so Release/ClickOnce builds work without appsettings.Development.json or a Public Documents config file. Includes AppSecrets.example.cs as a template; copy to gitignored AppSecrets.cs and fill in credentials before publishing. Improves dashboard messages when HRMS is missing vs unreachable.
main
SYED MUSTUFA AHMED NAQVI 2026-09-04 10:56:14 +05:00
parent 252e1b0b80
commit 7cb1ffd65b
5 changed files with 203 additions and 163 deletions

View File

@ -0,0 +1,12 @@
namespace HikvisionAttendanceManager.App.Services;
/// <summary>
/// Copy this file to AppSecrets.cs and fill in values before publishing ClickOnce.
/// AppSecrets.cs is gitignored and compiled into the application — no external config file required.
/// </summary>
internal static class AppSecretsExample
{
internal const string HrmsConnectionString = "";
internal const string HikvisionUsername = "";
internal const string HikvisionPassword = "";
}

View File

@ -0,0 +1,52 @@
using Microsoft.Extensions.Configuration;
using System.IO;
namespace HikvisionAttendanceManager.App.Services;
/// <summary>Resolves app settings from embedded AppSecrets, then optional appsettings JSON files.</summary>
internal static class AppSettingsConfiguration
{
public static IConfigurationRoot Build()
{
var embedded = new Dictionary<string, string?>();
if (AppSecrets.HasHrmsConnection)
embedded["ConnectionStrings:Hrms"] = AppSecrets.HrmsConnectionString.Trim();
if (AppSecrets.HasHikvisionCredentials)
{
embedded["Hikvision:Username"] = AppSecrets.HikvisionUsername.Trim();
embedded["Hikvision:Password"] = AppSecrets.HikvisionPassword.Trim();
}
return new ConfigurationBuilder()
.AddInMemoryCollection(embedded)
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
}
public static string? GetConnectionString() => Build().GetConnectionString("Hrms")?.Trim();
public static string? GetSetting(string key) => Build()[key]?.Trim();
public static void LogStartupConfigurationStatus()
{
AppLogger.Info($"Configuration sources: {DescribeConfigSources()}");
if (!AppSecrets.HasHrmsConnection && string.IsNullOrWhiteSpace(GetConnectionString()))
AppLogger.Warning("HRMS connection is not configured. Set values in Services/AppSecrets.cs before publishing.");
if (!AppSecrets.HasHikvisionCredentials && string.IsNullOrWhiteSpace(GetSetting("Hikvision:Username")))
AppLogger.Warning("Hikvision default credentials are not configured in AppSecrets.cs.");
}
public static string DescribeConfigSources()
{
var sources = new List<string>();
if (AppSecrets.HasHrmsConnection)
sources.Add("embedded AppSecrets");
if (File.Exists(Path.Combine(AppContext.BaseDirectory, "appsettings.json")))
sources.Add("application appsettings.json");
if (File.Exists(Path.Combine(AppContext.BaseDirectory, "appsettings.Development.json")))
sources.Add("application appsettings.Development.json");
return sources.Count == 0 ? "none" : string.Join(", ", sources);
}
}

View File

@ -64,18 +64,20 @@ public sealed class DashboardService
return null; return null;
if (!HrmsConnectionFactory.TryGetConnectionString(out _)) if (!HrmsConnectionFactory.TryGetConnectionString(out _))
return "Unable to load device data.\nCheck the HRMS connection."; return "Unable to load device data.\nHRMS is not configured. Set connection values in Services/AppSecrets.cs before publishing.";
try try
{ {
await using var connection = HrmsConnectionFactory.CreateConnection(); await using var connection = HrmsConnectionFactory.CreateConnection();
await connection.OpenAsync(cancellationToken).ConfigureAwait(false); await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
return null; return devices.Count == 0
? "Connected to HRMS, but no active Hikvision devices were returned."
: null;
} }
catch (Exception ex) catch (Exception ex)
{ {
AppLogger.Error("Dashboard: HRMS connection check failed.", ex); AppLogger.Error("Dashboard: HRMS connection check failed.", ex);
return "Unable to load device data.\nCheck the HRMS connection."; return "Unable to connect to the HRMS database.\nCheck server, credentials, and network access.";
} }
} }

View File

@ -1,5 +1,4 @@
using HikvisionAttendanceManager.App.Models; using HikvisionAttendanceManager.App.Models;
using Microsoft.Extensions.Configuration;
namespace HikvisionAttendanceManager.App.Services; namespace HikvisionAttendanceManager.App.Services;
@ -37,42 +36,15 @@ public static class HikvisionCredentialsFactory
public static void LogDevelopmentCredentialStatus() public static void LogDevelopmentCredentialStatus()
{ {
if (!IsDevelopment()) AppSettingsConfiguration.LogStartupConfigurationStatus();
return;
var username = GetDevelopmentSetting("Hikvision:Username"); var username = AppSettingsConfiguration.GetSetting("Hikvision:Username");
var hasPassword = !string.IsNullOrWhiteSpace(GetDevelopmentSetting("Hikvision:Password")); var hasPassword = !string.IsNullOrWhiteSpace(AppSettingsConfiguration.GetSetting("Hikvision:Password"));
if (!string.IsNullOrWhiteSpace(username) && hasPassword) if (!string.IsNullOrWhiteSpace(username) && hasPassword)
AppLogger.Info($"Hikvision default credentials loaded for user '{username}'."); AppLogger.Info($"Hikvision default credentials loaded for user '{username}'.");
else else
AppLogger.Warning("Hikvision default credentials were not found in Development settings."); AppLogger.Warning("Hikvision default credentials were not found in appsettings.");
} }
private static string? GetDevelopmentSetting(string key) private static string? GetDevelopmentSetting(string key) => AppSettingsConfiguration.GetSetting(key);
{
if (!IsDevelopment())
return null;
return BuildDevelopmentConfiguration()[key]?.Trim();
}
private static IConfiguration BuildDevelopmentConfiguration() =>
new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
private static bool IsDevelopment()
{
var environment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")?.Trim();
if (!string.IsNullOrWhiteSpace(environment))
return string.Equals(environment, "Development", StringComparison.OrdinalIgnoreCase);
#if DEBUG
return true;
#else
return false;
#endif
}
} }

View File

@ -25,7 +25,7 @@ public static class HrmsConnectionFactory
AppLogger.Info($"Environment = {environment}"); AppLogger.Info($"Environment = {environment}");
AppLogger.Info($"Development config loaded = {(IsDevelopment() && developmentConfigExists ? "YES" : "NO")}"); AppLogger.Info($"Development config loaded = {(IsDevelopment() && developmentConfigExists ? "YES" : "NO")}");
if (IsDevelopment() && TryGetDevelopmentConnectionString(out connectionString)) if (IsDevelopment() && TryGetJsonConnectionString(out connectionString))
{ {
AppLogger.Info("HRMS connection configuration loaded from Development settings."); AppLogger.Info("HRMS connection configuration loaded from Development settings.");
AppLogger.Info("HRMS connection configuration found = YES"); AppLogger.Info("HRMS connection configuration found = YES");
@ -53,12 +53,19 @@ public static class HrmsConnectionFactory
{ {
AppLogger.Info("HRMS connection configuration loaded from service database environment settings."); AppLogger.Info("HRMS connection configuration loaded from service database environment settings.");
AppLogger.Info("HRMS connection configuration found = YES"); AppLogger.Info("HRMS connection configuration found = YES");
return true;
} }
else
if (TryGetJsonConnectionString(out connectionString))
{ {
AppLogger.Warning("HRMS connection configuration found = NO"); AppLogger.Info($"HRMS connection configuration loaded from appsettings ({AppSettingsConfiguration.DescribeConfigSources()}).");
AppLogger.Info("HRMS connection configuration found = YES");
return true;
} }
return resolved;
AppLogger.Warning("HRMS connection configuration found = NO");
connectionString = "";
return false;
} }
public static MySqlConnection CreateConnection() public static MySqlConnection CreateConnection()
@ -125,14 +132,9 @@ public static class HrmsConnectionFactory
#endif #endif
} }
private static bool TryGetDevelopmentConnectionString(out string connectionString) private static bool TryGetJsonConnectionString(out string connectionString)
{ {
var configuration = new ConfigurationBuilder() connectionString = AppSettingsConfiguration.GetConnectionString() ?? "";
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
connectionString = configuration.GetConnectionString("Hrms")?.Trim() ?? "";
return !string.IsNullOrWhiteSpace(connectionString); return !string.IsNullOrWhiteSpace(connectionString);
} }
} }