Rapor parametresinin decode işlemi

This commit is contained in:
Sedat ÖZTÜRK 2026-06-29 17:44:30 +03:00
parent c079aee0ca
commit bfaa5224d6
10 changed files with 247 additions and 64 deletions

View file

@ -16,6 +16,7 @@ namespace Sozsoft.Platform.ListForms;
public interface IListFormCustomizationManager
{
Task<List<ListFormCustomization>> GetUsersServerCustomizations(string listFormCode);
Task<ListFormCustomization> GetUserGridState(string listFormCode, string filterName);
Task Delete(Guid id);
}
@ -61,6 +62,21 @@ public class ListFormCustomizationManager : PlatformDomainService, IListFormCust
return customizations;
}
public async Task<ListFormCustomization> GetUserGridState(string listFormCode, string filterName)
{
var items = await listFormCustomizationRepository.GetListAsync(a =>
a.ListFormCode == listFormCode &&
a.CustomizationType == ListFormCustomizationTypeEnum.GridState &&
a.FilterName == filterName &&
((a.UserId == userId && a.RoleId == null) ||
(a.UserId == null && roleIds.Contains(a.RoleId)) ||
(a.UserId == null && a.RoleId == null)));
return items
.OrderByDescending(a => a.UserId != null ? 3 : a.RoleId != null ? 2 : 1)
.FirstOrDefault();
}
public async Task Delete(Guid id)
{
await listFormCustomizationRepository.DeleteAsync(id);

View file

@ -32,11 +32,11 @@ public class DynamicFormReport : XtraReport
private const float RowHeight = 24;
private const float ColumnGap = 10;
private static readonly System.Drawing.Color FieldBorderColor = System.Drawing.Color.FromArgb(226, 232, 240);
private static IServiceProvider _serviceProvider;
private static IServiceScopeFactory _scopeFactory;
public static void SetServiceProvider(IServiceProvider serviceProvider)
public static void SetScopeFactory(IServiceScopeFactory scopeFactory)
{
_serviceProvider = serviceProvider;
_scopeFactory = scopeFactory;
}
public static async Task<DynamicFormReport> CreateAsync(IDictionary<string, string> parameters)
@ -52,7 +52,7 @@ public class DynamicFormReport : XtraReport
private async Task BuildAsync(IDictionary<string, string> parameters)
{
if (_serviceProvider == null)
if (_scopeFactory == null)
{
throw new InvalidOperationException("DynamicFormReport service provider is not configured.");
}
@ -67,7 +67,7 @@ public class DynamicFormReport : XtraReport
throw new ArgumentException("id parameter is required.");
}
using var scope = _serviceProvider.CreateScope();
using var scope = _scopeFactory.CreateScope();
var authManager = scope.ServiceProvider.GetRequiredService<IListFormAuthorizationManager>();
var listFormManager = scope.ServiceProvider.GetRequiredService<IListFormManager>();
var listFormFieldManager = scope.ServiceProvider.GetRequiredService<IListFormFieldManager>();

View file

@ -30,11 +30,11 @@ public class DynamicGridReport : XtraReport
private const int HorizontalMargin = 50;
private const int LandscapeColumnThreshold = 5;
private static readonly System.Drawing.Color TableBorderColor = System.Drawing.Color.FromArgb(226, 232, 240);
private static IServiceProvider _serviceProvider;
private static IServiceScopeFactory _scopeFactory;
public static void SetServiceProvider(IServiceProvider serviceProvider)
public static void SetScopeFactory(IServiceScopeFactory scopeFactory)
{
_serviceProvider = serviceProvider;
_scopeFactory = scopeFactory;
}
public static async Task<DynamicGridReport> CreateAsync(IDictionary<string, string> parameters)
@ -50,7 +50,7 @@ public class DynamicGridReport : XtraReport
private async Task BuildAsync(IDictionary<string, string> parameters)
{
if (_serviceProvider == null)
if (_scopeFactory == null)
{
throw new InvalidOperationException("DynamicListFormReport service provider is not configured.");
}
@ -60,7 +60,7 @@ public class DynamicGridReport : XtraReport
throw new ArgumentException("listFormCode parameter is required.");
}
using var scope = _serviceProvider.CreateScope();
using var scope = _scopeFactory.CreateScope();
var authManager = scope.ServiceProvider.GetRequiredService<IListFormAuthorizationManager>();
var listFormManager = scope.ServiceProvider.GetRequiredService<IListFormManager>();
var listFormFieldManager = scope.ServiceProvider.GetRequiredService<IListFormFieldManager>();
@ -79,6 +79,9 @@ public class DynamicGridReport : XtraReport
}
var listForm = await listFormManager.GetUserListForm(listFormCode);
var gridState = await customizationManager.GetUserGridState(
listFormCode,
DynamicReportGridStateHelper.GetFilterName(listForm, "list"));
var fields = (await listFormFieldManager.GetUserListFormFields(listFormCode))
.Where(x => x.CanRead)
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
@ -103,18 +106,19 @@ public class DynamicGridReport : XtraReport
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
var visibleFields = fields
var availableFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
.Where(x => x.IsActive != false && x.Visible == true)
.Where(x => x.IsActive != false)
.Where(x => selectFieldsByName.ContainsKey(x.FieldName))
.ToList();
var visibleFields = DynamicReportGridStateHelper.GetVisibleFields(
availableFields,
gridState?.CustomizationData);
if (visibleFields.Count == 0)
{
visibleFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
.Where(x => x.IsActive != false)
.Where(x => selectFieldsByName.ContainsKey(x.FieldName))
visibleFields = availableFields
.Where(x => x.Visible == true)
.Take(8)
.ToList();
}

View file

@ -18,14 +18,14 @@ public partial class DynamicReport : XtraReport
private const int VerticalMargin = 35;
private Dictionary<string, string> _parameters;
private static IServiceProvider _serviceProvider;
private static IServiceScopeFactory _scopeFactory;
/// <summary>
/// Uygulama başlangıcında bir kez set edilmeli (Program.cs'de)
/// </summary>
public static void SetServiceProvider(IServiceProvider serviceProvider)
public static void SetScopeFactory(IServiceScopeFactory scopeFactory)
{
_serviceProvider = serviceProvider;
_scopeFactory = scopeFactory;
}
public DynamicReport()
@ -88,12 +88,12 @@ public partial class DynamicReport : XtraReport
{
try
{
if (_serviceProvider == null)
if (_scopeFactory == null)
{
return;
}
using var scope = _serviceProvider.CreateScope();
using var scope = _scopeFactory.CreateScope();
var currentUser = scope.ServiceProvider.GetRequiredService<ICurrentUser>();
currentUserNameLabel.Text = currentUser.UserName ?? string.Empty;
}
@ -106,10 +106,10 @@ public partial class DynamicReport : XtraReport
{
try
{
if (_serviceProvider == null)
if (_scopeFactory == null)
return;
using var scope = _serviceProvider.CreateScope();
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<PlatformDbContext>();
var language = dbContext.Set<Language>()

View file

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Sozsoft.Platform.Entities;
namespace Sozsoft.Reports.PredefinedReports;
internal static class DynamicReportGridStateHelper
{
public static string GetFilterName(ListForm listForm, string prefix) =>
$"{prefix}-{GetStateStorageKey(listForm)}";
public static List<ListFormField> GetVisibleFields(
IReadOnlyList<ListFormField> availableFields,
string gridStateJson)
{
if (!TryGetColumnStates(gridStateJson, out var statesByField))
{
return availableFields
.Where(x => x.Visible == true)
.ToList();
}
return availableFields
.Where(field => statesByField.TryGetValue(field.FieldName, out var state)
? state.Visible
: field.Visible == true)
.OrderBy(field => statesByField.TryGetValue(field.FieldName, out var state)
? state.VisibleIndex
: int.MaxValue)
.ToList();
}
private static string GetStateStorageKey(ListForm listForm)
{
if (!string.IsNullOrWhiteSpace(listForm.StateStoringJson))
{
try
{
using var document = JsonDocument.Parse(listForm.StateStoringJson);
if (TryGetProperty(document.RootElement, "storageKey", out var storageKey) &&
storageKey.ValueKind == JsonValueKind.String &&
!string.IsNullOrWhiteSpace(storageKey.GetString()))
{
return storageKey.GetString();
}
}
catch (JsonException)
{
// Fall back to the same default value used by GridOptionsDto.
}
}
return $"listFormGridStorage-{listForm.ListFormCode}";
}
private static bool TryGetColumnStates(
string gridStateJson,
out Dictionary<string, GridColumnState> statesByField)
{
statesByField = null;
if (string.IsNullOrWhiteSpace(gridStateJson))
{
return false;
}
try
{
using var document = JsonDocument.Parse(gridStateJson);
if (!TryGetProperty(document.RootElement, "columns", out var columns) ||
columns.ValueKind != JsonValueKind.Array)
{
return false;
}
var states = new List<GridColumnState>();
AddColumnStates(columns, true, states);
statesByField = states
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName))
.GroupBy(x => x.FieldName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(x => x.Key, x => x.First(), StringComparer.OrdinalIgnoreCase);
return statesByField.Count > 0;
}
catch (JsonException)
{
return false;
}
}
private static void AddColumnStates(JsonElement columns, bool parentVisible, ICollection<GridColumnState> states)
{
foreach (var column in columns.EnumerateArray())
{
var visible = parentVisible &&
(!TryGetProperty(column, "visible", out var visibleProperty) ||
visibleProperty.ValueKind != JsonValueKind.False);
var fieldName = GetColumnFieldName(column);
var visibleIndex = TryGetProperty(column, "visibleIndex", out var visibleIndexProperty) &&
visibleIndexProperty.TryGetInt32(out var value)
? value
: states.Count;
if (!string.IsNullOrWhiteSpace(fieldName))
{
states.Add(new GridColumnState(fieldName, visible, visibleIndex));
}
if (TryGetProperty(column, "columns", out var childColumns) &&
childColumns.ValueKind == JsonValueKind.Array)
{
AddColumnStates(childColumns, visible, states);
}
}
}
private static string GetColumnFieldName(JsonElement column)
{
if (TryGetProperty(column, "dataField", out var dataField) &&
dataField.ValueKind == JsonValueKind.String)
{
return dataField.GetString();
}
if (TryGetProperty(column, "name", out var name) &&
name.ValueKind == JsonValueKind.String)
{
return name.GetString();
}
return null;
}
private static bool TryGetProperty(JsonElement element, string name, out JsonElement value)
{
if (element.ValueKind == JsonValueKind.Object)
{
foreach (var property in element.EnumerateObject())
{
if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase))
{
value = property.Value;
return true;
}
}
}
value = default;
return false;
}
private sealed record GridColumnState(string FieldName, bool Visible, int VisibleIndex);
}

View file

@ -34,11 +34,11 @@ public class DynamicTreeReport : XtraReport
private const string TreePrefixColumn = "__TreePrefix";
private const string TreeDataSourceName = "DynamicTree";
private static readonly System.Drawing.Color TableBorderColor = System.Drawing.Color.FromArgb(226, 232, 240);
private static IServiceProvider _serviceProvider;
private static IServiceScopeFactory _scopeFactory;
public static void SetServiceProvider(IServiceProvider serviceProvider)
public static void SetScopeFactory(IServiceScopeFactory scopeFactory)
{
_serviceProvider = serviceProvider;
_scopeFactory = scopeFactory;
}
public static async Task<DynamicTreeReport> CreateAsync(IDictionary<string, string> parameters)
@ -54,7 +54,7 @@ public class DynamicTreeReport : XtraReport
private async Task BuildAsync(IDictionary<string, string> parameters)
{
if (_serviceProvider == null)
if (_scopeFactory == null)
{
throw new InvalidOperationException("DynamicTreeReport service provider is not configured.");
}
@ -64,7 +64,7 @@ public class DynamicTreeReport : XtraReport
throw new ArgumentException("listFormCode parameter is required.");
}
using var scope = _serviceProvider.CreateScope();
using var scope = _scopeFactory.CreateScope();
var authManager = scope.ServiceProvider.GetRequiredService<IListFormAuthorizationManager>();
var listFormManager = scope.ServiceProvider.GetRequiredService<IListFormManager>();
var listFormFieldManager = scope.ServiceProvider.GetRequiredService<IListFormFieldManager>();
@ -83,6 +83,9 @@ public class DynamicTreeReport : XtraReport
}
var listForm = await listFormManager.GetUserListForm(listFormCode);
var gridState = await customizationManager.GetUserGridState(
listFormCode,
DynamicReportGridStateHelper.GetFilterName(listForm, "tree"));
var treeOptions = GetTreeOptions(listForm);
if (string.IsNullOrWhiteSpace(treeOptions?.KeyExpr) || string.IsNullOrWhiteSpace(treeOptions.ParentIdExpr))
{
@ -113,18 +116,19 @@ public class DynamicTreeReport : XtraReport
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
var visibleFields = fields
var availableFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
.Where(x => x.IsActive != false && x.Visible == true)
.Where(x => x.IsActive != false)
.Where(x => selectFieldsByName.ContainsKey(x.FieldName))
.ToList();
var visibleFields = DynamicReportGridStateHelper.GetVisibleFields(
availableFields,
gridState?.CustomizationData);
if (visibleFields.Count == 0)
{
visibleFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
.Where(x => x.IsActive != false)
.Where(x => selectFieldsByName.ContainsKey(x.FieldName))
visibleFields = availableFields
.Where(x => x.Visible == true)
.Take(8)
.ToList();
}

View file

@ -21,17 +21,13 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
{
readonly string reportDirectory = "PredefinedReports\\Reports";
const string FileExtension = ".repx";
private readonly IServiceProvider _serviceProvider;
private readonly IServiceScopeFactory _scopeFactory;
public CustomReportStorageWebExtension(IServiceProvider serviceProvider)
public CustomReportStorageWebExtension(IServiceScopeFactory scopeFactory)
{
_serviceProvider = serviceProvider;
_scopeFactory = scopeFactory;
// DynamicReport için ServiceProvider'ı set et
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicTreeReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
ConfigureDynamicReportScopes(scopeFactory);
if (!Directory.Exists(reportDirectory))
{
@ -39,15 +35,11 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
}
}
public CustomReportStorageWebExtension(IServiceProvider serviceProvider, string reportDirectory)
public CustomReportStorageWebExtension(IServiceScopeFactory scopeFactory, string reportDirectory)
{
_serviceProvider = serviceProvider;
_scopeFactory = scopeFactory;
// DynamicReport için ServiceProvider'ı set et
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicTreeReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
ConfigureDynamicReportScopes(scopeFactory);
if (!Directory.Exists(reportDirectory))
{
@ -56,6 +48,14 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
this.reportDirectory = reportDirectory;
}
private static void ConfigureDynamicReportScopes(IServiceScopeFactory scopeFactory)
{
Sozsoft.Reports.PredefinedReports.DynamicReport.SetScopeFactory(scopeFactory);
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetScopeFactory(scopeFactory);
Sozsoft.Reports.PredefinedReports.DynamicTreeReport.SetScopeFactory(scopeFactory);
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetScopeFactory(scopeFactory);
}
private static bool IsSafeReportFileName(string reportName)
{
return !string.IsNullOrWhiteSpace(reportName) &&
@ -242,7 +242,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
throw new InvalidOperationException("A valid report template id is required.");
}
using var scope = _serviceProvider.CreateScope();
using var scope = _scopeFactory.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<IRepository<ReportTemplate, Guid>>();
var template = await repository.FindAsync(reportTemplateId);
if (template == null)
@ -309,7 +309,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
bottomMargin.Controls.Add(userNameLabel);
}
using var scope = _serviceProvider.CreateScope();
using var scope = _scopeFactory.CreateScope();
var currentUser = scope.ServiceProvider.GetService<ICurrentUser>();
userNameLabel.Text = currentUser?.UserName ?? string.Empty;
userNameLabel.BoundsF = new System.Drawing.RectangleF(0, 8, pageWidth / 2, 18);
@ -379,14 +379,14 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
private static (string ReportName, Dictionary<string, string> Parameters) ParseReportUrl(string url)
{
var decodedUrl = DecodeRepeatedly(url);
var parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var queryIndex = decodedUrl.IndexOf('?');
var reportPath = queryIndex < 0 ? decodedUrl : decodedUrl[..queryIndex];
// Split on URL delimiters before decoding so an encoded slash remains part of its parameter.
var queryIndex = url.IndexOf('?');
var reportPath = queryIndex < 0 ? url : url[..queryIndex];
if (queryIndex >= 0)
{
var queryString = decodedUrl[(queryIndex + 1)..];
var queryString = url[(queryIndex + 1)..];
var paramPairs = queryString.Split('&', StringSplitOptions.RemoveEmptyEntries);
foreach (var pair in paramPairs)
@ -401,7 +401,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
var pathParts = reportPath
.Split('/', StringSplitOptions.RemoveEmptyEntries)
.Select(Uri.UnescapeDataString)
.Select(DecodeRepeatedly)
.ToArray();
if (pathParts.Length == 0)

View file

@ -222,7 +222,12 @@ const FormButtons = (props: {
if (onActionNew) {
onActionNew()
} else {
navigate(ROUTES_ENUM.protected.admin.formNew.replace(':listFormCode', listFormCode))
navigate(
ROUTES_ENUM.protected.admin.formNew.replace(
':listFormCode',
encodeURIComponent(listFormCode),
),
)
}
}}
{...(permissions.c ? {} : { disabled: true })}
@ -241,8 +246,8 @@ const FormButtons = (props: {
} else {
navigate(
ROUTES_ENUM.protected.admin.formEdit
.replace(':listFormCode', listFormCode)
.replace(':id', id!),
.replace(':listFormCode', encodeURIComponent(listFormCode))
.replace(':id', encodeURIComponent(id!)),
)
}
}}
@ -262,8 +267,8 @@ const FormButtons = (props: {
} else {
navigate(
ROUTES_ENUM.protected.admin.formView
.replace(':listFormCode', listFormCode)
.replace(':id', id!),
.replace(':listFormCode', encodeURIComponent(listFormCode))
.replace(':id', encodeURIComponent(id!)),
)
}
}}

View file

@ -140,8 +140,8 @@ const useGridData = (props: {
if (!isSubForm) {
navigate(
ROUTES_ENUM.protected.admin.formView
.replace(':listFormCode', listFormCode)
.replace(':id', id!),
.replace(':listFormCode', encodeURIComponent(listFormCode))
.replace(':id', encodeURIComponent(id!)),
)
} else if (props.onSubmitAction) {
props.onSubmitAction()

View file

@ -653,11 +653,12 @@ const useListFormColumns = ({
}
if (!gridDto.gridOptions.keyFieldName) return
if (!gridDto.gridOptions.listFormCode) return
const detailFormName = gridDto.gridOptions.listFormCode
const id = e.row.data[gridDto.gridOptions.keyFieldName]
const url = `/admin/form/${detailFormName}/${id}`
const url = `/admin/form/${encodeURIComponent(detailFormName)}/${encodeURIComponent(String(id))}`
window.open(url, isPwaMode ? '_self' : '_blank')
},