Devexpress FormReport ve GridReport Optimize

This commit is contained in:
Sedat ÖZTÜRK 2026-06-17 17:59:10 +03:00
parent cf90472088
commit ecb32a502a
4 changed files with 363 additions and 601 deletions

View file

@ -4,7 +4,6 @@ using System.Data;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using DevExpress.DataAccess.ConnectionParameters; using DevExpress.DataAccess.ConnectionParameters;
using DevExpress.DataAccess.Sql; using DevExpress.DataAccess.Sql;
@ -95,7 +94,7 @@ public class DynamicFormReport : XtraReport
var queryParams = new QueryParameters var queryParams = new QueryParameters
{ {
ListFormCode = listFormCode, ListFormCode = listFormCode,
Filter = GetParameter(parameters, "filter") ?? CreateKeyFilter(listForm.KeyFieldName, id), Filter = DynamicReportImageHelper.GetParameter(parameters, "filter") ?? CreateKeyFilter(listForm.KeyFieldName, id),
Take = 1, Take = 1,
Skip = 0 Skip = 0
}; };
@ -103,7 +102,9 @@ public class DynamicFormReport : XtraReport
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams); selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
var reportFields = GetReportFields(listForm, fields, selectQueryManager, defaultValueHelper); var reportFields = GetReportFields(listForm, fields, selectQueryManager, defaultValueHelper);
var selectQuery = StripReportPaging(ApplyQueryParameters(selectQueryManager.SelectQuery, selectQueryManager.SelectQueryParameters)); var selectQuery = DynamicReportImageHelper.StripReportPaging(DynamicReportImageHelper.ApplyQueryParameters(
selectQueryManager.SelectQuery,
selectQueryManager.SelectQueryParameters));
selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns( selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns(
selectQuery, selectQuery,
reportFields, reportFields,
@ -114,7 +115,7 @@ public class DynamicFormReport : XtraReport
ConfigurePage(); ConfigurePage();
ConfigureDataSource(selectQuery, connectionString, dataSourceType); ConfigureDataSource(selectQuery, connectionString, dataSourceType);
await BuildLayoutAsync(listForm, reportFields, row, localizer, GetImageBaseUrl(configuration)); await BuildLayoutAsync(listForm, reportFields, row, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration));
RequestParameters = false; RequestParameters = false;
} }
@ -139,7 +140,8 @@ public class DynamicFormReport : XtraReport
private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType) private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType)
{ {
var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType))); var dataSource = new SqlDataSource(new CustomStringConnectionParameters(
DynamicReportImageHelper.ToDevExpressConnectionString(connectionString, dataSourceType)));
dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery)); dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery));
ComponentStorage.Add(dataSource); ComponentStorage.Add(dataSource);
DataSource = dataSource; DataSource = dataSource;
@ -154,18 +156,20 @@ public class DynamicFormReport : XtraReport
string imageBaseUrl) string imageBaseUrl)
{ {
Bands.Clear(); Bands.Clear();
var formGroups = GetFormGroups(listForm, fields).ToList();
var listSubForms = GetListSubForms(listForm, row).ToList();
var topMargin = new TopMarginBand { HeightF = Margins.Top }; var topMargin = new TopMarginBand { HeightF = Margins.Top };
var bottomMargin = new BottomMarginBand { HeightF = 32 }; var bottomMargin = new BottomMarginBand { HeightF = 32 };
var reportHeader = new ReportHeaderBand { HeightF = 38 }; var reportHeader = new ReportHeaderBand { HeightF = 38 };
var detail = new DetailBand { HeightF = CalculateDetailHeight(fields, listForm, row) }; var detail = new DetailBand { HeightF = CalculateDetailHeight(formGroups, listSubForms.Count) };
Bands.AddRange([topMargin, reportHeader, detail, bottomMargin]); Bands.AddRange([topMargin, reportHeader, detail, bottomMargin]);
var pageWidth = PageWidth - Margins.Left - Margins.Right; var pageWidth = PageWidth - Margins.Left - Margins.Right;
reportHeader.Controls.Add(new XRLabel reportHeader.Controls.Add(new XRLabel
{ {
Text = GetLocalizedReportTitle(listForm, localizer), Text = DynamicReportImageHelper.GetLocalizedReportTitle(listForm, localizer),
BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30), BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30),
Font = new DXFont("Arial", 14F, DXFontStyle.Bold), Font = new DXFont("Arial", 14F, DXFontStyle.Bold),
TextAlignment = TextAlignment.MiddleLeft TextAlignment = TextAlignment.MiddleLeft
@ -188,13 +192,13 @@ public class DynamicFormReport : XtraReport
}); });
var y = 0F; var y = 0F;
foreach (var group in GetFormGroups(listForm, fields)) foreach (var group in formGroups)
{ {
if (!string.IsNullOrWhiteSpace(group.Caption)) if (!string.IsNullOrWhiteSpace(group.Caption))
{ {
detail.Controls.Add(new XRLabel detail.Controls.Add(new XRLabel
{ {
Text = Localize(localizer, group.Caption, group.Caption), Text = DynamicReportImageHelper.Localize(localizer, group.Caption, group.Caption),
BoundsF = new System.Drawing.RectangleF(0, y, pageWidth, 22), BoundsF = new System.Drawing.RectangleF(0, y, pageWidth, 22),
Font = new DXFont("Arial", 9F, DXFontStyle.Bold), Font = new DXFont("Arial", 9F, DXFontStyle.Bold),
BackColor = System.Drawing.Color.FromArgb(238, 242, 247), BackColor = System.Drawing.Color.FromArgb(238, 242, 247),
@ -223,7 +227,7 @@ public class DynamicFormReport : XtraReport
y += 8; y += 8;
} }
foreach (var subForm in GetListSubForms(listForm, row)) foreach (var subForm in listSubForms)
{ {
detail.Controls.Add(new XRSubreport detail.Controls.Add(new XRSubreport
{ {
@ -246,7 +250,7 @@ public class DynamicFormReport : XtraReport
{ {
return new XRLabel return new XRLabel
{ {
Text = GetLocalizedFieldCaption(field.Field, localizer), Text = DynamicReportImageHelper.GetLocalizedFieldCaption(field.Field, localizer),
BoundsF = new System.Drawing.RectangleF(x, y, width, height), BoundsF = new System.Drawing.RectangleF(x, y, width, height),
Font = new DXFont("Arial", 8F, DXFontStyle.Bold), Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
BackColor = System.Drawing.Color.FromArgb(248, 250, 252), BackColor = System.Drawing.Color.FromArgb(248, 250, 252),
@ -280,7 +284,7 @@ public class DynamicFormReport : XtraReport
BorderColor = FieldBorderColor, BorderColor = FieldBorderColor,
BorderWidth = 0.25F, BorderWidth = 0.25F,
Padding = new PaddingInfo(5, 5, 2, 2), Padding = new PaddingInfo(5, 5, 2, 2),
TextAlignment = ToTextAlignment(field.Field.Alignment, field.Field.SourceDbType), TextAlignment = DynamicReportImageHelper.ToTextAlignment(field.Field.Alignment, field.Field.SourceDbType),
Multiline = true, Multiline = true,
CanGrow = true CanGrow = true
}; };
@ -392,19 +396,13 @@ public class DynamicFormReport : XtraReport
return null; return null;
} }
if (TryGetRowValue(row, field.BindingName, out var value) && HasValue(value)) if (DynamicReportImageHelper.TryGetRowValue(row, field.BindingName, out var value) &&
DynamicReportImageHelper.HasValue(value))
{ {
return value; return value;
} }
return TryGetRowValue(row, field.Field.FieldName, out value) ? value : null; return DynamicReportImageHelper.TryGetRowValue(row, field.Field.FieldName, out value) ? value : null;
}
private static bool HasValue(object value)
{
return value != null &&
value != DBNull.Value &&
!string.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture));
} }
private static bool TryGetFormattedValue( private static bool TryGetFormattedValue(
@ -413,7 +411,7 @@ public class DynamicFormReport : XtraReport
out string text) out string text)
{ {
text = null; text = null;
if (row == null || !TryGetRowValue(row, field.BindingName, out var rawValue)) if (row == null || !DynamicReportImageHelper.TryGetRowValue(row, field.BindingName, out var rawValue))
{ {
return false; return false;
} }
@ -436,15 +434,14 @@ public class DynamicFormReport : XtraReport
} }
private static float CalculateDetailHeight( private static float CalculateDetailHeight(
IReadOnlyList<FormReportField> fields, IReadOnlyList<FormGroup> groups,
ListForm listForm, int subFormCount)
IDictionary<string, object> row)
{ {
var groupHeight = GetFormGroups(listForm, fields) var groupHeight = groups
.Sum(group => (string.IsNullOrWhiteSpace(group.Caption) ? 0 : 26) + .Sum(group => (string.IsNullOrWhiteSpace(group.Caption) ? 0 : 26) +
group.Fields.Sum(field => GetFieldRowHeight(field) + 3) + 8); group.Fields.Sum(field => GetFieldRowHeight(field) + 3) + 8);
var subFormHeight = GetListSubForms(listForm, row).Count() * 32; var subFormHeight = subFormCount * 32;
return Math.Max(groupHeight + subFormHeight, RowHeight); return Math.Max(groupHeight + subFormHeight, RowHeight);
} }
@ -486,11 +483,12 @@ public class DynamicFormReport : XtraReport
.Select(x => new FieldWithEditor(x, null)) .Select(x => new FieldWithEditor(x, null))
.ToList(); .ToList();
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
return orderedFields return orderedFields
.Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.Field.FieldName)) .Where(x => selectFieldsByName.ContainsKey(x.Field.FieldName))
.Select(x => .Select(x =>
{ {
var selectField = selectQueryManager.SelectFields.First(s => s.FieldName == x.Field.FieldName); var selectField = selectFieldsByName[x.Field.FieldName];
return CreateFormReportField(x.Field, selectField, x.EditorType, defaultValueHelper); return CreateFormReportField(x.Field, selectField, x.EditorType, defaultValueHelper);
}) })
.Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined") .Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined")
@ -584,11 +582,12 @@ public class DynamicFormReport : XtraReport
var relation = subForm.Relation? var relation = subForm.Relation?
.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.ParentFieldName) && .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.ParentFieldName) &&
!string.IsNullOrWhiteSpace(x.ChildFieldName) && !string.IsNullOrWhiteSpace(x.ChildFieldName) &&
TryGetRowValue(row, x.ParentFieldName, out var value) && DynamicReportImageHelper.TryGetRowValue(row, x.ParentFieldName, out var value) &&
value != null && value != null &&
value != DBNull.Value); value != DBNull.Value);
if (relation == null || !TryGetRowValue(row, relation.ParentFieldName, out var parentValue)) if (relation == null ||
!DynamicReportImageHelper.TryGetRowValue(row, relation.ParentFieldName, out var parentValue))
{ {
continue; continue;
} }
@ -612,8 +611,8 @@ public class DynamicFormReport : XtraReport
string editorType, string editorType,
DefaultValueHelper defaultValueHelper) DefaultValueHelper defaultValueHelper)
{ {
var bindingName = GetReportFieldName(selectField); var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField);
var lookup = GetLookup(field); var lookup = DynamicReportImageHelper.GetLookup(field);
if (lookup == null || selectField?.JoinOptions != null) if (lookup == null || selectField?.JoinOptions != null)
{ {
@ -627,7 +626,7 @@ public class DynamicFormReport : XtraReport
if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData)
{ {
var staticLookupValues = GetStaticLookupValues(lookup, defaultValueHelper); var staticLookupValues = DynamicReportImageHelper.GetStaticLookupValues(lookup, defaultValueHelper);
return new FormReportField( return new FormReportField(
field, field,
staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName, staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName,
@ -661,238 +660,6 @@ public class DynamicFormReport : XtraReport
return null; return null;
} }
private static bool TryGetRowValue(IDictionary<string, object> row, string name, out object value)
{
foreach (var item in row)
{
if (string.Equals(item.Key, name, StringComparison.OrdinalIgnoreCase))
{
value = item.Value;
return true;
}
}
value = null;
return false;
}
private static string ToDevExpressConnectionString(string connectionString, DataSourceTypeEnum dataSourceType)
{
if (string.IsNullOrWhiteSpace(connectionString) ||
connectionString.Contains("XpoProvider=", StringComparison.OrdinalIgnoreCase))
{
return connectionString;
}
var provider = dataSourceType switch
{
DataSourceTypeEnum.Mssql => "MSSqlServer",
DataSourceTypeEnum.Postgresql => "Postgres",
_ => null
};
return string.IsNullOrWhiteSpace(provider)
? connectionString
: $"XpoProvider={provider};{connectionString}";
}
private static string ApplyQueryParameters(string sql, Dictionary<string, object> parameters)
{
if (string.IsNullOrWhiteSpace(sql) || parameters == null || parameters.Count == 0)
{
return sql;
}
foreach (var parameter in parameters.OrderByDescending(x => x.Key.Length))
{
var key = parameter.Key.StartsWith("@") ? parameter.Key : "@" + parameter.Key;
sql = Regex.Replace(
sql,
Regex.Escape(key) + @"\b",
ToSqlLiteral(parameter.Value),
RegexOptions.CultureInvariant);
}
return sql;
}
private static string StripReportPaging(string sql)
{
if (string.IsNullOrWhiteSpace(sql))
{
return sql;
}
return Regex.Replace(
sql,
@"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
private static string ToSqlLiteral(object value)
{
if (value == null || value == DBNull.Value)
{
return "NULL";
}
return value switch
{
string text => $"'{text.Replace("'", "''")}'",
Guid guid => $"'{guid}'",
DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'",
DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'",
bool boolean => boolean ? "1" : "0",
byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal
=> Convert.ToString(value, CultureInfo.InvariantCulture),
_ => $"'{value.ToString()?.Replace("'", "''")}'"
};
}
private static string GetReportFieldName(SelectField selectField)
{
if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias))
{
return selectField.JoinOptions.FieldNameAlias;
}
return selectField?.FieldName;
}
private static LookupDto GetLookup(ListFormField field)
{
if (string.IsNullOrWhiteSpace(field.LookupJson))
{
return null;
}
try
{
return JsonSerializer.Deserialize<LookupDto>(field.LookupJson);
}
catch (JsonException)
{
return null;
}
}
private static Dictionary<string, string> GetStaticLookupValues(
LookupDto lookup,
DefaultValueHelper defaultValueHelper)
{
if (string.IsNullOrWhiteSpace(lookup.LookupQuery))
{
return null;
}
try
{
var lookupJson = defaultValueHelper.GetDefaultValue(lookup.LookupQuery);
using var document = JsonDocument.Parse(lookupJson);
if (document.RootElement.ValueKind != JsonValueKind.Array)
{
return null;
}
var valueExpr = lookup.ValueExpr ?? "Key";
var displayExpr = lookup.DisplayExpr ?? "Name";
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in document.RootElement.EnumerateArray())
{
if (!TryGetProperty(item, valueExpr, out var valueElement) ||
!TryGetProperty(item, displayExpr, out var displayElement))
{
continue;
}
var value = GetJsonScalar(valueElement);
if (value == null)
{
continue;
}
values[value] = GetJsonScalar(displayElement) ?? value;
}
return values.Count == 0 ? null : values;
}
catch (JsonException)
{
return null;
}
}
private static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement value)
{
foreach (var property in element.EnumerateObject())
{
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
{
value = property.Value;
return true;
}
}
value = default;
return false;
}
private static string GetJsonScalar(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.GetRawText(),
JsonValueKind.True => bool.TrueString,
JsonValueKind.False => bool.FalseString,
JsonValueKind.Null => null,
_ => element.ToString()
};
}
private static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer<PlatformResource> localizer)
{
var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title;
return Localize(localizer, title, listForm.ListFormCode);
}
private static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer<PlatformResource> localizer)
{
var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
return Localize(localizer, caption, field.FieldName);
}
private static string Localize(IStringLocalizer<PlatformResource> localizer, string key, string fallback)
{
if (string.IsNullOrWhiteSpace(key))
{
return fallback;
}
var localized = localizer[key];
return localized.ResourceNotFound || string.IsNullOrWhiteSpace(localized.Value)
? key
: localized.Value;
}
private static TextAlignment ToTextAlignment(string alignment, DbType dbType)
{
return alignment?.ToLowerInvariant() switch
{
"center" => TextAlignment.MiddleCenter,
"right" => TextAlignment.MiddleRight,
"left" => TextAlignment.MiddleLeft,
_ => IsNumeric(dbType) ? TextAlignment.MiddleRight : TextAlignment.MiddleLeft
};
}
private static bool IsNumeric(DbType dbType)
{
return dbType is DbType.Currency or DbType.Decimal or DbType.Double or DbType.Int16 or DbType.Int32
or DbType.Int64 or DbType.Single or DbType.UInt16 or DbType.UInt32 or DbType.UInt64;
}
private static bool IsBooleanEditor(FormReportField field) private static bool IsBooleanEditor(FormReportField field)
{ {
return field.EditorType is PlatformConsts.EditorTypes.dxCheckBox or PlatformConsts.EditorTypes.dxSwitch || return field.EditorType is PlatformConsts.EditorTypes.dxCheckBox or PlatformConsts.EditorTypes.dxSwitch ||
@ -928,16 +695,6 @@ public class DynamicFormReport : XtraReport
return false; return false;
} }
private static string GetParameter(IDictionary<string, string> parameters, string name)
{
return parameters.TryGetValue(name, out var value) ? value : null;
}
private static string GetImageBaseUrl(IConfiguration configuration)
{
return configuration["App:CdnUrl"] ?? configuration["App:SelfUrl"];
}
private sealed record FieldWithEditor(ListFormField Field, string EditorType); private sealed record FieldWithEditor(ListFormField Field, string EditorType);
private sealed record FormGroup(string Caption, IReadOnlyList<FormReportField> Fields); private sealed record FormGroup(string Caption, IReadOnlyList<FormReportField> Fields);
private sealed record FormReportField( private sealed record FormReportField(

View file

@ -1,10 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using DevExpress.DataAccess.ConnectionParameters; using DevExpress.DataAccess.ConnectionParameters;
using DevExpress.DataAccess.Sql; using DevExpress.DataAccess.Sql;
@ -100,11 +97,13 @@ public class DynamicGridReport : XtraReport
}; };
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams); selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
var visibleFields = fields var visibleFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined") .Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
.Where(x => x.IsActive != false && x.Visible == true) .Where(x => x.IsActive != false && x.Visible == true)
.Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.FieldName)) .Where(x => selectFieldsByName.ContainsKey(x.FieldName))
.ToList(); .ToList();
if (visibleFields.Count == 0) if (visibleFields.Count == 0)
@ -112,7 +111,7 @@ public class DynamicGridReport : XtraReport
visibleFields = fields visibleFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined") .Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
.Where(x => x.IsActive != false) .Where(x => x.IsActive != false)
.Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.FieldName)) .Where(x => selectFieldsByName.ContainsKey(x.FieldName))
.Take(8) .Take(8)
.ToList(); .ToList();
} }
@ -120,24 +119,27 @@ public class DynamicGridReport : XtraReport
var reportColumns = visibleFields var reportColumns = visibleFields
.Select(field => .Select(field =>
{ {
var selectField = selectQueryManager.SelectFields.First(s => s.FieldName == field.FieldName); var selectField = selectFieldsByName[field.FieldName];
editorTypesByField.TryGetValue(field.FieldName, out var editorType);
return CreateReportColumn( return CreateReportColumn(
field, field,
selectField, selectField,
defaultValueHelper, defaultValueHelper,
DynamicReportImageHelper.HasImageEditor(listForm, field.FieldName), DynamicReportImageHelper.IsImageEditor(editorType),
HasHtmlEditor(listForm, field.FieldName)); editorType == PlatformConsts.EditorTypes.dxHtmlEditor);
}) })
.Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined") .Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined")
.ToList(); .ToList();
var selectQuery = StripReportPaging(ApplyQueryParameters(selectQueryManager.SelectQuery, selectQueryManager.SelectQueryParameters)); var selectQuery = DynamicReportImageHelper.StripReportPaging(DynamicReportImageHelper.ApplyQueryParameters(
selectQueryManager.SelectQuery,
selectQueryManager.SelectQueryParameters));
selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns( selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns(
selectQuery, selectQuery,
reportColumns, reportColumns,
defaultValueHelper, defaultValueHelper,
dataSourceType); dataSourceType);
var imageBaseUrl = GetImageBaseUrl(configuration); var imageBaseUrl = DynamicReportImageHelper.GetImageBaseUrl(configuration);
reportColumns = await ApplyImageCountsAsync(reportColumns, dynamicDataRepository, selectQuery, connectionString, imageBaseUrl); reportColumns = await ApplyImageCountsAsync(reportColumns, dynamicDataRepository, selectQuery, connectionString, imageBaseUrl);
ConfigurePage(reportColumns); ConfigurePage(reportColumns);
@ -172,87 +174,14 @@ public class DynamicGridReport : XtraReport
private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType) private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType)
{ {
var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType))); var dataSource = new SqlDataSource(new CustomStringConnectionParameters(
DynamicReportImageHelper.ToDevExpressConnectionString(connectionString, dataSourceType)));
dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery)); dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery));
ComponentStorage.Add(dataSource); ComponentStorage.Add(dataSource);
DataSource = dataSource; DataSource = dataSource;
DataMember = QueryName; DataMember = QueryName;
} }
private static string ToDevExpressConnectionString(string connectionString, DataSourceTypeEnum dataSourceType)
{
if (string.IsNullOrWhiteSpace(connectionString) ||
connectionString.Contains("XpoProvider=", StringComparison.OrdinalIgnoreCase))
{
return connectionString;
}
var provider = dataSourceType switch
{
DataSourceTypeEnum.Mssql => "MSSqlServer",
DataSourceTypeEnum.Postgresql => "Postgres",
_ => null
};
return string.IsNullOrWhiteSpace(provider)
? connectionString
: $"XpoProvider={provider};{connectionString}";
}
private static string ApplyQueryParameters(string sql, Dictionary<string, object> parameters)
{
if (string.IsNullOrWhiteSpace(sql) || parameters == null || parameters.Count == 0)
{
return sql;
}
foreach (var parameter in parameters.OrderByDescending(x => x.Key.Length))
{
var key = parameter.Key.StartsWith("@") ? parameter.Key : "@" + parameter.Key;
sql = Regex.Replace(
sql,
Regex.Escape(key) + @"\b",
ToSqlLiteral(parameter.Value),
RegexOptions.CultureInvariant);
}
return sql;
}
private static string StripReportPaging(string sql)
{
if (string.IsNullOrWhiteSpace(sql))
{
return sql;
}
return Regex.Replace(
sql,
@"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
private static string ToSqlLiteral(object value)
{
if (value == null || value == DBNull.Value)
{
return "NULL";
}
return value switch
{
string text => $"'{text.Replace("'", "''")}'",
Guid guid => $"'{guid}'",
DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'",
DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'",
bool boolean => boolean ? "1" : "0",
byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal
=> Convert.ToString(value, CultureInfo.InvariantCulture),
_ => $"'{value.ToString()?.Replace("'", "''")}'"
};
}
private static async Task<List<ReportColumn>> ApplyImageCountsAsync( private static async Task<List<ReportColumn>> ApplyImageCountsAsync(
IReadOnlyList<ReportColumn> columns, IReadOnlyList<ReportColumn> columns,
IDynamicDataRepository dynamicDataRepository, IDynamicDataRepository dynamicDataRepository,
@ -292,34 +221,13 @@ public class DynamicGridReport : XtraReport
private static object GetImageRawValue(IDictionary<string, object> row, ReportColumn column) private static object GetImageRawValue(IDictionary<string, object> row, ReportColumn column)
{ {
if (TryGetRowValue(row, column.BindingName, out var value) && HasValue(value)) if (DynamicReportImageHelper.TryGetRowValue(row, column.BindingName, out var value) &&
DynamicReportImageHelper.HasValue(value))
{ {
return value; return value;
} }
return TryGetRowValue(row, column.Field.FieldName, out value) ? value : null; return DynamicReportImageHelper.TryGetRowValue(row, column.Field.FieldName, out value) ? value : null;
}
private static bool HasValue(object value)
{
return value != null &&
value != DBNull.Value &&
!string.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture));
}
private static bool TryGetRowValue(IDictionary<string, object> row, string name, out object value)
{
foreach (var item in row)
{
if (string.Equals(item.Key, name, StringComparison.OrdinalIgnoreCase))
{
value = item.Value;
return true;
}
}
value = null;
return false;
} }
private void BuildLayout( private void BuildLayout(
@ -347,7 +255,7 @@ public class DynamicGridReport : XtraReport
var pageWidth = PageWidth - Margins.Left - Margins.Right; var pageWidth = PageWidth - Margins.Left - Margins.Right;
reportHeader.Controls.Add(new XRLabel reportHeader.Controls.Add(new XRLabel
{ {
Text = GetLocalizedReportTitle(listForm, localizer), Text = DynamicReportImageHelper.GetLocalizedReportTitle(listForm, localizer),
BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30), BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30),
Font = new DXFont("Arial", 14F, DXFontStyle.Bold), Font = new DXFont("Arial", 14F, DXFontStyle.Bold),
TextAlignment = TextAlignment.MiddleLeft TextAlignment = TextAlignment.MiddleLeft
@ -419,7 +327,7 @@ public class DynamicGridReport : XtraReport
var field = column.Field; var field = column.Field;
return new XRTableCell return new XRTableCell
{ {
Text = GetLocalizedFieldCaption(field, localizer), Text = DynamicReportImageHelper.GetLocalizedFieldCaption(field, localizer),
WidthF = width, WidthF = width,
Weight = width, Weight = width,
Font = new DXFont("Arial", 8F, DXFontStyle.Bold), Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
@ -430,31 +338,6 @@ public class DynamicGridReport : XtraReport
}; };
} }
private static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer<PlatformResource> localizer)
{
var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title;
return Localize(localizer, title, listForm.ListFormCode);
}
private static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer<PlatformResource> localizer)
{
var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
return Localize(localizer, caption, field.FieldName);
}
private static string Localize(IStringLocalizer<PlatformResource> localizer, string key, string fallback)
{
if (string.IsNullOrWhiteSpace(key))
{
return fallback;
}
var localized = localizer[key];
return localized.ResourceNotFound || string.IsNullOrWhiteSpace(localized.Value)
? key
: localized.Value;
}
private static XRTableCell CreateDetailCell(ReportColumn column, float width, string imageBaseUrl, float height) private static XRTableCell CreateDetailCell(ReportColumn column, float width, string imageBaseUrl, float height)
{ {
var field = column.Field; var field = column.Field;
@ -479,7 +362,7 @@ public class DynamicGridReport : XtraReport
Weight = width, Weight = width,
Font = new DXFont("Segoe UI Emoji", 8F), Font = new DXFont("Segoe UI Emoji", 8F),
Padding = new PaddingInfo(4, 4, 2, 2), Padding = new PaddingInfo(4, 4, 2, 2),
TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType), TextAlignment = DynamicReportImageHelper.ToTextAlignment(field.Alignment, field.SourceDbType),
Multiline = false, Multiline = false,
CanGrow = false CanGrow = false
}; };
@ -521,7 +404,7 @@ public class DynamicGridReport : XtraReport
Weight = width, Weight = width,
Font = new DXFont("Arial", 8F, DXFontStyle.Bold), Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
Padding = new PaddingInfo(4, 4, 2, 2), Padding = new PaddingInfo(4, 4, 2, 2),
TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType) TextAlignment = DynamicReportImageHelper.ToTextAlignment(field.Alignment, field.SourceDbType)
}; };
if (summaryType == null) if (summaryType == null)
@ -580,7 +463,7 @@ public class DynamicGridReport : XtraReport
return 75; return 75;
} }
if (IsNumeric(field.SourceDbType)) if (DynamicReportImageHelper.IsNumeric(field.SourceDbType))
{ {
return 70; return 70;
} }
@ -622,16 +505,6 @@ public class DynamicGridReport : XtraReport
return DynamicReportImageHelper.GetGridImageColumnWidth(column.ImageCount); return DynamicReportImageHelper.GetGridImageColumnWidth(column.ImageCount);
} }
private static string GetReportFieldName(SelectField selectField)
{
if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias))
{
return selectField.JoinOptions.FieldNameAlias;
}
return selectField?.FieldName;
}
private static ReportColumn CreateReportColumn( private static ReportColumn CreateReportColumn(
ListFormField field, ListFormField field,
SelectField selectField, SelectField selectField,
@ -639,8 +512,8 @@ public class DynamicGridReport : XtraReport
bool isImage, bool isImage,
bool isHtml) bool isHtml)
{ {
var bindingName = GetReportFieldName(selectField); var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField);
var lookup = GetLookup(field); var lookup = DynamicReportImageHelper.GetLookup(field);
if (lookup == null || selectField?.JoinOptions != null) if (lookup == null || selectField?.JoinOptions != null)
{ {
@ -654,7 +527,7 @@ public class DynamicGridReport : XtraReport
if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData)
{ {
var staticLookupValues = GetStaticLookupValues(lookup, defaultValueHelper); var staticLookupValues = DynamicReportImageHelper.GetStaticLookupValues(lookup, defaultValueHelper);
return new ReportColumn( return new ReportColumn(
field, field,
staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName, staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName,
@ -667,119 +540,6 @@ public class DynamicGridReport : XtraReport
return new ReportColumn(field, bindingName, null, null, isImage, isHtml); return new ReportColumn(field, bindingName, null, null, isImage, isHtml);
} }
private static bool HasHtmlEditor(ListForm listForm, string fieldName)
{
if (string.IsNullOrWhiteSpace(listForm?.EditingFormJson) || string.IsNullOrWhiteSpace(fieldName))
{
return false;
}
try
{
var groups = JsonSerializer.Deserialize<List<EditingFormDto>>(listForm.EditingFormJson) ?? [];
return groups
.SelectMany(x => x.Items ?? [])
.Any(x => string.Equals(x.DataField, fieldName, StringComparison.OrdinalIgnoreCase) &&
x.EditorType2 == PlatformConsts.EditorTypes.dxHtmlEditor);
}
catch (JsonException)
{
return false;
}
}
private static LookupDto GetLookup(ListFormField field)
{
if (string.IsNullOrWhiteSpace(field.LookupJson))
{
return null;
}
try
{
return JsonSerializer.Deserialize<LookupDto>(field.LookupJson);
}
catch (JsonException)
{
return null;
}
}
private static Dictionary<string, string> GetStaticLookupValues(
LookupDto lookup,
DefaultValueHelper defaultValueHelper)
{
if (string.IsNullOrWhiteSpace(lookup.LookupQuery))
{
return null;
}
try
{
var lookupJson = defaultValueHelper.GetDefaultValue(lookup.LookupQuery);
using var document = JsonDocument.Parse(lookupJson);
if (document.RootElement.ValueKind != JsonValueKind.Array)
{
return null;
}
var valueExpr = lookup.ValueExpr ?? "Key";
var displayExpr = lookup.DisplayExpr ?? "Name";
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in document.RootElement.EnumerateArray())
{
if (!TryGetProperty(item, valueExpr, out var valueElement) ||
!TryGetProperty(item, displayExpr, out var displayElement))
{
continue;
}
var value = GetJsonScalar(valueElement);
if (value == null)
{
continue;
}
values[value] = GetJsonScalar(displayElement) ?? value;
}
return values.Count == 0 ? null : values;
}
catch (JsonException)
{
return null;
}
}
private static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement value)
{
foreach (var property in element.EnumerateObject())
{
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
{
value = property.Value;
return true;
}
}
value = default;
return false;
}
private static string GetJsonScalar(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.GetRawText(),
JsonValueKind.True => bool.TrueString,
JsonValueKind.False => bool.FalseString,
JsonValueKind.Null => null,
_ => element.ToString()
};
}
private sealed record ReportColumn( private sealed record ReportColumn(
ListFormField Field, ListFormField Field,
string BindingName, string BindingName,
@ -821,30 +581,6 @@ public class DynamicGridReport : XtraReport
return null; return null;
} }
private static TextAlignment ToTextAlignment(string alignment, DbType dbType) private static string GetParameter(IDictionary<string, string> parameters, string name) =>
{ DynamicReportImageHelper.GetParameter(parameters, name);
return alignment?.ToLowerInvariant() switch
{
"center" => TextAlignment.MiddleCenter,
"right" => TextAlignment.MiddleRight,
"left" => TextAlignment.MiddleLeft,
_ => IsNumeric(dbType) ? TextAlignment.MiddleRight : TextAlignment.MiddleLeft
};
}
private static bool IsNumeric(DbType dbType)
{
return dbType is DbType.Currency or DbType.Decimal or DbType.Double or DbType.Int16 or DbType.Int32
or DbType.Int64 or DbType.Single or DbType.UInt16 or DbType.UInt32 or DbType.UInt64;
}
private static string GetParameter(IDictionary<string, string> parameters, string name)
{
return parameters.TryGetValue(name, out var value) ? value : null;
}
private static string GetImageBaseUrl(IConfiguration configuration)
{
return configuration["App:CdnUrl"] ?? configuration["App:SelfUrl"];
}
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -11,9 +12,14 @@ using DevExpress.Data.Filtering;
using DevExpress.XtraPrinting; using DevExpress.XtraPrinting;
using DevExpress.XtraPrinting.Drawing; using DevExpress.XtraPrinting.Drawing;
using DevExpress.XtraReports.UI; using DevExpress.XtraReports.UI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Sozsoft.Platform; using Sozsoft.Platform;
using Sozsoft.Platform.Entities; using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.ListForms; using Sozsoft.Platform.ListForms;
using Sozsoft.Platform.Localization;
using Sozsoft.Platform.Queries;
namespace Sozsoft.Reports.PredefinedReports; namespace Sozsoft.Reports.PredefinedReports;
@ -26,6 +32,9 @@ internal static class DynamicReportImageHelper
private const int MaxFormImages = 8; private const int MaxFormImages = 8;
private const string ImageUrlFunctionName = "SozReportImageUrl"; private const string ImageUrlFunctionName = "SozReportImageUrl";
private const string ImageSourceFunctionName = "SozReportImageSource"; private const string ImageSourceFunctionName = "SozReportImageSource";
private static readonly Regex ReportPagingRegex = new(
@"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly HttpClient HttpClient = new(new HttpClientHandler private static readonly HttpClient HttpClient = new(new HttpClientHandler
{ {
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
@ -40,6 +49,289 @@ internal static class DynamicReportImageHelper
RegisterImageFunction(new ReportImageSourceFunction()); RegisterImageFunction(new ReportImageSourceFunction());
} }
public static Dictionary<string, SelectField> CreateSelectFieldMap(IEnumerable<SelectField> selectFields)
{
var map = new Dictionary<string, SelectField>(StringComparer.OrdinalIgnoreCase);
foreach (var selectField in selectFields ?? [])
{
if (!string.IsNullOrWhiteSpace(selectField.FieldName))
{
map.TryAdd(selectField.FieldName, selectField);
}
}
return map;
}
public static Dictionary<string, string> CreateEditorTypeMap(ListForm listForm)
{
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(listForm?.EditingFormJson))
{
return map;
}
try
{
var groups = JsonSerializer.Deserialize<List<EditingFormDto>>(listForm.EditingFormJson) ?? [];
foreach (var item in groups.SelectMany(x => x.Items ?? []))
{
if (!string.IsNullOrWhiteSpace(item.DataField))
{
map[item.DataField] = item.EditorType2;
}
}
}
catch (JsonException)
{
}
return map;
}
public static string GetReportFieldName(SelectField selectField)
{
if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias))
{
return selectField.JoinOptions.FieldNameAlias;
}
return selectField?.FieldName;
}
public static string ToDevExpressConnectionString(string connectionString, DataSourceTypeEnum dataSourceType)
{
if (string.IsNullOrWhiteSpace(connectionString) ||
connectionString.Contains("XpoProvider=", StringComparison.OrdinalIgnoreCase))
{
return connectionString;
}
var provider = dataSourceType switch
{
DataSourceTypeEnum.Mssql => "MSSqlServer",
DataSourceTypeEnum.Postgresql => "Postgres",
_ => null
};
return string.IsNullOrWhiteSpace(provider)
? connectionString
: $"XpoProvider={provider};{connectionString}";
}
public static string ApplyQueryParameters(string sql, Dictionary<string, object> parameters)
{
if (string.IsNullOrWhiteSpace(sql) || parameters == null || parameters.Count == 0)
{
return sql;
}
foreach (var parameter in parameters.OrderByDescending(x => x.Key.Length))
{
var key = parameter.Key.StartsWith("@") ? parameter.Key : "@" + parameter.Key;
sql = Regex.Replace(
sql,
Regex.Escape(key) + @"\b",
ToSqlLiteral(parameter.Value),
RegexOptions.CultureInvariant);
}
return sql;
}
public static string StripReportPaging(string sql)
{
return string.IsNullOrWhiteSpace(sql)
? sql
: ReportPagingRegex.Replace(sql, string.Empty);
}
public static string ToSqlLiteral(object value)
{
if (value == null || value == DBNull.Value)
{
return "NULL";
}
return value switch
{
string text => $"'{text.Replace("'", "''")}'",
Guid guid => $"'{guid}'",
DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'",
DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'",
bool boolean => boolean ? "1" : "0",
byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal
=> Convert.ToString(value, CultureInfo.InvariantCulture),
_ => $"'{value.ToString()?.Replace("'", "''")}'"
};
}
public static bool TryGetRowValue(IDictionary<string, object> row, string name, out object value)
{
if (row != null && !string.IsNullOrWhiteSpace(name))
{
if (row.TryGetValue(name, out value))
{
return true;
}
foreach (var item in row)
{
if (string.Equals(item.Key, name, StringComparison.OrdinalIgnoreCase))
{
value = item.Value;
return true;
}
}
}
value = null;
return false;
}
public static LookupDto GetLookup(ListFormField field)
{
if (string.IsNullOrWhiteSpace(field.LookupJson))
{
return null;
}
try
{
return JsonSerializer.Deserialize<LookupDto>(field.LookupJson);
}
catch (JsonException)
{
return null;
}
}
public static Dictionary<string, string> GetStaticLookupValues(
LookupDto lookup,
DefaultValueHelper defaultValueHelper)
{
if (string.IsNullOrWhiteSpace(lookup.LookupQuery))
{
return null;
}
try
{
var lookupJson = defaultValueHelper.GetDefaultValue(lookup.LookupQuery);
using var document = JsonDocument.Parse(lookupJson);
if (document.RootElement.ValueKind != JsonValueKind.Array)
{
return null;
}
var valueExpr = lookup.ValueExpr ?? "Key";
var displayExpr = lookup.DisplayExpr ?? "Name";
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in document.RootElement.EnumerateArray())
{
if (!TryGetLookupProperty(item, valueExpr, out var valueElement) ||
!TryGetLookupProperty(item, displayExpr, out var displayElement))
{
continue;
}
var value = GetJsonScalar(valueElement);
if (value == null)
{
continue;
}
values[value] = GetJsonScalar(displayElement) ?? value;
}
return values.Count == 0 ? null : values;
}
catch (JsonException)
{
return null;
}
}
public static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer<PlatformResource> localizer)
{
var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title;
return Localize(localizer, title, listForm.ListFormCode);
}
public static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer<PlatformResource> localizer)
{
var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
return Localize(localizer, caption, field.FieldName);
}
public static string Localize(IStringLocalizer<PlatformResource> localizer, string key, string fallback)
{
if (string.IsNullOrWhiteSpace(key))
{
return fallback;
}
var localized = localizer[key];
return localized.ResourceNotFound || string.IsNullOrWhiteSpace(localized.Value)
? key
: localized.Value;
}
public static TextAlignment ToTextAlignment(string alignment, DbType dbType)
{
return alignment?.ToLowerInvariant() switch
{
"center" => TextAlignment.MiddleCenter,
"right" => TextAlignment.MiddleRight,
"left" => TextAlignment.MiddleLeft,
_ => IsNumeric(dbType) ? TextAlignment.MiddleRight : TextAlignment.MiddleLeft
};
}
public static bool IsNumeric(DbType dbType)
{
return dbType is DbType.Currency or DbType.Decimal or DbType.Double or DbType.Int16 or DbType.Int32
or DbType.Int64 or DbType.Single or DbType.UInt16 or DbType.UInt32 or DbType.UInt64;
}
public static string GetParameter(IDictionary<string, string> parameters, string name)
{
return parameters.TryGetValue(name, out var value) ? value : null;
}
public static string GetImageBaseUrl(IConfiguration configuration)
{
return configuration["App:CdnUrl"] ?? configuration["App:SelfUrl"];
}
private static bool TryGetLookupProperty(JsonElement element, string propertyName, out JsonElement value)
{
foreach (var property in element.EnumerateObject())
{
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
{
value = property.Value;
return true;
}
}
value = default;
return false;
}
private static string GetJsonScalar(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.GetRawText(),
JsonValueKind.True => bool.TrueString,
JsonValueKind.False => bool.FalseString,
JsonValueKind.Null => null,
_ => element.ToString()
};
}
public static bool IsImageEditor(string editorType) public static bool IsImageEditor(string editorType)
{ {
return editorType is PlatformConsts.EditorTypes.dxImageUpload or PlatformConsts.EditorTypes.dxImageViewer; return editorType is PlatformConsts.EditorTypes.dxImageUpload or PlatformConsts.EditorTypes.dxImageViewer;
@ -150,11 +442,11 @@ internal static class DynamicReportImageHelper
} }
} }
private static bool HasValue(object value) public static bool HasValue(object value)
{ {
return value != null && return value != null &&
value != DBNull.Value && value != DBNull.Value &&
!string.IsNullOrWhiteSpace(Convert.ToString(value)); !string.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture));
} }
private static bool HasImageValue(object value) private static bool HasImageValue(object value)

View file

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Sozsoft.Platform; using Sozsoft.Platform;
@ -14,6 +12,12 @@ internal static class DynamicReportSqlBuilder
{ {
private const string SourceAlias = "ReportSource"; private const string SourceAlias = "ReportSource";
private const string LookupAlias = "LookupSource"; private const string LookupAlias = "LookupSource";
private static readonly Regex OrderByRegex = new(
@"(?<![A-Za-z0-9_])ORDER\s+BY\b[\s\S]*$",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex LookupParameterRegex = new(
@"@\s*param\d+",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
internal interface IReportLookupColumn internal interface IReportLookupColumn
{ {
@ -81,7 +85,7 @@ internal static class DynamicReportSqlBuilder
{ {
var sourceField = GetSourceField(column.Field.FieldName); var sourceField = GetSourceField(column.Field.FieldName);
var cases = column.StaticLookupValues var cases = column.StaticLookupValues
.Select(item => $"WHEN {ToSqlLiteral(item.Key)} THEN {ToSqlLiteral(item.Value)}"); .Select(item => $"WHEN {DynamicReportImageHelper.ToSqlLiteral(item.Key)} THEN {DynamicReportImageHelper.ToSqlLiteral(item.Value)}");
return $"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {QuoteSqlIdentifier(column.BindingName)}"; return $"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {QuoteSqlIdentifier(column.BindingName)}";
} }
@ -123,17 +127,9 @@ internal static class DynamicReportSqlBuilder
} }
var query = lookupQuery.Trim().TrimEnd(';'); var query = lookupQuery.Trim().TrimEnd(';');
query = Regex.Replace( query = OrderByRegex.Replace(query, string.Empty);
query,
@"(?<![A-Za-z0-9_])ORDER\s+BY\b[\s\S]*$",
string.Empty,
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
return Regex.Replace( return LookupParameterRegex.Replace(query, "NULL");
query,
@"@\s*param\d+",
"NULL",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
} }
private static string NormalizeSql(string sql) private static string NormalizeSql(string sql)
@ -146,23 +142,4 @@ internal static class DynamicReportSqlBuilder
return $"\"{identifier.Replace("\"", "\"\"")}\""; return $"\"{identifier.Replace("\"", "\"\"")}\"";
} }
private static string ToSqlLiteral(object value)
{
if (value == null || value == DBNull.Value)
{
return "NULL";
}
return value switch
{
string text => $"'{text.Replace("'", "''")}'",
Guid guid => $"'{guid}'",
DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'",
DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'",
bool boolean => boolean ? "1" : "0",
byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal
=> Convert.ToString(value, CultureInfo.InvariantCulture),
_ => $"'{value.ToString()?.Replace("'", "''")}'"
};
}
} }