Refactor MgGridDataColumn URL templating & update csproj refs
Refactored MgGridDataColumn to efficiently parse and cache URL templates and property accessors, improving cell rendering performance. Replaced Regex.Replace with a compiled, cached approach using [GeneratedRegex]. Updated all project files to use $(Configuration) in DLL HintPaths for correct build output. Added Microsoft.AspNetCore.App framework reference and removed unused references. No breaking API changes.
This commit is contained in:
parent
518cfa6865
commit
cc2ab55402
|
|
@ -1,6 +1,8 @@
|
|||
using DevExpress.Blazor;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AyCode.Blazor.Components.Components.Grids;
|
||||
|
|
@ -8,10 +10,13 @@ namespace AyCode.Blazor.Components.Components.Grids;
|
|||
/// <summary>
|
||||
/// Extended DxGridDataColumn with additional parameters for InfoPanel support.
|
||||
/// </summary>
|
||||
public class MgGridDataColumn : DxGridDataColumn
|
||||
public partial class MgGridDataColumn : DxGridDataColumn
|
||||
{
|
||||
private static readonly ConcurrentDictionary<(Type Type, string Property), Func<object, object?>?> SAccessorCache = new();
|
||||
|
||||
private string? _urlLink;
|
||||
private bool _isInitialized;
|
||||
private TemplatePart[]? _templateParts;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this column should be visible in the InfoPanel. Default is true.
|
||||
|
|
@ -42,6 +47,7 @@ public class MgGridDataColumn : DxGridDataColumn
|
|||
set
|
||||
{
|
||||
if (_urlLink == value) return;
|
||||
|
||||
_urlLink = value;
|
||||
if (_isInitialized) UpdateCellDisplayTemplate();
|
||||
}
|
||||
|
|
@ -56,11 +62,14 @@ public class MgGridDataColumn : DxGridDataColumn
|
|||
|
||||
private void UpdateCellDisplayTemplate()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_urlLink))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_urlLink)) return;
|
||||
|
||||
_templateParts = ParseTemplate(_urlLink);
|
||||
var parts = _templateParts;
|
||||
|
||||
CellDisplayTemplate = context => builder =>
|
||||
{
|
||||
var url = BuildUrlFromTemplate(_urlLink, context.DataItem);
|
||||
var url = BuildUrl(parts, context.DataItem);
|
||||
builder.OpenElement(0, "a");
|
||||
builder.AddAttribute(1, "href", url);
|
||||
builder.AddAttribute(2, "target", "_blank");
|
||||
|
|
@ -69,28 +78,83 @@ public class MgGridDataColumn : DxGridDataColumn
|
|||
builder.CloseElement();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a parsed segment of a URL template: either a literal string or a property placeholder.
|
||||
/// </summary>
|
||||
internal readonly record struct TemplatePart(string Value, bool IsProperty);
|
||||
|
||||
[GeneratedRegex(@"\{([^}]+)\}")]
|
||||
private static partial Regex TemplateRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Parses a URL template into literal and property placeholder segments.
|
||||
/// </summary>
|
||||
internal static TemplatePart[] ParseTemplate(string template)
|
||||
{
|
||||
var parts = new List<TemplatePart>();
|
||||
var lastIndex = 0;
|
||||
|
||||
foreach (Match match in TemplateRegex().Matches(template))
|
||||
{
|
||||
if (match.Index > lastIndex) parts.Add(new TemplatePart(template[lastIndex..match.Index], IsProperty: false));
|
||||
|
||||
parts.Add(new TemplatePart(match.Groups[1].Value, IsProperty: true));
|
||||
lastIndex = match.Index + match.Length;
|
||||
}
|
||||
|
||||
if (lastIndex < template.Length) parts.Add(new TemplatePart(template[lastIndex..], IsProperty: false));
|
||||
|
||||
return [.. parts];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a URL from pre-parsed template parts using cached compiled property accessors.
|
||||
/// </summary>
|
||||
internal static string BuildUrl(TemplatePart[] parts, object? dataItem)
|
||||
{
|
||||
if (dataItem is null || parts.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
var type = dataItem.GetType();
|
||||
var sb = new StringBuilder(parts.Length * 16);
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (!part.IsProperty)
|
||||
{
|
||||
sb.Append(part.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
var accessor = SAccessorCache.GetOrAdd((type, part.Value), static key => CompileAccessor(key.Type, key.Property));
|
||||
|
||||
if (accessor is not null) sb.Append(accessor(dataItem)?.ToString() ?? string.Empty);
|
||||
else sb.Append('{').Append(part.Value).Append('}');
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces {property} placeholders in the template with values from the data item.
|
||||
/// Exposed for unit testing.
|
||||
/// Convenience overload that parses the template on each call — prefer pre-parsed <see cref="BuildUrl"/> for hot paths.
|
||||
/// </summary>
|
||||
internal static string BuildUrlFromTemplate(string template, object? dataItem)
|
||||
{
|
||||
if (dataItem == null) return template;
|
||||
return dataItem is null ? template : BuildUrl(ParseTemplate(template), dataItem);
|
||||
}
|
||||
|
||||
return Regex.Replace(template, "{([^}]+)}", match =>
|
||||
private static Func<object, object?>? CompileAccessor(Type type, string propertyName)
|
||||
{
|
||||
var propName = match.Groups[1].Value;
|
||||
var prop = type.GetProperty(propertyName);
|
||||
if (prop is null) return null;
|
||||
|
||||
//TODO: delegate-et kéne használni és cache-elni egy dictionary-ba! - J.
|
||||
var prop = dataItem.GetType().GetProperty(propName);
|
||||
if (prop != null)
|
||||
{
|
||||
var value = prop.GetValue(dataItem);
|
||||
return value?.ToString() ?? string.Empty;
|
||||
}
|
||||
return match.Value;
|
||||
});
|
||||
var param = Expression.Parameter(typeof(object), "obj");
|
||||
var body = Expression.Convert(
|
||||
Expression.Property(Expression.Convert(param, type), prop),
|
||||
typeof(object));
|
||||
|
||||
return Expression.Lambda<Func<object, object?>>(body, param).Compile();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue