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.
This commit is contained in:
106
DbFirst.API/Dashboards/SqlDashboardStorage.cs
Normal file
106
DbFirst.API/Dashboards/SqlDashboardStorage.cs
Normal file
@@ -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<string?>? _userProvider;
|
||||||
|
|
||||||
|
public SqlDashboardStorage(string connectionString, string tableName = "TBDD_SMF_CONFIG", Func<string?>? userProvider = null)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
_tableName = tableName;
|
||||||
|
_userProvider = userProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<DashboardInfo> GetAvailableDashboardsInfo()
|
||||||
|
{
|
||||||
|
var dashboards = new List<DashboardInfo>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
17
DbFirst.API/Data/Dashboards/DashboardStorage.sql
Normal file
17
DbFirst.API/Data/Dashboards/DashboardStorage.sql
Normal file
@@ -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
|
||||||
@@ -10,48 +10,6 @@
|
|||||||
</JsonDataSource>
|
</JsonDataSource>
|
||||||
</DataSources>
|
</DataSources>
|
||||||
<Items>
|
<Items>
|
||||||
<Grid ComponentName="gridDashboardItem1" Name="Grid 1" DataSource="jsonDataSource1">
|
|
||||||
<DataItems>
|
|
||||||
<Dimension DataMember="Address" DefaultId="DataItem0" />
|
|
||||||
<Dimension DataMember="City" DefaultId="DataItem1" />
|
|
||||||
<Dimension DataMember="CompanyName" DefaultId="DataItem2" />
|
|
||||||
</DataItems>
|
|
||||||
<GridColumns>
|
|
||||||
<GridDimensionColumn>
|
|
||||||
<Dimension DefaultId="DataItem0" />
|
|
||||||
</GridDimensionColumn>
|
|
||||||
<GridDimensionColumn>
|
|
||||||
<Dimension DefaultId="DataItem1" />
|
|
||||||
</GridDimensionColumn>
|
|
||||||
<GridDimensionColumn>
|
|
||||||
<Dimension DefaultId="DataItem2" />
|
|
||||||
</GridDimensionColumn>
|
|
||||||
</GridColumns>
|
|
||||||
<GridOptions />
|
|
||||||
<ColumnFilterOptions />
|
|
||||||
</Grid>
|
|
||||||
<Chart ComponentName="chartDashboardItem1" Name="Chart 1" DataSource="jsonDataSource1">
|
|
||||||
<DataItems>
|
|
||||||
<Measure DataMember="Address" SummaryType="Count" DefaultId="DataItem0" />
|
|
||||||
<Measure DataMember="City" SummaryType="Count" DefaultId="DataItem1" />
|
|
||||||
<Measure DataMember="CompanyName" SummaryType="Count" DefaultId="DataItem2" />
|
|
||||||
</DataItems>
|
|
||||||
<Panes>
|
|
||||||
<Pane Name="Pane 1">
|
|
||||||
<Series>
|
|
||||||
<Simple>
|
|
||||||
<Value DefaultId="DataItem0" />
|
|
||||||
</Simple>
|
|
||||||
<Simple>
|
|
||||||
<Value DefaultId="DataItem1" />
|
|
||||||
</Simple>
|
|
||||||
<Simple>
|
|
||||||
<Value DefaultId="DataItem2" />
|
|
||||||
</Simple>
|
|
||||||
</Series>
|
|
||||||
</Pane>
|
|
||||||
</Panes>
|
|
||||||
</Chart>
|
|
||||||
<Grid ComponentName="gridDashboardItem2" Name="Grid 2" DataSource="catalogsDataSource">
|
<Grid ComponentName="gridDashboardItem2" Name="Grid 2" DataSource="catalogsDataSource">
|
||||||
<DataItems>
|
<DataItems>
|
||||||
<Measure DataMember="guid" DefaultId="DataItem0" />
|
<Measure DataMember="guid" DefaultId="DataItem0" />
|
||||||
@@ -91,13 +49,7 @@
|
|||||||
</Items>
|
</Items>
|
||||||
<LayoutTree>
|
<LayoutTree>
|
||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
<LayoutGroup Orientation="Vertical">
|
|
||||||
<LayoutGroup>
|
|
||||||
<LayoutItem DashboardItem="gridDashboardItem1" />
|
|
||||||
<LayoutItem DashboardItem="gridDashboardItem2" />
|
<LayoutItem DashboardItem="gridDashboardItem2" />
|
||||||
</LayoutGroup>
|
</LayoutGroup>
|
||||||
<LayoutItem DashboardItem="chartDashboardItem1" />
|
|
||||||
</LayoutGroup>
|
|
||||||
</LayoutGroup>
|
|
||||||
</LayoutTree>
|
</LayoutTree>
|
||||||
</Dashboard>
|
</Dashboard>
|
||||||
@@ -26,8 +26,4 @@
|
|||||||
<ProjectReference Include="..\DbFirst.Infrastructure\DbFirst.Infrastructure.csproj" />
|
<ProjectReference Include="..\DbFirst.Infrastructure\DbFirst.Infrastructure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Data\Dashboards\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using DbFirst.API.Middleware;
|
using DbFirst.API.Middleware;
|
||||||
|
using DbFirst.API.Dashboards;
|
||||||
using DbFirst.Application;
|
using DbFirst.Application;
|
||||||
using DbFirst.Application.Repositories;
|
using DbFirst.Application.Repositories;
|
||||||
using DbFirst.Domain;
|
using DbFirst.Domain;
|
||||||
@@ -10,6 +11,7 @@ using DevExpress.DashboardAspNetCore;
|
|||||||
using DevExpress.DashboardCommon;
|
using DevExpress.DashboardCommon;
|
||||||
using DevExpress.DashboardWeb;
|
using DevExpress.DashboardWeb;
|
||||||
using DevExpress.DataAccess.Json;
|
using DevExpress.DataAccess.Json;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -108,7 +110,10 @@ builder.Services.AddScoped<DashboardConfigurator>((IServiceProvider serviceProvi
|
|||||||
}
|
}
|
||||||
|
|
||||||
DashboardConfigurator configurator = new DashboardConfigurator();
|
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();
|
DataSourceInMemoryStorage dataSourceStorage = new DataSourceInMemoryStorage();
|
||||||
DashboardJsonDataSource jsonDataSourceUrl = new DashboardJsonDataSource("JSON Data Source (URL)");
|
DashboardJsonDataSource jsonDataSourceUrl = new DashboardJsonDataSource("JSON Data Source (URL)");
|
||||||
@@ -126,6 +131,10 @@ builder.Services.AddScoped<DashboardConfigurator>((IServiceProvider serviceProvi
|
|||||||
dataSourceStorage.RegisterDataSource(catalogsJsonDataSource.Name, catalogsJsonDataSource.SaveToXml());
|
dataSourceStorage.RegisterDataSource(catalogsJsonDataSource.Name, catalogsJsonDataSource.SaveToXml());
|
||||||
|
|
||||||
configurator.SetDataSourceStorage(dataSourceStorage);
|
configurator.SetDataSourceStorage(dataSourceStorage);
|
||||||
|
|
||||||
|
EnsureDashboardInStorage(dashboardStorage, "DefaultDashboard", defaultDashboardPath);
|
||||||
|
EnsureDashboardInStorage(dashboardStorage, "CatalogsGrid", catalogsGridDashboardPath);
|
||||||
|
|
||||||
return configurator;
|
return configurator;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,3 +159,15 @@ app.MapDashboardRoute("api/dashboard", "DefaultDashboard");
|
|||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user