From 9db55fd2fda5bbfaf8de7a459a1a94f320b46365 Mon Sep 17 00:00:00 2001 From: OlgunR Date: Tue, 3 Feb 2026 11:56:26 +0100 Subject: [PATCH] Switch dashboard storage to SQL Server Replaced file-based dashboard storage with SQL Server-backed storage using the new SqlDashboardStorage class and TBDD_SMF_CONFIG table. Updated Program.cs to use the new storage and ensure default dashboards are loaded into the database. Simplified DefaultDashboard.xml to remove old items. Added SQL script for the dashboard storage table. Cleaned up unused folder references in the project file. This centralizes dashboard management and supports multi-instance scenarios. --- DbFirst.API/Dashboards/SqlDashboardStorage.cs | 106 ++++++++++++++++++ .../Data/Dashboards/DashboardStorage.sql | 17 +++ .../Data/Dashboards/DefaultDashboard.xml | 50 +-------- DbFirst.API/DbFirst.API.csproj | 4 - DbFirst.API/Program.cs | 23 +++- 5 files changed, 146 insertions(+), 54 deletions(-) create mode 100644 DbFirst.API/Dashboards/SqlDashboardStorage.cs create mode 100644 DbFirst.API/Data/Dashboards/DashboardStorage.sql diff --git a/DbFirst.API/Dashboards/SqlDashboardStorage.cs b/DbFirst.API/Dashboards/SqlDashboardStorage.cs new file mode 100644 index 0000000..fced0b2 --- /dev/null +++ b/DbFirst.API/Dashboards/SqlDashboardStorage.cs @@ -0,0 +1,106 @@ +using System.Data; +using System.Text; +using System.Xml.Linq; +using DevExpress.DashboardWeb; +using Microsoft.Data.SqlClient; + +namespace DbFirst.API.Dashboards; + +public sealed class SqlDashboardStorage : IEditableDashboardStorage +{ + private readonly string _connectionString; + private readonly string _tableName; + private readonly Func? _userProvider; + + public SqlDashboardStorage(string connectionString, string tableName = "TBDD_SMF_CONFIG", Func? userProvider = null) + { + _connectionString = connectionString; + _tableName = tableName; + _userProvider = userProvider; + } + + public IEnumerable GetAvailableDashboardsInfo() + { + var dashboards = new List(); + + using var connection = new SqlConnection(_connectionString); + using var command = new SqlCommand($"SELECT DashboardId, DashboardName FROM dbo.[{_tableName}] WHERE ACTIVE = 1 ORDER BY DashboardName", connection); + + connection.Open(); + using var reader = command.ExecuteReader(); + while (reader.Read()) + { + var id = reader.GetString(0); + var name = reader.GetString(1); + dashboards.Add(new DashboardInfo { ID = id, Name = name }); + } + + return dashboards; + } + + public XDocument LoadDashboard(string dashboardId) + { + using var connection = new SqlConnection(_connectionString); + using var command = new SqlCommand($"SELECT DashboardData FROM dbo.[{_tableName}] WHERE DashboardId = @Id AND ACTIVE = 1", connection); + command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId }); + + connection.Open(); + var data = command.ExecuteScalar() as byte[]; + if (data == null) + { + throw new ArgumentException($"Dashboard '{dashboardId}' not found."); + } + + var xml = Encoding.UTF8.GetString(data); + return XDocument.Parse(xml); + } + + public string AddDashboard(XDocument dashboard, string dashboardName) + { + var id = string.IsNullOrWhiteSpace(dashboardName) + ? Guid.NewGuid().ToString("N") + : dashboardName; + var payload = Encoding.UTF8.GetBytes(dashboard.ToString(SaveOptions.DisableFormatting)); + var userName = _userProvider?.Invoke(); + + using var connection = new SqlConnection(_connectionString); + using var command = new SqlCommand($"INSERT INTO dbo.[{_tableName}] (ACTIVE, DashboardId, DashboardName, DashboardData, ADDED_WHO, ADDED_WHEN) VALUES (1, @Id, @Name, @Data, COALESCE(@User, SUSER_SNAME()), SYSUTCDATETIME())", connection); + command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = id }); + command.Parameters.Add(new SqlParameter("@Name", SqlDbType.NVarChar, 256) { Value = string.IsNullOrWhiteSpace(dashboardName) ? id : dashboardName }); + command.Parameters.Add(new SqlParameter("@Data", SqlDbType.VarBinary, -1) { Value = payload }); + command.Parameters.Add(new SqlParameter("@User", SqlDbType.NVarChar, 50) { Value = (object?)userName ?? DBNull.Value }); + + connection.Open(); + command.ExecuteNonQuery(); + return id; + } + + public void SaveDashboard(string dashboardId, XDocument dashboard) + { + var payload = Encoding.UTF8.GetBytes(dashboard.ToString(SaveOptions.DisableFormatting)); + var userName = _userProvider?.Invoke(); + + using var connection = new SqlConnection(_connectionString); + using var command = new SqlCommand($"UPDATE dbo.[{_tableName}] SET DashboardData = @Data, CHANGED_WHO = COALESCE(@User, SUSER_SNAME()), CHANGED_WHEN = SYSUTCDATETIME() WHERE DashboardId = @Id", connection); + command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId }); + command.Parameters.Add(new SqlParameter("@Data", SqlDbType.VarBinary, -1) { Value = payload }); + command.Parameters.Add(new SqlParameter("@User", SqlDbType.NVarChar, 50) { Value = (object?)userName ?? DBNull.Value }); + + connection.Open(); + var rows = command.ExecuteNonQuery(); + if (rows == 0) + { + throw new ArgumentException($"Dashboard '{dashboardId}' not found."); + } + } + + public void DeleteDashboard(string dashboardId) + { + using var connection = new SqlConnection(_connectionString); + using var command = new SqlCommand($"DELETE FROM dbo.[{_tableName}] WHERE DashboardId = @Id", connection); + command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId }); + + connection.Open(); + command.ExecuteNonQuery(); + } +} diff --git a/DbFirst.API/Data/Dashboards/DashboardStorage.sql b/DbFirst.API/Data/Dashboards/DashboardStorage.sql new file mode 100644 index 0000000..2afd9d0 --- /dev/null +++ b/DbFirst.API/Data/Dashboards/DashboardStorage.sql @@ -0,0 +1,17 @@ +CREATE TABLE dbo.TBDD_SMF_CONFIG ( + [GUID] [bigint] IDENTITY(1,1) NOT NULL, + [ACTIVE] [bit] NOT NULL, + DashboardId NVARCHAR(128) NOT NULL, + DashboardName NVARCHAR(256) NOT NULL, + DashboardData VARBINARY(MAX) NOT NULL, + --- INSERT YOUR COLUMNS HERE --- + [ADDED_WHO] [nvarchar](50) NOT NULL, + [ADDED_WHEN] [datetime] NOT NULL, + [CHANGED_WHO] [nvarchar](50) NULL, + [CHANGED_WHEN] [datetime] NULL, + CONSTRAINT [PK_TBDD_SMF_CONFIG_DashboardStorage] PRIMARY KEY CLUSTERED +( + [GUID] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = ON, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] +) ON [PRIMARY] +GO diff --git a/DbFirst.API/Data/Dashboards/DefaultDashboard.xml b/DbFirst.API/Data/Dashboards/DefaultDashboard.xml index c4986d7..9c1ba8d 100644 --- a/DbFirst.API/Data/Dashboards/DefaultDashboard.xml +++ b/DbFirst.API/Data/Dashboards/DefaultDashboard.xml @@ -10,48 +10,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -91,13 +49,7 @@ - - - - - - - + \ No newline at end of file diff --git a/DbFirst.API/DbFirst.API.csproj b/DbFirst.API/DbFirst.API.csproj index 4f70300..0cd3dea 100644 --- a/DbFirst.API/DbFirst.API.csproj +++ b/DbFirst.API/DbFirst.API.csproj @@ -26,8 +26,4 @@ - - - - diff --git a/DbFirst.API/Program.cs b/DbFirst.API/Program.cs index a226710..a3405d2 100644 --- a/DbFirst.API/Program.cs +++ b/DbFirst.API/Program.cs @@ -1,4 +1,5 @@ using DbFirst.API.Middleware; +using DbFirst.API.Dashboards; using DbFirst.Application; using DbFirst.Application.Repositories; using DbFirst.Domain; @@ -10,6 +11,7 @@ using DevExpress.DashboardAspNetCore; using DevExpress.DashboardCommon; using DevExpress.DashboardWeb; using DevExpress.DataAccess.Json; +using System.Xml.Linq; var builder = WebApplication.CreateBuilder(args); @@ -108,7 +110,10 @@ builder.Services.AddScoped((IServiceProvider serviceProvi } DashboardConfigurator configurator = new DashboardConfigurator(); - configurator.SetDashboardStorage(new DashboardFileStorage(dashboardsPath)); + + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? string.Empty; + var dashboardStorage = new SqlDashboardStorage(connectionString, "TBDD_SMF_CONFIG"); + configurator.SetDashboardStorage(dashboardStorage); DataSourceInMemoryStorage dataSourceStorage = new DataSourceInMemoryStorage(); DashboardJsonDataSource jsonDataSourceUrl = new DashboardJsonDataSource("JSON Data Source (URL)"); @@ -126,6 +131,10 @@ builder.Services.AddScoped((IServiceProvider serviceProvi dataSourceStorage.RegisterDataSource(catalogsJsonDataSource.Name, catalogsJsonDataSource.SaveToXml()); configurator.SetDataSourceStorage(dataSourceStorage); + + EnsureDashboardInStorage(dashboardStorage, "DefaultDashboard", defaultDashboardPath); + EnsureDashboardInStorage(dashboardStorage, "CatalogsGrid", catalogsGridDashboardPath); + return configurator; }); @@ -150,3 +159,15 @@ app.MapDashboardRoute("api/dashboard", "DefaultDashboard"); app.MapControllers(); app.Run(); + +static void EnsureDashboardInStorage(IEditableDashboardStorage storage, string id, string filePath) +{ + var exists = storage.GetAvailableDashboardsInfo().Any(info => string.Equals(info.ID, id, StringComparison.OrdinalIgnoreCase)); + if (exists || !File.Exists(filePath)) + { + return; + } + + var doc = XDocument.Load(filePath); + storage.AddDashboard(doc, id); +}