diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..604db435 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,73 @@ + + + + + + latest + + + disable + + enable + CA2007 + + + + + latest + true + + + true + true + + + false + + + false + + + All + + + + + true + + + true + + + true + embedded + + + + + false + + + false + false + false + false + + + + + true + true + + + + diff --git a/Radzen.Blazor.Tests/ExpressionSerializerTests.cs b/Radzen.Blazor.Tests/ExpressionSerializerTests.cs index 48ae8c39..7b8792b1 100644 --- a/Radzen.Blazor.Tests/ExpressionSerializerTests.cs +++ b/Radzen.Blazor.Tests/ExpressionSerializerTests.cs @@ -22,7 +22,7 @@ namespace Radzen.Blazor.Tests public Guid Id { get; set; } public TimeOnly StartTime { get; set; } public DateOnly BirthDate { get; set; } - public List Scores { get; set; } + public IEnumerable Scores { get; set; } public List Tags { get; set; } public List Children { get; set; } public Address Address { get; set; } diff --git a/Radzen.Blazor.Tests/PivotDataGridTests.cs b/Radzen.Blazor.Tests/PivotDataGridTests.cs index bf1d888e..ed22e325 100644 --- a/Radzen.Blazor.Tests/PivotDataGridTests.cs +++ b/Radzen.Blazor.Tests/PivotDataGridTests.cs @@ -605,7 +605,7 @@ namespace Radzen.Blazor.Tests Assert.True(grid.AllowFieldsPicking); } - private static IRenderedComponent> RenderPivotDataGrid(TestContext ctx, Action>>? configure = null) + private static IRenderedComponent> RenderPivotDataGrid(TestContext ctx, Action>> configure = null) { return ctx.RenderComponent>(parameters => { diff --git a/Radzen.Blazor.Tests/Radzen.Blazor.Tests.csproj b/Radzen.Blazor.Tests/Radzen.Blazor.Tests.csproj index 1f7d97ed..f5078fe5 100644 --- a/Radzen.Blazor.Tests/Radzen.Blazor.Tests.csproj +++ b/Radzen.Blazor.Tests/Radzen.Blazor.Tests.csproj @@ -2,7 +2,7 @@ net10.0 - + disable false diff --git a/.editorconfig b/Radzen.Blazor/.editorconfig similarity index 85% rename from .editorconfig rename to Radzen.Blazor/.editorconfig index 58f9e59f..9911a98a 100644 --- a/.editorconfig +++ b/Radzen.Blazor/.editorconfig @@ -6,6 +6,48 @@ root = true #### Core EditorConfig Options #### +dotnet_diagnostic.CA1002.severity = none +dotnet_diagnostic.CA1003.severity = none +dotnet_diagnostic.CA1024.severity = none +dotnet_diagnostic.CA1030.severity = none +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1033.severity = none +dotnet_diagnostic.CA1044.severity = none +dotnet_diagnostic.CA1050.severity = none +dotnet_diagnostic.CA1051.severity = none +dotnet_diagnostic.CA1052.severity = none +dotnet_diagnostic.CA1054.severity = none +dotnet_diagnostic.CA1055.severity = none +dotnet_diagnostic.CA1056.severity = none +dotnet_diagnostic.CA1063.severity = none +dotnet_diagnostic.CA1068.severity = none +dotnet_diagnostic.CA1308.severity = none +dotnet_diagnostic.CA1708.severity = none +dotnet_diagnostic.CA1711.severity = none +dotnet_diagnostic.CA1716.severity = none +dotnet_diagnostic.CA1720.severity = none +dotnet_diagnostic.CA1721.severity = none +dotnet_diagnostic.CA1724.severity = none +dotnet_diagnostic.CA1725.severity = none +dotnet_diagnostic.CA1802.severity = none +dotnet_diagnostic.CA1814.severity = none +dotnet_diagnostic.CA1815.severity = none +dotnet_diagnostic.CA1816.severity = none +dotnet_diagnostic.CA1819.severity = none +dotnet_diagnostic.CA1822.severity = none +dotnet_diagnostic.CA1827.severity = none +dotnet_diagnostic.CA1834.severity = none +dotnet_diagnostic.CA1845.severity = none +dotnet_diagnostic.CA1849.severity = none +dotnet_diagnostic.CA1851.severity = none +dotnet_diagnostic.CA1859.severity = none +dotnet_diagnostic.CA1863.severity = none +dotnet_diagnostic.CA1869.severity = none +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA2012.severity = none +dotnet_diagnostic.CA2211.severity = none +dotnet_diagnostic.CA2227.severity = none + # Indentation and spacing indent_size = 4 indent_style = space diff --git a/Radzen.Blazor/AIChatService.cs b/Radzen.Blazor/AIChatService.cs index 47dc4b42..ecbbed8a 100644 --- a/Radzen.Blazor/AIChatService.cs +++ b/Radzen.Blazor/AIChatService.cs @@ -21,13 +21,16 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions sessions = new(); private readonly object sessionsLock = new(); + // Add this static field to cache the JsonSerializerOptions instance + private static readonly JsonSerializerOptions CachedJsonSerializerOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; + /// /// Gets the configuration options for the chat streaming service. /// public AIChatServiceOptions Options => options.Value; /// - public async IAsyncEnumerable GetCompletionsAsync(string userInput, string sessionId = null, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null, string endpoint = null, string proxy = null, string apiKey = null, string apiKeyHeader = null) + public async IAsyncEnumerable GetCompletionsAsync(string userInput, string? sessionId = null, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default, string? model = null, string? systemPrompt = null, double? temperature = null, int? maxTokens = null, string? endpoint = null, string? proxy = null, string? apiKey = null, string? apiKeyHeader = null) { if (string.IsNullOrWhiteSpace(userInput)) { @@ -57,13 +60,18 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions(); - var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { - throw new Exception($"Chat stream failed: {await response.Content.ReadAsStringAsync(cancellationToken)}"); + throw new HttpRequestException($"Chat stream failed: {await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)}"); } - using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); + using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream); var assistantResponse = new StringBuilder(); - string line; + string? line; while ((line = await reader.ReadLineAsync()) is not null && !cancellationToken.IsCancellationRequested) { - if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:")) + if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:", StringComparison.Ordinal)) { continue; } @@ -118,7 +126,7 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions - public ConversationSession GetOrCreateSession(string sessionId = null) + public ConversationSession GetOrCreateSession(string? sessionId = null) { lock (sessionsLock) { @@ -182,9 +190,14 @@ public class AIChatService(IServiceProvider serviceProvider, IOptionsThe updated service collection. public static IServiceCollection AddAIChatService(this IServiceCollection services, Action configureOptions) { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - if (configureOptions == null) - { - throw new ArgumentNullException(nameof(configureOptions)); - } + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(configureOptions); services.Configure(configureOptions); services.AddScoped(); diff --git a/Radzen.Blazor/AIChatServiceOptions.cs b/Radzen.Blazor/AIChatServiceOptions.cs index af3fbd39..e2ce0cf2 100644 --- a/Radzen.Blazor/AIChatServiceOptions.cs +++ b/Radzen.Blazor/AIChatServiceOptions.cs @@ -13,7 +13,7 @@ public class AIChatServiceOptions /// /// Gets or sets the proxy URL for the AI service, if any. If set, this will override the Endpoint. /// - public string Proxy { get; set; } = null; + public string? Proxy { get; set; } /// /// Gets or sets the API key for authentication with the AI service. @@ -28,7 +28,7 @@ public class AIChatServiceOptions /// /// Gets or sets the model name to use for executing chat completions (e.g., 'gpt-3.5-turbo'). /// - public string Model { get; set; } + public string? Model { get; set; } /// /// Gets or sets the system prompt for the AI assistant. diff --git a/Radzen.Blazor/AppointmentData.cs b/Radzen.Blazor/AppointmentData.cs index 432fb168..63b93d23 100644 --- a/Radzen.Blazor/AppointmentData.cs +++ b/Radzen.Blazor/AppointmentData.cs @@ -22,19 +22,19 @@ namespace Radzen.Blazor /// Gets or sets the text of the appointment. /// /// The text. - public string Text { get; set; } + public string? Text { get; set; } /// /// Gets or sets the data associated with the appointment /// /// The data. - public object Data { get; set; } + public object? Data { get; set; } /// /// Determines whether the specified object is equal to this instance. Used to check if two appointments are equal. /// /// The object to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is AppointmentData data && Start == data.Start && diff --git a/Radzen.Blazor/AxisBase.cs b/Radzen.Blazor/AxisBase.cs index 8d778485..7a6ed8e7 100644 --- a/Radzen.Blazor/AxisBase.cs +++ b/Radzen.Blazor/AxisBase.cs @@ -13,7 +13,7 @@ namespace Radzen.Blazor /// /// The stroke. [Parameter] - public string Stroke { get; set; } + public string? Stroke { get; set; } /// /// Gets or sets the pixel width of axis. /// @@ -26,21 +26,21 @@ namespace Radzen.Blazor /// /// The child content. [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } /// /// Gets or sets the format string used to display the axis values. /// /// The format string. [Parameter] - public string FormatString { get; set; } + public string? FormatString { get; set; } /// /// Gets or sets a formatter function that formats the axis values. /// /// The formatter. [Parameter] - public Func Formatter { get; set; } + public Func? Formatter { get; set; } /// /// Gets or sets the type of the line used to display the axis. @@ -80,20 +80,20 @@ namespace Radzen.Blazor /// /// The minimum. [Parameter] - public object Min { get; set; } + public object? Min { get; set; } /// /// Specifies the maximum value of the axis. /// /// The maximum. [Parameter] - public object Max { get; set; } + public object? Max { get; set; } /// /// Specifies the step of the axis. /// [Parameter] - public object Step { get; set; } + public object? Step { get; set; } /// /// Gets or sets a value indicating whether this is visible. @@ -139,7 +139,7 @@ namespace Radzen.Blazor } else { - return scale.FormatTick(FormatString, value); + return scale.FormatTick(FormatString ?? string.Empty, value); } } diff --git a/Radzen.Blazor/CartesianSeries.cs b/Radzen.Blazor/CartesianSeries.cs index a65e5383..dedaa312 100644 --- a/Radzen.Blazor/CartesianSeries.cs +++ b/Radzen.Blazor/CartesianSeries.cs @@ -19,7 +19,17 @@ namespace Radzen.Blazor /// Cache for the value returned by when that value is only dependent on /// . /// - Func categoryPropertyCache; + Func? categoryPropertyCache; + + /// + /// Returns the parent instance or throws an if not present. + /// + /// The parent . + /// Thrown when the parent chart is not set. + protected RadzenChart RequireChart() + { + return Chart ?? throw new InvalidOperationException($"{GetType().Name} requires a parent RadzenChart."); + } /// /// Creates a getter function that returns a value from the specified category scale for the specified data item. @@ -34,13 +44,13 @@ namespace Radzen.Blazor if (IsNumeric(CategoryProperty)) { - categoryPropertyCache = PropertyAccess.Getter(CategoryProperty); + categoryPropertyCache = PropertyAccess.Getter(CategoryProperty!); return categoryPropertyCache; } if (IsDate(CategoryProperty)) { - var category = PropertyAccess.Getter(CategoryProperty); + var category = PropertyAccess.Getter(CategoryProperty!); categoryPropertyCache = (item) => category(item).Ticks; return categoryPropertyCache; } @@ -49,7 +59,7 @@ namespace Radzen.Blazor { Func category = String.IsNullOrEmpty(CategoryProperty) ? (item) => string.Empty : PropertyAccess.Getter(CategoryProperty); - return (item) => ordinal.Data.IndexOf(category(item)); + return (item) => ordinal.Data?.IndexOf(category(item)) ?? -1; } return (item) => Items.IndexOf(item); @@ -60,6 +70,8 @@ namespace Radzen.Blazor /// protected Func ComposeCategory(ScaleBase scale) { + ArgumentNullException.ThrowIfNull(scale); + return scale.Compose(Category(scale)); } @@ -68,6 +80,8 @@ namespace Radzen.Blazor /// protected Func ComposeValue(ScaleBase scale) { + ArgumentNullException.ThrowIfNull(scale); + return scale.Compose(Value); } @@ -77,7 +91,7 @@ namespace Radzen.Blazor /// Name of the property. /// true if the specified property name is date; otherwise, false. /// Property {propertyName} does not exist - protected bool IsDate(string propertyName) + protected bool IsDate(string? propertyName) { if (String.IsNullOrEmpty(propertyName)) { @@ -106,7 +120,7 @@ namespace Radzen.Blazor /// Name of the property. /// true if the specified property name is numeric; otherwise, false. /// Property {propertyName} does not exist - protected bool IsNumeric(string propertyName) + protected bool IsNumeric(string? propertyName) { if (String.IsNullOrEmpty(propertyName)) { @@ -125,21 +139,21 @@ namespace Radzen.Blazor /// [Parameter] - public string Title { get; set; } + public string Title { get; set; } = null!; /// /// Gets or sets the child content. /// /// The child content. [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } /// /// Gets or sets the tooltip template. /// /// The tooltip template. [Parameter] - public RenderFragment TooltipTemplate { get; set; } + public RenderFragment? TooltipTemplate { get; set; } /// /// Gets the list of overlays. @@ -157,7 +171,7 @@ namespace Radzen.Blazor /// The name of the property of that provides the X axis (a.k.a. category axis) values. /// [Parameter] - public string CategoryProperty { get; set; } + public string? CategoryProperty { get; set; } /// /// Gets or sets a value indicating whether this is visible. @@ -194,7 +208,7 @@ namespace Radzen.Blazor /// The name of the property of that provides the Y axis (a.k.a. value axis) values. /// [Parameter] - public string ValueProperty { get; set; } + public string? ValueProperty { get; set; } /// [Parameter] @@ -223,7 +237,7 @@ namespace Radzen.Blazor /// /// The data. [Parameter] - public IEnumerable Data { get; set; } + public IEnumerable? Data { get; set; } /// /// Stores as an IList of . @@ -272,6 +286,8 @@ namespace Radzen.Blazor /// public virtual ScaleBase TransformCategoryScale(ScaleBase scale) { + ArgumentNullException.ThrowIfNull(scale); + if (Items == null) { return scale; @@ -298,7 +314,7 @@ namespace Radzen.Blazor var data = GetCategories(); - if (scale is OrdinalScale ordinal) + if (scale is OrdinalScale ordinal && ordinal.Data != null) { foreach (var item in ordinal.Data) { @@ -328,6 +344,8 @@ namespace Radzen.Blazor /// public virtual ScaleBase TransformValueScale(ScaleBase scale) { + ArgumentNullException.ThrowIfNull(scale); + if (Items != null) { if (Items.Any()) @@ -398,29 +416,32 @@ namespace Radzen.Blazor { if (Data != null) { - if (Data is IList) + if (Data is IList list) { - Items = Data as IList; + Items = list; } else { Items = Data.ToList(); } - if (IsDate(CategoryProperty) || IsNumeric(CategoryProperty)) + if (!string.IsNullOrEmpty(CategoryProperty) && (IsDate(CategoryProperty) || IsNumeric(CategoryProperty))) { Items = Items.AsQueryable().OrderBy(CategoryProperty).ToList(); } } - await Chart.Refresh(false); + if (Chart != null) + { + await Chart.Refresh(false); + } } } /// protected override void Initialize() { - Chart.AddSeries(this); + Chart?.AddSeries(this); } /// @@ -443,6 +464,9 @@ namespace Radzen.Blazor /// true if the polygon contains the point, false otherwise. protected bool InsidePolygon(Point point, Point[] polygon) { + ArgumentNullException.ThrowIfNull(point); + ArgumentNullException.ThrowIfNull(polygon); + var minX = polygon[0].X; var maxX = polygon[0].X; var minY = polygon[0].Y; @@ -479,18 +503,22 @@ namespace Radzen.Blazor /// public virtual RenderFragment RenderTooltip(object data) { + var chart = RequireChart(); var item = (TItem)data; - + return builder => { - if (Chart.Tooltip.Shared) + if (chart.Tooltip.Shared) { - var category = PropertyAccess.GetValue(item, CategoryProperty); - builder.OpenComponent(0); - builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item)); - builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item)); - builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category)); - builder.CloseComponent(); + var category = !string.IsNullOrEmpty(CategoryProperty) ? PropertyAccess.GetValue(item, CategoryProperty) : null; + if (category != null) + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item)); + builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item)); + builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category)); + builder.CloseComponent(); + } } else { @@ -508,9 +536,11 @@ namespace Radzen.Blazor private RenderFragment RenderSharedTooltipItems(object category) { + var chart = RequireChart(); + return builder => { - var visibleSeries = Chart.Series.Where(s => s.Visible).ToList(); + var visibleSeries = chart.Series.Where(s => s.Visible).ToList(); foreach (var series in visibleSeries) { @@ -524,7 +554,7 @@ namespace Radzen.Blazor { return builder => { - var item = Items.FirstOrDefault(i => object.Equals(PropertyAccess.GetValue(i, CategoryProperty), category)); + var item = Items.FirstOrDefault(i => !string.IsNullOrEmpty(CategoryProperty) && object.Equals(PropertyAccess.GetValue(i, CategoryProperty), category)); if (item != null) { @@ -553,7 +583,7 @@ namespace Radzen.Blazor /// The item. protected virtual string TooltipStyle(TItem item) { - return Chart.Tooltip.Style; + return Chart?.Tooltip?.Style ?? string.Empty; } /// @@ -562,7 +592,13 @@ namespace Radzen.Blazor /// The item. protected virtual string TooltipClass(TItem item) { - return $"rz-series-{Chart.Series.IndexOf(this)}-tooltip"; + var chart = Chart; + if (chart == null) + { + return "rz-series-tooltip"; + } + + return $"rz-series-{chart.Series.IndexOf(this)}-tooltip"; } /// @@ -576,6 +612,8 @@ namespace Radzen.Blazor /// protected virtual RenderFragment RenderLegendItem(bool clickable) { + var chart = RequireChart(); + var index = chart.Series.IndexOf(this); var style = new List(); if (IsVisible == false) @@ -586,7 +624,7 @@ namespace Radzen.Blazor return builder => { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(LegendItem.Index), Chart.Series.IndexOf(this)); + builder.AddAttribute(1, nameof(LegendItem.Index), index); builder.AddAttribute(2, nameof(LegendItem.Color), Color); builder.AddAttribute(3, nameof(LegendItem.MarkerType), MarkerType); builder.AddAttribute(4, nameof(LegendItem.Style), string.Join(";", style)); @@ -617,19 +655,35 @@ namespace Radzen.Blazor /// public double GetMedian() { - return Data.Select(e => Value(e)).OrderBy(e => e).Skip(Data.Count() / 2).FirstOrDefault(); + var values = Items.Select(Value).OrderBy(e => e).ToList(); + if (values.Count == 0) + { + return 0; + } + + return values[values.Count / 2]; } /// public double GetMean() { - return Data.Select(e => Value(e)).DefaultIfEmpty(double.NaN).Average(); + return Items.Any() ? Items.Select(Value).Average() : double.NaN; } /// public double GetMode() { - return Data.Any() ? Data.GroupBy(e => Value(e)).Select(g => new { Value = g.Key, Count = g.Count() }).OrderByDescending(e => e.Count).FirstOrDefault().Value : double.NaN; + if (!Items.Any()) + { + return double.NaN; + } + + return Items + .GroupBy(item => Value(item)) + .Select(g => new { Value = g.Key, Count = g.Count() }) + .OrderByDescending(e => e.Count) + .First() + .Value; } /// @@ -639,33 +693,43 @@ namespace Radzen.Blazor { double a = double.NaN, b = double.NaN; - if (Data.Any()) + var chart = Chart; + if (chart == null) + { + return (a, b); + } + + if (Items.Any()) { Func X; Func Y; - if (Chart.ShouldInvertAxes()) + if (chart.ShouldInvertAxes()) { - X = e => Chart.CategoryScale.Scale(Value(e)); - Y = e => Chart.ValueScale.Scale(Category(Chart.ValueScale)(e)); + var valueScale = chart.ValueScale; + var categoryAccessor = Category(chart.ValueScale); + X = e => chart.CategoryScale.Scale(Value(e)); + Y = e => valueScale.Scale(categoryAccessor(e)); } else { - X = e => Chart.CategoryScale.Scale(Category(Chart.CategoryScale)(e)); - Y = e => Chart.ValueScale.Scale(Value(e)); + var categoryAccessor = Category(chart.CategoryScale); + X = e => chart.CategoryScale.Scale(categoryAccessor(e)); + Y = e => chart.ValueScale.Scale(Value(e)); } - var avgX = Data.Select(e => X(e)).Average(); - var avgY = Data.Select(e => Y(e)).Average(); - var sumXY = Data.Sum(e => (X(e) - avgX) * (Y(e) - avgY)); - if (Chart.ShouldInvertAxes()) + var data = Items.ToList(); + var avgX = data.Select(e => X(e)).Average(); + var avgY = data.Select(e => Y(e)).Average(); + var sumXY = data.Sum(e => (X(e) - avgX) * (Y(e) - avgY)); + if (chart.ShouldInvertAxes()) { - var sumYSq = Data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY)); + var sumYSq = data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY)); b = sumXY / sumYSq; a = avgX - b * avgY; } else { - var sumXSq = Data.Sum(e => (X(e) - avgX) * (X(e) - avgX)); + var sumXSq = data.Sum(e => (X(e) - avgX) * (X(e) - avgX)); b = sumXY / sumXSq; a = avgY - b * avgX; } @@ -678,7 +742,9 @@ namespace Radzen.Blazor { IsVisible = !IsVisible; - if (Chart.LegendClick.HasDelegate) + var chart = Chart; + + if (chart?.LegendClick.HasDelegate == true) { var args = new LegendClickEventArgs { @@ -687,18 +753,28 @@ namespace Radzen.Blazor IsVisible = IsVisible, }; - await Chart.LegendClick.InvokeAsync(args); + await chart.LegendClick.InvokeAsync(args); IsVisible = args.IsVisible; } - await Chart.Refresh(); + if (chart != null) + { + await chart.Refresh(); + } } /// public string GetTitle() { - return String.IsNullOrEmpty(Title) ? $"Series {Chart.Series.IndexOf(this) + 1}" : Title; + var chart = Chart; + if (string.IsNullOrEmpty(Title)) + { + var index = chart?.Series.IndexOf(this) ?? 0; + return $"Series {index + 1}"; + } + + return Title; } /// @@ -716,8 +792,9 @@ namespace Radzen.Blazor /// The item. protected virtual string TooltipTitle(TItem item) { - var category = Category(Chart.CategoryScale); - return Chart.CategoryAxis.Format(Chart.CategoryScale, Chart.CategoryScale.Value(category(item))); + var chart = RequireChart(); + var category = Category(chart.CategoryScale); + return chart.CategoryAxis.Format(chart.CategoryScale, chart.CategoryScale.Value(category(item))); } /// @@ -727,7 +804,8 @@ namespace Radzen.Blazor /// System.String. protected virtual string TooltipValue(TItem item) { - return Chart.ValueAxis.Format(Chart.ValueScale, Chart.ValueScale.Value(Value(item))); + var chart = RequireChart(); + return chart.ValueAxis.Format(chart.ValueScale, chart.ValueScale.Value(Value(item))); } /// @@ -736,8 +814,9 @@ namespace Radzen.Blazor /// The item. internal virtual double TooltipX(TItem item) { - var category = Category(Chart.CategoryScale); - return Chart.CategoryScale.Scale(category(item), true); + var chart = RequireChart(); + var category = Category(chart.CategoryScale); + return chart.CategoryScale.Scale(category(item), true); } /// @@ -746,7 +825,8 @@ namespace Radzen.Blazor /// The item. internal virtual double TooltipY(TItem item) { - return Chart.ValueScale.Scale(Value(item), true); + var chart = RequireChart(); + return chart.ValueScale.Scale(Value(item), true); } /// @@ -760,25 +840,26 @@ namespace Radzen.Blazor return new { Item = item, Distance = distance }; }).Aggregate((a, b) => a.Distance < b.Distance ? a : b).Item; - return (retObject, + return (retObject!, new Point() { X = TooltipX(retObject), Y = TooltipY(retObject)}); } - return (null, null); + return (default!, new Point()); } /// public virtual IEnumerable GetDataLabels(double offsetX, double offsetY) { + var chart = RequireChart(); var list = new List(); - foreach (var d in Data) + foreach (var d in Items) { list.Add(new ChartDataLabel { Position = new Point { X = TooltipX(d) + offsetX, Y = TooltipY(d) + offsetY }, TextAnchor = "middle", - Text = Chart.ValueAxis.Format(Chart.ValueScale, Value(d)) + Text = chart.ValueAxis.Format(chart.ValueScale, Value(d)) }); } @@ -793,12 +874,12 @@ namespace Radzen.Blazor /// The default value. /// The color range value. /// The value of the item. - protected string PickColor(int index, IEnumerable colors, string defaultValue = null, IList colorRange = null, double value = 0.0) + protected string? PickColor(int index, IEnumerable? colors, string? defaultValue = null, IList? colorRange = null, double value = 0.0) { if (colorRange != null) { var result = colorRange.Where(r => r.Min <= value && r.Max >= value).FirstOrDefault(); - return result != null ? result.Color : defaultValue; + return result?.Color ?? defaultValue; } else { @@ -819,18 +900,20 @@ namespace Radzen.Blazor /// public async Task InvokeClick(EventCallback handler, object data) { - var category = Category(Chart.CategoryScale); + var chart = RequireChart(); + var category = Category(chart.CategoryScale); + var dataItem = (TItem)data; await handler.InvokeAsync(new SeriesClickEventArgs { Data = data, Title = GetTitle(), - Category = PropertyAccess.GetValue(data, CategoryProperty), - Value = PropertyAccess.GetValue(data, ValueProperty), + Category = !string.IsNullOrEmpty(CategoryProperty) ? PropertyAccess.GetValue(data, CategoryProperty) : null, + Value = !string.IsNullOrEmpty(ValueProperty) ? PropertyAccess.GetValue(data, ValueProperty) : null, Point = new SeriesPoint { - Category = category((TItem)data), - Value = Value((TItem)data) + Category = category(dataItem), + Value = Value(dataItem) } }); } diff --git a/Radzen.Blazor/ChatMessage.cs b/Radzen.Blazor/ChatMessage.cs index 6dc64a6f..08071f08 100644 --- a/Radzen.Blazor/ChatMessage.cs +++ b/Radzen.Blazor/ChatMessage.cs @@ -39,5 +39,5 @@ public class ChatMessage /// /// Gets or sets the role associated with the message (e.g., "user", "assistant"). /// - public string Role { get; set; } + public string? Role { get; set; } } \ No newline at end of file diff --git a/Radzen.Blazor/CompositeFilterDescriptor.cs b/Radzen.Blazor/CompositeFilterDescriptor.cs index 11f9ec7a..7ea8582d 100644 --- a/Radzen.Blazor/CompositeFilterDescriptor.cs +++ b/Radzen.Blazor/CompositeFilterDescriptor.cs @@ -12,25 +12,25 @@ public class CompositeFilterDescriptor /// Gets or sets the name of the filtered property. /// /// The property. - public string Property { get; set; } + public string? Property { get; set; } /// /// Gets or sets the property type. /// /// The property type. - public Type Type { get; set; } + public Type? Type { get; set; } /// /// Gets or sets the name of the filtered property. /// /// The property. - public string FilterProperty { get; set; } + public string? FilterProperty { get; set; } /// /// Gets or sets the value to filter by. /// /// The filter value. - public object FilterValue { get; set; } + public object? FilterValue { get; set; } /// /// Gets or sets the operator which will compare the property value with . @@ -48,6 +48,6 @@ public class CompositeFilterDescriptor /// Gets or sets the filters. /// /// The filters. - public IEnumerable Filters { get; set; } + public IEnumerable? Filters { get; set; } } diff --git a/Radzen.Blazor/ContextMenuService.cs b/Radzen.Blazor/ContextMenuService.cs index 2359b8fe..0cd3869b 100644 --- a/Radzen.Blazor/ContextMenuService.cs +++ b/Radzen.Blazor/ContextMenuService.cs @@ -1,225 +1,227 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Radzen -{ - /// - /// Class ContextMenuService. Contains various methods with options to open and close context menus. - /// Should be added as scoped service in the application services and RadzenContextMenu should be added in application main layout. - /// Implements the - /// - /// - /// - /// - /// @inject ContextMenuService ContextMenuService - /// <RadzenButton Text="Show context menu" ContextMenu=@(args => ShowContextMenuWithContent(args)) /> - /// @code { - /// void ShowContextMenuWithContent(MouseEventArgs args) => ContextMenuService.Open(args, ds => - /// @<RadzenMenu Click="OnMenuItemClick"> - /// <RadzenMenuItem Text="Item1" Value="1"></RadzenMenuItem> - /// <RadzenMenuItem Text="Item2" Value="2"></RadzenMenuItem> - /// <RadzenMenuItem Text="More items" Value="3"> - /// <RadzenMenuItem Text="More sub items" Value="4"> - /// <RadzenMenuItem Text="Item1" Value="5"></RadzenMenuItem> - /// <RadzenMenuItem Text="Item2" Value="6"></RadzenMenuItem> - /// </RadzenMenuItem> - /// </RadzenMenuItem> - /// </RadzenMenu>); - /// - /// void OnMenuItemClick(MenuItemEventArgs args) - /// { - /// Console.WriteLine($"Menu item with Value={args.Value} clicked"); - /// } - /// } - /// - /// - public class ContextMenuService : IDisposable - { - /// - /// Gets or sets the navigation manager. - /// - /// The navigation manager. - NavigationManager navigationManager { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The URI helper. - public ContextMenuService(NavigationManager uriHelper) - { - navigationManager = uriHelper; - - if (navigationManager != null) - { - navigationManager.LocationChanged += UriHelper_OnLocationChanged; - } - } - - /// - /// Handles the OnLocationChanged event of the UriHelper control. - /// - /// The source of the event. - /// The instance containing the event data. - private void UriHelper_OnLocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using System; +using System.Collections.Generic; + +namespace Radzen +{ + /// + /// Class ContextMenuService. Contains various methods with options to open and close context menus. + /// Should be added as scoped service in the application services and RadzenContextMenu should be added in application main layout. + /// Implements the + /// + /// + /// + /// + /// @inject ContextMenuService ContextMenuService + /// <RadzenButton Text="Show context menu" ContextMenu=@(args => ShowContextMenuWithContent(args)) /> + /// @code { + /// void ShowContextMenuWithContent(MouseEventArgs args) => ContextMenuService.Open(args, ds => + /// @<RadzenMenu Click="OnMenuItemClick"> + /// <RadzenMenuItem Text="Item1" Value="1"></RadzenMenuItem> + /// <RadzenMenuItem Text="Item2" Value="2"></RadzenMenuItem> + /// <RadzenMenuItem Text="More items" Value="3"> + /// <RadzenMenuItem Text="More sub items" Value="4"> + /// <RadzenMenuItem Text="Item1" Value="5"></RadzenMenuItem> + /// <RadzenMenuItem Text="Item2" Value="6"></RadzenMenuItem> + /// </RadzenMenuItem> + /// </RadzenMenuItem> + /// </RadzenMenu>); + /// + /// void OnMenuItemClick(MenuItemEventArgs args) + /// { + /// Console.WriteLine($"Menu item with Value={args.Value} clicked"); + /// } + /// } + /// + /// + public class ContextMenuService : IDisposable + { + /// + /// Gets or sets the navigation manager. + /// + /// The navigation manager. + NavigationManager? navigationManager { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The URI helper. + public ContextMenuService(NavigationManager? uriHelper) { - this.OnNavigate?.Invoke(); - } - - /// - /// Occurs when [on navigate]. - /// - public event Action OnNavigate; - - /// - /// Raises the Close event. - /// - public event Action OnClose; - - /// - /// Occurs when [on open]. - /// - public event Action OnOpen; - - /// - /// Opens the specified arguments. - /// - /// The instance containing the event data. - /// The items. - /// The click. - public void Open(MouseEventArgs args, IEnumerable items, Action click = null) - { - var options = new ContextMenuOptions(); - - options.Items = items; - options.Click = click; - - OpenTooltip(args, options); - } - - /// - /// Opens the specified arguments. - /// - /// The instance containing the event data. - /// Content of the child. - public void Open(MouseEventArgs args, RenderFragment childContent) - { - var options = new ContextMenuOptions(); - - options.ChildContent = childContent; - - OpenTooltip(args, options); - } - - /// - /// Opens the tooltip. - /// - /// The instance containing the event data. - /// The options. - private void OpenTooltip(MouseEventArgs args, ContextMenuOptions options) - { - OnOpen?.Invoke(args, options); - } - - /// - /// Closes this instance. - /// - public void Close() - { - OnClose?.Invoke(); - } - - /// - /// Disposes this instance. - /// - public void Dispose() - { - navigationManager.LocationChanged -= UriHelper_OnLocationChanged; - } - } - - /// - /// Class ContextMenuOptions. - /// - public class ContextMenuOptions - { - /// - /// Gets or sets the child content. - /// - /// The child content. - public RenderFragment ChildContent { get; set; } - /// - /// Gets or sets the items. - /// - /// The items. - public IEnumerable Items { get; set; } - - /// - /// Gets or sets the click. - /// - /// The click. - public Action Click { get; set; } - } - - /// - /// Class ContextMenu. - /// - public class ContextMenu - { - /// - /// Gets or sets the options. - /// - /// The options. - public ContextMenuOptions Options { get; set; } - /// - /// Gets or sets the mouse event arguments. - /// - /// The mouse event arguments. - public MouseEventArgs MouseEventArgs { get; set; } - } - - /// - /// Class ContextMenuItem. - /// - public class ContextMenuItem - { - /// - /// Gets or sets the text. - /// - /// The text. - public string Text { get; set; } - /// - /// Gets or sets the value. - /// - /// The value. - public object Value { get; set; } - /// - /// Gets or sets the icon. - /// - /// The icon. - public string Icon { get; set; } - /// - /// Gets or sets the icon color. - /// - /// The icon color. - public string IconColor { get; set; } - /// - /// Gets or sets the image. - /// - /// The image. - public string Image { get; set; } - /// - /// Gets or sets the image style. - /// - /// The image style. - public string ImageStyle { get; set; } - /// - /// Gets a value indicating whether this instance is disabled. - /// - /// true if this instance is disabled; otherwise, false. - public bool Disabled { get; set; } - } -} + navigationManager = uriHelper; + + if (navigationManager != null) + { + navigationManager.LocationChanged += UriHelper_OnLocationChanged; + } + } + + /// + /// Handles the OnLocationChanged event of the UriHelper control. + /// + /// The source of the event. + /// The instance containing the event data. + private void UriHelper_OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) + { + this.OnNavigate?.Invoke(); + } + + /// + /// Occurs when [on navigate]. + /// + public event Action? OnNavigate; + + /// + /// Raises the Close event. + /// + public event Action? OnClose; + + /// + /// Occurs when [on open]. + /// + public event Action? OnOpen; + + /// + /// Opens the specified arguments. + /// + /// The instance containing the event data. + /// The items. + /// The click. + public void Open(MouseEventArgs args, IEnumerable items, Action? click = null) + { + var options = new ContextMenuOptions(); + + options.Items = items; + options.Click = click; + + OpenTooltip(args, options); + } + + /// + /// Opens the specified arguments. + /// + /// The instance containing the event data. + /// Content of the child. + public void Open(MouseEventArgs args, RenderFragment childContent) + { + var options = new ContextMenuOptions(); + + options.ChildContent = childContent; + + OpenTooltip(args, options); + } + + /// + /// Opens the tooltip. + /// + /// The instance containing the event data. + /// The options. + private void OpenTooltip(MouseEventArgs args, ContextMenuOptions options) + { + OnOpen?.Invoke(args, options); + } + + /// + /// Closes this instance. + /// + public void Close() + { + OnClose?.Invoke(); + } + + /// + /// Disposes this instance. + /// + public void Dispose() + { + if (navigationManager != null) + { + navigationManager.LocationChanged -= UriHelper_OnLocationChanged; + } + GC.SuppressFinalize(this); + } + } + + /// + /// Class ContextMenuOptions. + /// + public class ContextMenuOptions + { + /// + /// Gets or sets the child content. + /// + /// The child content. + public RenderFragment? ChildContent { get; set; } + /// + /// Gets or sets the items. + /// + /// The items. + public IEnumerable? Items { get; set; } + + /// + /// Gets or sets the click. + /// + /// The click. + public Action? Click { get; set; } + } + + /// + /// Class ContextMenu. + /// + public class ContextMenu + { + /// + /// Gets or sets the options. + /// + /// The options. + public ContextMenuOptions? Options { get; set; } + /// + /// Gets or sets the mouse event arguments. + /// + /// The mouse event arguments. + public MouseEventArgs? MouseEventArgs { get; set; } + } + + /// + /// Class ContextMenuItem. + /// + public class ContextMenuItem + { + /// + /// Gets or sets the text. + /// + /// The text. + public string? Text { get; set; } + /// + /// Gets or sets the value. + /// + /// The value. + public object? Value { get; set; } + /// + /// Gets or sets the icon. + /// + /// The icon. + public string? Icon { get; set; } + /// + /// Gets or sets the icon color. + /// + /// The icon color. + public string? IconColor { get; set; } + /// + /// Gets or sets the image. + /// + /// The image. + public string? Image { get; set; } + /// + /// Gets or sets the image style. + /// + /// The image style. + public string? ImageStyle { get; set; } + /// + /// Gets a value indicating whether this instance is disabled. + /// + /// true if this instance is disabled; otherwise, false. + public bool Disabled { get; set; } + } +} diff --git a/Radzen.Blazor/ConvertType.cs b/Radzen.Blazor/ConvertType.cs index 4dc9c0d7..531e0243 100644 --- a/Radzen.Blazor/ConvertType.cs +++ b/Radzen.Blazor/ConvertType.cs @@ -18,25 +18,39 @@ public static class ConvertType /// The type. /// The culture. /// System.Object - public static object ChangeType(object value, Type type, CultureInfo culture = null) + public static object? ChangeType(object value, Type type, CultureInfo? culture = null) { + ArgumentNullException.ThrowIfNull(type); + + // CA1062: Validate 'value' is non-null before using it + if (value == null) + { + if (Nullable.GetUnderlyingType(type) != null) + { + return null; + } + throw new ArgumentNullException(nameof(value)); + } + if (culture == null) { culture = CultureInfo.CurrentCulture; } - if (value == null && Nullable.GetUnderlyingType(type) != null) - { - return value; - } if ((Nullable.GetUnderlyingType(type) ?? type) == typeof(Guid) && value is string) { return Guid.Parse((string)value); } - if (Nullable.GetUnderlyingType(type)?.IsEnum == true) + var underlyingEnumType = Nullable.GetUnderlyingType(type); + if (underlyingEnumType?.IsEnum == true) { - return Enum.Parse(Nullable.GetUnderlyingType(type), value.ToString()); + var valueString = value.ToString(); + if (valueString == null) + { + throw new ArgumentNullException(nameof(value), "Enum value cannot be null."); + } + return Enum.Parse(underlyingEnumType, valueString); } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) diff --git a/Radzen.Blazor/CookieThemeService.cs b/Radzen.Blazor/CookieThemeService.cs index 187f042f..a6c5e372 100644 --- a/Radzen.Blazor/CookieThemeService.cs +++ b/Radzen.Blazor/CookieThemeService.cs @@ -44,12 +44,12 @@ namespace Radzen /// /// Gets or sets a value indicating whether to use secure cookies. /// - public bool IsSecure { get; set; } = false; + public bool IsSecure { get; set; } /// /// Gets or sets the SameSite attribute for the cookie. /// - public CookieSameSiteMode? SameSite { get; set; } = null; + public CookieSameSiteMode? SameSite { get; set; } } /// @@ -64,13 +64,16 @@ namespace Radzen /// /// Initializes a new instance of the class. /// - public CookieThemeService(IJSRuntime jsRuntime, ThemeService themeService, IOptions options) + public CookieThemeService(IJSRuntime jsRuntime, ThemeService themeService, IOptions? options) { this.jsRuntime = jsRuntime; this.themeService = themeService; - this.options = options.Value; + this.options = options?.Value ?? new CookieThemeServiceOptions(); - themeService.ThemeChanged += OnThemeChanged; + if (themeService != null) + { + themeService.ThemeChanged += OnThemeChanged; + } _ = InitializeAsync(); } diff --git a/Radzen.Blazor/DataBoundFormComponent.cs b/Radzen.Blazor/DataBoundFormComponent.cs index 953c8a0a..eb6db709 100644 --- a/Radzen.Blazor/DataBoundFormComponent.cs +++ b/Radzen.Blazor/DataBoundFormComponent.cs @@ -47,14 +47,14 @@ namespace Radzen /// /// The name. [Parameter] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the placeholder. /// /// The placeholder. [Parameter] - public string Placeholder { get; set; } + public string? Placeholder { get; set; } /// /// Gets or sets a value indicating whether this is disabled. @@ -80,36 +80,36 @@ namespace Radzen /// /// The form /// - IRadzenForm _form; + IRadzenForm? form; /// /// Gets or sets the form. /// /// The form. [CascadingParameter] - public IRadzenForm Form + public IRadzenForm? Form { get { - return _form; + return form; } set { - _form = value; - _form?.AddComponent(this); + form = value; + form?.AddComponent(this); } } /// /// The value /// - private T _value = default; + private T? _value; /// /// Gets or sets the value. /// /// The value. [Parameter] - public T Value + public T? Value { get { @@ -167,18 +167,18 @@ namespace Radzen /// /// The text property. [Parameter] - public string TextProperty { get; set; } + public string? TextProperty { get; set; } /// /// The data /// - IEnumerable _data = null; + IEnumerable? _data; /// /// Gets or sets the data. /// /// The data. [Parameter] - public virtual IEnumerable Data + public virtual IEnumerable? Data { get { @@ -208,7 +208,7 @@ namespace Radzen /// Gets the query. /// /// The query. - protected virtual IQueryable Query + protected virtual IQueryable? Query { get { @@ -220,7 +220,7 @@ namespace Radzen /// Gets or sets the search text /// [Parameter] - public string SearchText + public string? SearchText { get { @@ -245,29 +245,30 @@ namespace Radzen /// /// The search text /// - internal string searchText; + internal string? searchText; /// /// The view /// - protected IQueryable _view = null; + protected IQueryable? _view; /// /// Gets the view. /// /// The view. - protected virtual IEnumerable View + protected virtual IEnumerable? View { get { - if (_view == null && Query != null) + var query = Query; + if (_view == null && query != null) { - if (!string.IsNullOrEmpty(searchText)) + if (!string.IsNullOrEmpty(searchText) && !string.IsNullOrEmpty(TextProperty)) { - _view = Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity); + _view = query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity); } else { - _view = (typeof(IQueryable).IsAssignableFrom(Data.GetType())) ? Query.Cast().ToList().AsQueryable() : Query; + _view = Data is IQueryable ? query.Cast().ToList().AsQueryable() : query; } } @@ -275,14 +276,14 @@ namespace Radzen } } - internal IEnumerable GetView() => View; + internal IEnumerable? GetView() => View; /// /// Gets or sets the edit context. /// /// The edit context. [CascadingParameter] - public EditContext EditContext { get; set; } + public EditContext? EditContext { get; set; } /// /// Gets the field identifier. @@ -296,7 +297,7 @@ namespace Radzen /// /// The value expression. [Parameter] - public Expression> ValueExpression { get; set; } + public Expression>? ValueExpression { get; set; } /// /// Set parameters as an asynchronous operation. /// @@ -338,7 +339,7 @@ namespace Radzen /// /// The sender. /// The instance containing the event data. - private void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e) + private void ValidationStateChanged(object? sender, ValidationStateChangedEventArgs e) { StateHasChanged(); } @@ -356,13 +357,15 @@ namespace Radzen } Form?.RemoveComponent(this); + + GC.SuppressFinalize(this); } /// /// Gets the value. /// /// System.Object. - public virtual object GetValue() + public virtual object? GetValue() { return Value; } @@ -386,13 +389,13 @@ namespace Radzen /// Provides support for RadzenFormField integration. [CascadingParameter] - public IFormFieldContext FormFieldContext { get; set; } + public IFormFieldContext? FormFieldContext { get; set; } /// Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField. - protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder; + protected string? CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder; /// - /// Handles the event. + /// Handles the ContextMenu event. /// /// The instance containing the event data. /// Task. diff --git a/Radzen.Blazor/DataGridCellMouseEventArgs.cs b/Radzen.Blazor/DataGridCellMouseEventArgs.cs index ac82c960..52ccca01 100644 --- a/Radzen.Blazor/DataGridCellMouseEventArgs.cs +++ b/Radzen.Blazor/DataGridCellMouseEventArgs.cs @@ -6,16 +6,16 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridCellMouseEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs +public class DataGridCellMouseEventArgs : Microsoft.AspNetCore.Components.Web.MouseEventArgs where T : notnull { /// /// Gets the data item which the clicked DataGrid row represents. /// - public T Data { get; internal set; } + public T? Data { get; internal set; } /// /// Gets the RadzenDataGridColumn which this cells represents. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } } diff --git a/Radzen.Blazor/DataGridCellRenderEventArgs.cs b/Radzen.Blazor/DataGridCellRenderEventArgs.cs index 23251264..dcb081df 100644 --- a/Radzen.Blazor/DataGridCellRenderEventArgs.cs +++ b/Radzen.Blazor/DataGridCellRenderEventArgs.cs @@ -6,11 +6,11 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridCellRenderEventArgs : RowRenderEventArgs +public class DataGridCellRenderEventArgs : RowRenderEventArgs where T : notnull { /// /// Gets the RadzenDataGridColumn which this cells represents. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } } diff --git a/Radzen.Blazor/DataGridChildData.cs b/Radzen.Blazor/DataGridChildData.cs index 1ed68cf1..6963432e 100644 --- a/Radzen.Blazor/DataGridChildData.cs +++ b/Radzen.Blazor/DataGridChildData.cs @@ -11,7 +11,7 @@ internal class DataGridChildData /// /// Gets or sets the parent child data. /// - internal DataGridChildData ParentChildData { get; set; } + internal DataGridChildData? ParentChildData { get; set; } /// /// Gets or sets the level. @@ -21,6 +21,6 @@ internal class DataGridChildData /// /// Gets or sets the data. /// - internal IEnumerable Data { get; set; } + internal IEnumerable? Data { get; set; } } diff --git a/Radzen.Blazor/DataGridColumnFilterEventArgs.cs b/Radzen.Blazor/DataGridColumnFilterEventArgs.cs index a4f2fcbb..902f5af8 100644 --- a/Radzen.Blazor/DataGridColumnFilterEventArgs.cs +++ b/Radzen.Blazor/DataGridColumnFilterEventArgs.cs @@ -6,22 +6,22 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridColumnFilterEventArgs +public class DataGridColumnFilterEventArgs where T : notnull { /// /// Gets the filtered RadzenDataGridColumn. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } /// /// Gets the new filter value of the filtered column. /// - public object FilterValue { get; internal set; } + public object? FilterValue { get; internal set; } /// /// Gets the new second filter value of the filtered column. /// - public object SecondFilterValue { get; internal set; } + public object? SecondFilterValue { get; internal set; } /// /// Gets the new filter operator of the filtered column. diff --git a/Radzen.Blazor/DataGridColumnGroupEventArgs.cs b/Radzen.Blazor/DataGridColumnGroupEventArgs.cs index 5779601a..c03d7a40 100644 --- a/Radzen.Blazor/DataGridColumnGroupEventArgs.cs +++ b/Radzen.Blazor/DataGridColumnGroupEventArgs.cs @@ -6,16 +6,16 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridColumnGroupEventArgs +public class DataGridColumnGroupEventArgs where T : notnull { /// /// Gets the grouped RadzenDataGridColumn. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } /// /// Gets the group descriptor. /// - public GroupDescriptor GroupDescriptor { get; internal set; } + public GroupDescriptor? GroupDescriptor { get; internal set; } } diff --git a/Radzen.Blazor/DataGridColumnReorderedEventArgs.cs b/Radzen.Blazor/DataGridColumnReorderedEventArgs.cs index 64bba042..5a5b5cfe 100644 --- a/Radzen.Blazor/DataGridColumnReorderedEventArgs.cs +++ b/Radzen.Blazor/DataGridColumnReorderedEventArgs.cs @@ -6,12 +6,12 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridColumnReorderedEventArgs +public class DataGridColumnReorderedEventArgs where T : notnull { /// /// Gets the reordered RadzenDataGridColumn. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } /// /// Gets the old index of the column. diff --git a/Radzen.Blazor/DataGridColumnReorderingEventArgs.cs b/Radzen.Blazor/DataGridColumnReorderingEventArgs.cs index f162984f..ef7f7c14 100644 --- a/Radzen.Blazor/DataGridColumnReorderingEventArgs.cs +++ b/Radzen.Blazor/DataGridColumnReorderingEventArgs.cs @@ -6,17 +6,17 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridColumnReorderingEventArgs +public class DataGridColumnReorderingEventArgs where T : notnull { /// /// Gets the reordered RadzenDataGridColumn. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } /// /// Gets the reordered to RadzenDataGridColumn. /// - public RadzenDataGridColumn ToColumn { get; internal set; } + public RadzenDataGridColumn? ToColumn { get; internal set; } /// /// Gets or sets a value which will cancel the event. diff --git a/Radzen.Blazor/DataGridColumnResizedEventArgs.cs b/Radzen.Blazor/DataGridColumnResizedEventArgs.cs index c17f87cb..d780dff8 100644 --- a/Radzen.Blazor/DataGridColumnResizedEventArgs.cs +++ b/Radzen.Blazor/DataGridColumnResizedEventArgs.cs @@ -6,12 +6,12 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridColumnResizedEventArgs +public class DataGridColumnResizedEventArgs where T : notnull { /// /// Gets the resized RadzenDataGridColumn. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } /// /// Gets the new width of the resized column. diff --git a/Radzen.Blazor/DataGridColumnSettings.cs b/Radzen.Blazor/DataGridColumnSettings.cs index 8c2eed89..4ad2864a 100644 --- a/Radzen.Blazor/DataGridColumnSettings.cs +++ b/Radzen.Blazor/DataGridColumnSettings.cs @@ -8,12 +8,12 @@ public class DataGridColumnSettings /// /// Property. /// - public string UniqueID { get; set; } + public string UniqueID { get; set; } = string.Empty; /// /// Property. /// - public string Property { get; set; } + public string Property { get; set; } = string.Empty; /// /// Visible. @@ -23,7 +23,7 @@ public class DataGridColumnSettings /// /// Width. /// - public string Width { get; set; } + public string Width { get; set; } = string.Empty; /// /// OrderIndex. @@ -43,7 +43,7 @@ public class DataGridColumnSettings /// /// FilterValue. /// - public object FilterValue { get; set; } + public object? FilterValue { get; set; } /// /// FilterOperator. @@ -53,7 +53,7 @@ public class DataGridColumnSettings /// /// SecondFilterValue. /// - public object SecondFilterValue { get; set; } + public object? SecondFilterValue { get; set; } /// /// SecondFilterOperator. @@ -68,7 +68,7 @@ public class DataGridColumnSettings /// /// CustomFilterExpression. /// - public string CustomFilterExpression { get; set; } + public string CustomFilterExpression { get; set; } = string.Empty; /// /// Gets or sets the mode that determines whether the filter applies to any or all items in a collection. diff --git a/Radzen.Blazor/DataGridColumnSortEventArgs.cs b/Radzen.Blazor/DataGridColumnSortEventArgs.cs index a730bc1d..38da16c4 100644 --- a/Radzen.Blazor/DataGridColumnSortEventArgs.cs +++ b/Radzen.Blazor/DataGridColumnSortEventArgs.cs @@ -6,12 +6,12 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridColumnSortEventArgs +public class DataGridColumnSortEventArgs where T : notnull { /// /// Gets the sorted RadzenDataGridColumn. /// - public RadzenDataGridColumn Column { get; internal set; } + public RadzenDataGridColumn? Column { get; internal set; } /// /// Gets the new sort order of the sorted column. diff --git a/Radzen.Blazor/DataGridLoadChildDataEventArgs.cs b/Radzen.Blazor/DataGridLoadChildDataEventArgs.cs index 6ab03c50..dc23fc3a 100644 --- a/Radzen.Blazor/DataGridLoadChildDataEventArgs.cs +++ b/Radzen.Blazor/DataGridLoadChildDataEventArgs.cs @@ -12,12 +12,12 @@ public class DataGridLoadChildDataEventArgs /// Gets or sets the data. /// /// The data. - public IEnumerable Data { get; set; } + public IEnumerable? Data { get; set; } /// /// Gets the item. /// /// The item. - public T Item { get; internal set; } + public T? Item { get; internal set; } } diff --git a/Radzen.Blazor/DataGridLoadColumnFilterDataEventArgs.cs b/Radzen.Blazor/DataGridLoadColumnFilterDataEventArgs.cs index 0eae80e5..88e3b60e 100644 --- a/Radzen.Blazor/DataGridLoadColumnFilterDataEventArgs.cs +++ b/Radzen.Blazor/DataGridLoadColumnFilterDataEventArgs.cs @@ -8,13 +8,13 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridLoadColumnFilterDataEventArgs +public class DataGridLoadColumnFilterDataEventArgs where T : notnull { /// /// Gets or sets the data. /// /// The data. - public IEnumerable Data { get; set; } + public IEnumerable? Data { get; set; } /// /// Gets or sets the total data count. @@ -37,17 +37,17 @@ public class DataGridLoadColumnFilterDataEventArgs /// Gets the filter expression as a string. /// /// The filter. - public string Filter { get; internal set; } + public string? Filter { get; internal set; } /// /// Gets or sets filter property used to limit and distinct values, if not set, args.Data are used as values. /// /// The filter property. - public string Property { get; set; } + public string? Property { get; set; } /// /// Gets the RadzenDataGridColumn. /// - public Radzen.Blazor.RadzenDataGridColumn Column { get; internal set; } + public Radzen.Blazor.RadzenDataGridColumn? Column { get; internal set; } } diff --git a/Radzen.Blazor/DataGridLoadSettingsEventArgs.cs b/Radzen.Blazor/DataGridLoadSettingsEventArgs.cs index 2cab35ce..00b8be6e 100644 --- a/Radzen.Blazor/DataGridLoadSettingsEventArgs.cs +++ b/Radzen.Blazor/DataGridLoadSettingsEventArgs.cs @@ -8,6 +8,6 @@ public class DataGridLoadSettingsEventArgs /// /// Gets or sets the settings. /// - public DataGridSettings Settings { get; set; } + public DataGridSettings? Settings { get; set; } } diff --git a/Radzen.Blazor/DataGridPickedColumnsChangedEventArgs.cs b/Radzen.Blazor/DataGridPickedColumnsChangedEventArgs.cs index c5c21d85..2aa75651 100644 --- a/Radzen.Blazor/DataGridPickedColumnsChangedEventArgs.cs +++ b/Radzen.Blazor/DataGridPickedColumnsChangedEventArgs.cs @@ -7,11 +7,11 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridPickedColumnsChangedEventArgs +public class DataGridPickedColumnsChangedEventArgs where T : notnull { /// /// Gets the picked columns. /// - public IEnumerable> Columns { get; internal set; } + public IEnumerable>? Columns { get; internal set; } } diff --git a/Radzen.Blazor/DataGridRenderEventArgs.cs b/Radzen.Blazor/DataGridRenderEventArgs.cs index d80a5541..669edadd 100644 --- a/Radzen.Blazor/DataGridRenderEventArgs.cs +++ b/Radzen.Blazor/DataGridRenderEventArgs.cs @@ -6,12 +6,12 @@ namespace Radzen; /// Supplies information about a event that is being raised. /// /// The data item type. -public class DataGridRenderEventArgs +public class DataGridRenderEventArgs where T : notnull { /// /// Gets the instance of the RadzenDataGrid component which has rendered. /// - public RadzenDataGrid Grid { get; internal set; } + public RadzenDataGrid? Grid { get; internal set; } /// /// Gets a value indicating whether this is the first time the RadzenDataGrid has rendered. diff --git a/Radzen.Blazor/DataGridRowMouseEventArgs.cs b/Radzen.Blazor/DataGridRowMouseEventArgs.cs index 6c0de1fb..0ac256bf 100644 --- a/Radzen.Blazor/DataGridRowMouseEventArgs.cs +++ b/Radzen.Blazor/DataGridRowMouseEventArgs.cs @@ -9,6 +9,6 @@ public class DataGridRowMouseEventArgs : Microsoft.AspNetCore.Components.Web. /// /// Gets the data item which the clicked DataGrid row represents. /// - public T Data { get; internal set; } + public T? Data { get; internal set; } } diff --git a/Radzen.Blazor/DataGridSettings.cs b/Radzen.Blazor/DataGridSettings.cs index 89b1772e..da84f153 100644 --- a/Radzen.Blazor/DataGridSettings.cs +++ b/Radzen.Blazor/DataGridSettings.cs @@ -10,12 +10,12 @@ public class DataGridSettings /// /// Columns. /// - public IEnumerable Columns { get; set; } + public IEnumerable Columns { get; set; } = System.Array.Empty(); /// /// Groups. /// - public IEnumerable Groups { get; set; } + public IEnumerable Groups { get; set; } = System.Array.Empty(); /// /// CurrentPage. diff --git a/Radzen.Blazor/DateScale.cs b/Radzen.Blazor/DateScale.cs index 1973272f..8f95172c 100644 --- a/Radzen.Blazor/DateScale.cs +++ b/Radzen.Blazor/DateScale.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Collections.Generic; @@ -50,14 +51,14 @@ namespace Radzen.Blazor { if (min != null) { - var minDate = Convert.ToDateTime(min); + var minDate = Convert.ToDateTime(min, CultureInfo.InvariantCulture); Input.Start = minDate.Ticks; Round = false; } if (max != null) { - var maxDate = Convert.ToDateTime(max); + var maxDate = Convert.ToDateTime(max, CultureInfo.InvariantCulture); Input.End = maxDate.Ticks; Round = false; } diff --git a/Radzen.Blazor/Debouncer.cs b/Radzen.Blazor/Debouncer.cs index 766a9472..778419ec 100644 --- a/Radzen.Blazor/Debouncer.cs +++ b/Radzen.Blazor/Debouncer.cs @@ -6,9 +6,9 @@ namespace Radzen; /// /// Utility class for debouncing and throttling function calls. /// -internal class Debouncer +internal class Debouncer : IDisposable { - private System.Timers.Timer timer; + private System.Timers.Timer? timer; private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1); /// @@ -86,4 +86,15 @@ internal class Debouncer timer.Start(); timerStarted = curTime; } + + /// + public void Dispose() + { + if(timer != null) + { + timer.Stop(); + timer.Dispose(); + timer = null; + } + } } diff --git a/Radzen.Blazor/DialogService.cs b/Radzen.Blazor/DialogService.cs index 06445aa0..e43c8a4e 100644 --- a/Radzen.Blazor/DialogService.cs +++ b/Radzen.Blazor/DialogService.cs @@ -41,8 +41,8 @@ namespace Radzen /// public class DialogService : IDisposable { - private DotNetObjectReference reference; - internal DotNetObjectReference Reference + private DotNetObjectReference? reference; + internal DotNetObjectReference? Reference { get { @@ -59,15 +59,15 @@ namespace Radzen /// Gets or sets the URI helper. /// /// The URI helper. - NavigationManager UriHelper { get; set; } - IJSRuntime JSRuntime { get; set; } + NavigationManager? UriHelper { get; set; } + IJSRuntime? JSRuntime { get; set; } /// /// Initializes a new instance of the class. /// /// The URI helper. /// IJSRuntime instance. - public DialogService(NavigationManager uriHelper, IJSRuntime jsRuntime) + public DialogService(NavigationManager? uriHelper, IJSRuntime? jsRuntime) { UriHelper = uriHelper; JSRuntime = jsRuntime; @@ -78,9 +78,9 @@ namespace Radzen } } - private void UriHelper_OnLocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) + private void UriHelper_OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) { - while (dialogs.Any()) + while (dialogs.Count > 0) { Close(); } @@ -94,27 +94,27 @@ namespace Radzen /// /// Raises the Close event. /// - public event Action OnClose; + public event Action? OnClose; /// /// Occurs when [on refresh]. /// - public event Action OnRefresh; + public event Action? OnRefresh; /// /// Occurs when a new dialog is open. /// - public event Action, DialogOptions> OnOpen; + public event Action, DialogOptions>? OnOpen; /// /// Raises the Close event for the side dialog /// - public event Action OnSideClose; + public event Action? OnSideClose; /// /// Raises the Open event for the side dialog /// - public event Action, SideDialogOptions> OnSideOpen; + public event Action, SideDialogOptions>? OnSideOpen; /// /// Opens a dialog with the specified arguments. @@ -123,7 +123,7 @@ namespace Radzen /// The text displayed in the title bar of the dialog. /// The dialog parameters. /// The dialog options. - public virtual void Open(string title, Dictionary parameters = null, DialogOptions options = null) where T : ComponentBase + public virtual void Open(string title, Dictionary? parameters = null, DialogOptions? options = null) where T : ComponentBase { OpenDialog(title, parameters, options); } @@ -135,7 +135,7 @@ namespace Radzen /// The type of the component to be displayed in the dialog. Must inherit from . /// The dialog parameters. /// The dialog options. - public virtual void Open(string title, Type componentType, Dictionary parameters = null, DialogOptions options = null) + public virtual void Open(string title, Type componentType, Dictionary? parameters = null, DialogOptions? options = null) { if (!typeof(ComponentBase).IsAssignableFrom(componentType)) { @@ -143,8 +143,12 @@ namespace Radzen } var method = GetType().GetMethod(nameof(OpenDialog), BindingFlags.Instance | BindingFlags.NonPublic); + if (method == null) + { + throw new InvalidOperationException("OpenDialog method not found."); + } - method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters, options }); + method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters!, options! }); } /// @@ -159,7 +163,7 @@ namespace Radzen /// The tasks /// protected List> tasks = new List>(); - private TaskCompletionSource sideDialogResultTask; + private TaskCompletionSource? sideDialogResultTask; /// /// Opens a dialog with the specified arguments. @@ -169,7 +173,7 @@ namespace Radzen /// The dialog parameters. Passed as property values of . /// The dialog options. /// The value passed as argument to . - public virtual Task OpenAsync(string title, Dictionary parameters = null, DialogOptions options = null) where T : ComponentBase + public virtual Task OpenAsync(string title, Dictionary? parameters = null, DialogOptions? options = null) where T : ComponentBase { var task = new TaskCompletionSource(); tasks.Add(task); @@ -188,7 +192,7 @@ namespace Radzen /// The dialog options. /// A task that represents the result passed as an argument to . /// Thrown if does not inherit from . - public virtual Task OpenAsync(string title, Type componentType, Dictionary parameters = null, DialogOptions options = null) + public virtual Task OpenAsync(string title, Type componentType, Dictionary? parameters = null, DialogOptions? options = null) { if (!typeof(ComponentBase).IsAssignableFrom(componentType)) { @@ -199,8 +203,12 @@ namespace Radzen tasks.Add(task); var method = GetType().GetMethod(nameof(OpenDialog), BindingFlags.Instance | BindingFlags.NonPublic); + if (method == null) + { + throw new InvalidOperationException("OpenDialog method not found."); + } - method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters, options }); + method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters!, options! }); return task.Task; } @@ -214,7 +222,7 @@ namespace Radzen /// The dialog parameters. Passed as property values of /// The side dialog options. /// A task that completes when the dialog is closed or a new one opened - public Task OpenSideAsync(string title, Dictionary parameters = null, SideDialogOptions options = null) + public Task OpenSideAsync(string title, Dictionary? parameters = null, SideDialogOptions? options = null) where T : ComponentBase { CloseSide(); @@ -238,7 +246,7 @@ namespace Radzen /// The side dialog options. /// A task that represents the result passed as an argument to . /// Thrown if does not inherit from . - public Task OpenSideAsync(string title, Type componentType, Dictionary parameters = null, SideDialogOptions options = null) + public Task OpenSideAsync(string title, Type componentType, Dictionary? parameters = null, SideDialogOptions? options = null) { if (!typeof(ComponentBase).IsAssignableFrom(componentType)) { @@ -267,7 +275,7 @@ namespace Radzen /// The text displayed in the title bar of the side dialog. /// The dialog parameters. Passed as property values of /// The side dialog options. - public void OpenSide(string title, Dictionary parameters = null, SideDialogOptions options = null) + public void OpenSide(string title, Dictionary? parameters = null, SideDialogOptions? options = null) where T : ComponentBase { CloseSide(); @@ -289,7 +297,7 @@ namespace Radzen /// The dialog parameters, passed as property values of the specified component. /// The side dialog options. /// Thrown if does not inherit from . - public void OpenSide(string title, Type componentType, Dictionary parameters = null, SideDialogOptions options = null) + public void OpenSide(string title, Type componentType, Dictionary? parameters = null, SideDialogOptions? options = null) { if (!typeof(ComponentBase).IsAssignableFrom(componentType)) { @@ -312,7 +320,7 @@ namespace Radzen /// Closes the side dialog /// /// The result of the Dialog - public virtual void CloseSide(dynamic result = null) + public virtual void CloseSide(dynamic? result = null) { if (sideDialogResultTask?.Task.IsCompleted == false) { @@ -322,7 +330,7 @@ namespace Radzen OnSideClose?.Invoke(result); } - private TaskCompletionSource sideDialogCloseTask; + private TaskCompletionSource? sideDialogCloseTask; internal void OnSideCloseComplete() { @@ -334,7 +342,7 @@ namespace Radzen /// Closes the side dialog and waits for the closing animation to finish. /// /// The result of the Dialog - public async Task CloseSideAsync(dynamic result = null) + public async Task CloseSideAsync(dynamic? result = null) { sideDialogCloseTask = new TaskCompletionSource(); @@ -351,7 +359,7 @@ namespace Radzen /// The dialog options. /// The cancellation token. /// The value passed as argument to . - public virtual Task OpenAsync(string title, RenderFragment childContent, DialogOptions options = null, CancellationToken? cancellationToken = null) + public virtual Task OpenAsync(string title, RenderFragment childContent, DialogOptions? options = null, CancellationToken? cancellationToken = null) { var task = new TaskCompletionSource(); @@ -377,7 +385,7 @@ namespace Radzen /// The dialog options. /// The cancellation token. /// The value passed as argument to . - public virtual Task OpenAsync(RenderFragment titleContent, RenderFragment childContent, DialogOptions options = null, CancellationToken? cancellationToken = null) + public virtual Task OpenAsync(RenderFragment titleContent, RenderFragment childContent, DialogOptions? options = null, CancellationToken? cancellationToken = null) { var task = new TaskCompletionSource(); @@ -402,7 +410,7 @@ namespace Radzen /// The text displayed in the title bar of the dialog. /// The content displayed in the dialog. /// The dialog options. - public virtual void Open(string title, RenderFragment childContent, DialogOptions options = null) + public virtual void Open(string title, RenderFragment childContent, DialogOptions? options = null) { options = options ?? new DialogOptions(); @@ -416,12 +424,13 @@ namespace Radzen /// protected List dialogs = new List(); - internal void OpenDialog(string title, Dictionary parameters, DialogOptions options) + internal void OpenDialog(string? title, Dictionary? parameters, DialogOptions? options) { dialogs.Add(new object()); // Validate and set default values for the dialog options options ??= new(); + parameters ??= new Dictionary(); options.Width = !String.IsNullOrEmpty(options.Width) ? options.Width : "600px"; options.Left = !String.IsNullOrEmpty(options.Left) ? options.Left : ""; options.Top = !String.IsNullOrEmpty(options.Top) ? options.Top : ""; @@ -440,7 +449,7 @@ namespace Radzen /// /// The result. [JSInvokable("DialogService.Close")] - public virtual void Close(dynamic result = null) + public virtual void Close(dynamic? result = null) { var dialog = dialogs.LastOrDefault(); @@ -464,7 +473,7 @@ namespace Radzen reference?.Dispose(); reference = null; - UriHelper.LocationChanged -= UriHelper_OnLocationChanged; + UriHelper?.LocationChanged -= UriHelper_OnLocationChanged; } /// @@ -475,7 +484,7 @@ namespace Radzen /// The options. /// The cancellation token. /// true if the user clicked the OK button, false otherwise. - public virtual async Task Confirm(string message = "Confirm?", string title = "Confirm", ConfirmOptions options = null, CancellationToken? cancellationToken = null) + public virtual async Task Confirm(string message = "Confirm?", string title = "Confirm", ConfirmOptions? options = null, CancellationToken? cancellationToken = null) { // Validate and set default values for the dialog options options ??= new(); @@ -524,7 +533,7 @@ namespace Radzen /// The options. /// The cancellation token. /// true if the user clicked the OK button, false otherwise. - public virtual async Task Confirm(RenderFragment message, string title = "Confirm", ConfirmOptions options = null, CancellationToken? cancellationToken = null) + public virtual async Task Confirm(RenderFragment message, string title = "Confirm", ConfirmOptions? options = null, CancellationToken? cancellationToken = null) { // Validate and set default values for the dialog options options ??= new(); @@ -573,7 +582,7 @@ namespace Radzen /// The options. /// The cancellation token. /// true if the user clicked the OK button, false otherwise. - public virtual async Task Alert(string message = "", string title = "Message", AlertOptions options = null, CancellationToken? cancellationToken = null) + public virtual async Task Alert(string message = "", string title = "Message", AlertOptions? options = null, CancellationToken? cancellationToken = null) { // Validate and set default values for the dialog options options ??= new(); @@ -616,7 +625,7 @@ namespace Radzen /// The options. /// The cancellation token. /// true if the user clicked the OK button, false otherwise. - public virtual async Task Alert(RenderFragment message, string title = "Message", AlertOptions options = null, CancellationToken? cancellationToken = null) + public virtual async Task Alert(RenderFragment message, string title = "Message", AlertOptions? options = null, CancellationToken? cancellationToken = null) { // Validate and set default values for the dialog options options ??= new(); @@ -660,7 +669,7 @@ namespace Radzen /// /// Occurs when a property value changes. /// - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; /// /// Raises the event. @@ -707,12 +716,12 @@ namespace Radzen } } - private string width; + private string? width; /// /// Gets or sets the width of the dialog. /// /// The width. - public string Width + public string? Width { get => width; set @@ -725,12 +734,12 @@ namespace Radzen } } - private string height; + private string? height; /// /// Gets or sets the height of the dialog. /// /// The height. - public string Height + public string? Height { get => height; set @@ -743,12 +752,12 @@ namespace Radzen } } - private string style; + private string? style; /// /// Gets or sets the CSS style of the dialog /// /// The style. - public string Style + public string? Style { get => style; set @@ -761,7 +770,7 @@ namespace Radzen } } - private bool closeDialogOnOverlayClick = false; + private bool closeDialogOnOverlayClick; /// /// Gets or sets a value indicating whether the dialog should be closed by clicking the overlay. /// @@ -779,11 +788,11 @@ namespace Radzen } } - private string cssClass; + private string? cssClass; /// /// Gets or sets dialog box custom class /// - public string CssClass + public string? CssClass { get => cssClass; set @@ -796,11 +805,11 @@ namespace Radzen } } - private string wrapperCssClass; + private string? wrapperCssClass; /// /// Gets or sets the CSS classes added to the dialog's wrapper element. /// - public string WrapperCssClass + public string? WrapperCssClass { get => wrapperCssClass; set @@ -813,11 +822,11 @@ namespace Radzen } } - private string contentCssClass; + private string? contentCssClass; /// /// Gets or sets the CSS classes added to the dialog's content element. /// - public string ContentCssClass + public string? ContentCssClass { get => contentCssClass; set @@ -830,7 +839,7 @@ namespace Radzen } } - private int closeTabIndex = 0; + private int closeTabIndex; /// /// Gets or sets a value the dialog escape tabindex. Set to 0 by default. /// @@ -847,14 +856,14 @@ namespace Radzen } } - private RenderFragment titleContent; + private RenderFragment? titleContent; private bool resizable; /// /// Gets or sets the title content. /// /// The title content. - public RenderFragment TitleContent + public RenderFragment? TitleContent { get => titleContent; set @@ -890,12 +899,12 @@ namespace Radzen /// public class SideDialogOptions : DialogOptionsBase { - private string title; + private string? title; /// /// The title displayed on the dialog. /// - public string Title + public string? Title { get => title; set @@ -945,7 +954,7 @@ namespace Radzen } } - private bool autoFocusFirstElement = false; + private bool autoFocusFirstElement; /// /// Gets or sets a value indicating whether to focus the first focusable HTML element. Set to true by default. @@ -1061,26 +1070,26 @@ namespace Radzen /// /// The icon. - public string Icon { get; set; } + public string? Icon { get; set; } /// /// Gets or sets the icon color in Title. /// /// The icon color. - public string IconColor { get; set; } + public string? IconColor { get; set; } /// /// Gets or sets the CSS style of the Icon in Title. /// public string IconStyle { get; set; } = "margin-right: 0.75rem"; - private Action resize; + private Action? resize; /// /// Gets or sets the change. /// /// The change. - public Action Resize + public Action? Resize { get => resize; set @@ -1112,13 +1121,13 @@ namespace Radzen } } - private Action drag; + private Action? drag; /// /// Gets or sets the change. /// /// The change. - public Action Drag + public Action? Drag { get => drag; set @@ -1131,13 +1140,13 @@ namespace Radzen } } - private string left; + private string? left; /// /// Gets or sets the X coordinate of the dialog. Maps to the left CSS attribute. /// /// The left. - public string Left + public string? Left { get => left; set @@ -1150,13 +1159,13 @@ namespace Radzen } } - private string top; + private string? top; /// /// Gets or sets the Y coordinate of the dialog. Maps to the top CSS attribute. /// /// The top. - public string Top + public string? Top { get => top; set @@ -1169,13 +1178,13 @@ namespace Radzen } } - private string bottom; + private string? bottom; /// /// Specifies the bottom CSS attribute. /// /// The bottom. - public string Bottom + public string? Bottom { get => bottom; set @@ -1188,13 +1197,13 @@ namespace Radzen } } - private RenderFragment childContent; + private RenderFragment? childContent; /// /// Gets or sets the child content. /// /// The child content. - public RenderFragment ChildContent + public RenderFragment? ChildContent { get => childContent; set @@ -1251,12 +1260,12 @@ namespace Radzen /// public class AlertOptions : DialogOptions { - private string okButtonText; + private string? okButtonText; /// /// Gets or sets the text of the OK button. /// - public string OkButtonText + public string? OkButtonText { get => okButtonText; set @@ -1275,12 +1284,12 @@ namespace Radzen /// public class ConfirmOptions : AlertOptions { - private string cancelButtonText; + private string? cancelButtonText; /// /// Gets or sets the text of the Cancel button. /// - public string CancelButtonText + public string? CancelButtonText { get => cancelButtonText; set @@ -1299,13 +1308,13 @@ namespace Radzen /// public class Dialog : INotifyPropertyChanged { - private string title; + private string? title; /// /// Gets or sets the title. /// /// The title. - public string Title + public string? Title { get => title; set @@ -1318,13 +1327,13 @@ namespace Radzen } } - private Type type; + private Type? type; /// /// Gets or sets the type. /// /// The type. - public Type Type + public Type? Type { get => type; set @@ -1337,13 +1346,13 @@ namespace Radzen } } - private Dictionary parameters; + private Dictionary? parameters; /// /// Gets or sets the parameters. /// /// The parameters. - public Dictionary Parameters + public Dictionary? Parameters { get => parameters; set @@ -1356,13 +1365,13 @@ namespace Radzen } } - private DialogOptions options; + private DialogOptions? options; /// /// Gets or sets the options. /// /// The options. - public DialogOptions Options + public DialogOptions? Options { get => options; set @@ -1378,7 +1387,7 @@ namespace Radzen /// /// Occurs when a property value changes. /// - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; /// /// Raises the event. diff --git a/Radzen.Blazor/Directory.Build.props b/Radzen.Blazor/Directory.Build.props new file mode 100644 index 00000000..604db435 --- /dev/null +++ b/Radzen.Blazor/Directory.Build.props @@ -0,0 +1,73 @@ + + + + + + latest + + + disable + + enable + CA2007 + + + + + latest + true + + + true + true + + + false + + + false + + + All + + + + + true + + + true + + + true + embedded + + + + + false + + + false + false + false + false + + + + + true + true + + + + diff --git a/Radzen.Blazor/DropDownBase.cs b/Radzen.Blazor/DropDownBase.cs index dfe48a36..dcfd211d 100644 --- a/Radzen.Blazor/DropDownBase.cs +++ b/Radzen.Blazor/DropDownBase.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.JSInterop; -using Radzen.Blazor; using System; using System.Collections; using System.Collections.Generic; @@ -22,12 +21,12 @@ namespace Radzen [Parameter] public int VirtualizationOverscanCount { get; set; } - internal Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize virtualize; + internal Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize? virtualize; /// /// The Virtualize instance. /// - public Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize Virtualize + public Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize? Virtualize { get { @@ -35,12 +34,12 @@ namespace Radzen } } - List virtualItems; + List? virtualItems; private async ValueTask> LoadItems(Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderRequest request) { var data = Data != null ? Data.Cast() : Enumerable.Empty(); - var view = (LoadData.HasDelegate ? data : View).Cast().AsQueryable(); + var view = (LoadData.HasDelegate ? data : View)?.Cast().AsQueryable() ?? Enumerable.Empty().AsQueryable(); var totalItemsCount = LoadData.HasDelegate ? Count : view.Count(); var top = request.Count; @@ -54,7 +53,7 @@ namespace Radzen await LoadData.InvokeAsync(new Radzen.LoadDataArgs() { Skip = request.StartIndex, Top = top, Filter = searchText }); } - virtualItems = (LoadData.HasDelegate ? Data : view.Skip(request.StartIndex).Take(top)).Cast().ToList(); + virtualItems = (LoadData.HasDelegate ? (Data ?? Enumerable.Empty()) : view.Skip(request.StartIndex).Take(top)).Cast().ToList(); return new Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderResult(virtualItems, LoadData.HasDelegate ? Count : totalItemsCount); } @@ -105,13 +104,13 @@ namespace Radzen builder.AddAttribute(1, "ItemsProvider", new Microsoft.AspNetCore.Components.Web.Virtualization.ItemsProviderDelegate(LoadItems)); builder.AddAttribute(2, "ChildContent", (RenderFragment)((context) => { - return (RenderFragment)((b) => + return b => { RenderItem(b, context); - }); + }; })); - if (VirtualizationOverscanCount != default(int)) + if (VirtualizationOverscanCount != default) { builder.AddAttribute(3, "OverscanCount", VirtualizationOverscanCount); } @@ -122,9 +121,13 @@ namespace Radzen } else { - foreach (var item in LoadData.HasDelegate ? Data : View) + var items = LoadData.HasDelegate ? Data : View; + if (items != null) { - RenderItem(builder, item); + foreach (var item in items) + { + RenderItem(builder, item); + } } } }); @@ -160,21 +163,18 @@ namespace Radzen // } - System.Collections.Generic.HashSet keys = new System.Collections.Generic.HashSet(); + HashSet keys = new HashSet(); - internal object GetKey(object item) + internal object? GetKey(object item) { - var value = GetItemOrValueFromProperty(item, ValueProperty); + var value = GetItemOrValueFromProperty(item, ValueProperty ?? string.Empty); - if (!keys.Contains(value)) + if (value != null) { keys.Add(value); - return value; - } - else - { - return item; } + + return value; } /// @@ -182,7 +182,7 @@ namespace Radzen /// /// The header template. [Parameter] - public RenderFragment HeaderTemplate { get; set; } + public RenderFragment? HeaderTemplate { get; set; } /// /// Gets or sets a value indicating whether filtering is allowed. Set to false by default. @@ -231,21 +231,21 @@ namespace Radzen /// /// The template. [Parameter] - public RenderFragment Template { get; set; } + public RenderFragment? Template { get; set; } /// /// Gets or sets the value property. /// /// The value property. [Parameter] - public string ValueProperty { get; set; } + public string? ValueProperty { get; set; } /// /// Gets or sets the disabled property. /// /// The disabled property. [Parameter] - public string DisabledProperty { get; set; } + public string? DisabledProperty { get; set; } /// /// Gets or sets the remove chip button title. @@ -282,7 +282,7 @@ namespace Radzen /// /// The selected item /// - protected object selectedItem = null; + protected object? selectedItem; Type GetItemType(IEnumerable items) { var firstType = items.Cast().FirstOrDefault()?.GetType() ?? typeof(object); @@ -301,7 +301,7 @@ namespace Radzen /// protected virtual async System.Threading.Tasks.Task SelectAll() { - if (Disabled) + if (Disabled || View == null) { return; } @@ -316,11 +316,14 @@ namespace Radzen selectedItems.Clear(); } - if (!string.IsNullOrEmpty(ValueProperty)) + if (!string.IsNullOrEmpty(ValueProperty) && Data != null) { var elementType = PropertyAccess.GetElementType(Data.GetType()); - System.Reflection.PropertyInfo pi = PropertyAccess.GetProperty(elementType, ValueProperty); - internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType); + System.Reflection.PropertyInfo? pi = PropertyAccess.GetProperty(elementType, ValueProperty); + if (pi != null) + { + internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType); + } } else { @@ -329,25 +332,34 @@ namespace Radzen internalValue = selectedItems.AsQueryable().Cast(type); } - await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged); + if (internalValue != null) + { + await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged); + } if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); } await Change.InvokeAsync(internalValue); StateHasChanged(); - await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId()); + if (JSRuntime != null) + { + await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId()); + } } internal bool IsAllSelected() { - List notDisabledItemsInList = View.Cast().ToList() + List notDisabledItemsInList = View != null ? View.Cast().ToList() .Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true) - .ToList(); + .ToList() : new List(); if (LoadData.HasDelegate && !string.IsNullOrEmpty(ValueProperty)) { return View != null && notDisabledItemsInList.Count > 0 && notDisabledItemsInList - .All(i => IsItemSelectedByValue(GetItemOrValueFromProperty(i, ValueProperty))); + .All(i => { + var value = GetItemOrValueFromProperty(i, ValueProperty); + return value != null ? IsItemSelectedByValue(value) : false; + }); } return View != null && notDisabledItemsInList.Count > 0 && selectedItems.Count == notDisabledItemsInList.Count; @@ -365,14 +377,17 @@ namespace Radzen /// /// Clears all. /// - protected async System.Threading.Tasks.Task ClearAll() + protected async Task ClearAll() { if (Disabled) return; searchText = null; await SearchTextChanged.InvokeAsync(searchText); - await JSRuntime.InvokeAsync("Radzen.setInputValue", search, ""); + if (JSRuntime != null) + { + await JSRuntime.InvokeAsync("Radzen.setInputValue", search, ""); + } internalValue = collectionAssignment.GetCleared(); selectedItem = null; @@ -381,7 +396,7 @@ namespace Radzen selectedIndex = -1; - await ValueChanged.InvokeAsync((T)internalValue); + await ValueChanged.InvokeAsync(internalValue != null ? (T)internalValue : default(T)!); if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); } await Change.InvokeAsync(internalValue); @@ -393,14 +408,14 @@ namespace Radzen /// /// The data /// - IEnumerable _data; + IEnumerable? _data; /// /// Gets or sets the data. /// /// The data. [Parameter] - public override IEnumerable Data + public override IEnumerable? Data { get { @@ -466,22 +481,26 @@ namespace Radzen } } - Func GetGetter(string propertyName, Type type) + Func GetGetter(string propertyName, Type type) { - if (propertyName?.Contains("[") == true) + if (propertyName?.Contains('[', StringComparison.Ordinal) == true) { var getter = typeof(PropertyAccess).GetMethod("Getter", [typeof(string), typeof(Type)]); + if (getter == null) + { + return PropertyAccess.Getter(propertyName, type); + } var getterMethod = getter.MakeGenericMethod([type, typeof(object)]); - return (i) => getterMethod.Invoke(i, [propertyName, type]); + return (i) => getterMethod.Invoke(i, [propertyName, type])!; } - return PropertyAccess.Getter(propertyName, type); + return PropertyAccess.Getter(propertyName ?? string.Empty, type); } - internal Func valuePropertyGetter; - internal Func textPropertyGetter; - internal Func disabledPropertyGetter; + internal Func? valuePropertyGetter; + internal Func? textPropertyGetter; + internal Func? disabledPropertyGetter; /// /// Gets the item or value from property. @@ -489,7 +508,7 @@ namespace Radzen /// The item. /// The property. /// System.Object. - public object GetItemOrValueFromProperty(object item, string property) + public object? GetItemOrValueFromProperty(object? item, string property) { if (item != null) { @@ -610,16 +629,19 @@ namespace Radzen if (Disabled) return; - await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID, true); - await JSRuntime.InvokeVoidAsync("Radzen.focusElement", isFilter ? UniqueID : SearchID); + if (JSRuntime != null) + { + await JSRuntime.InvokeVoidAsync("Radzen.togglePopup", Element, PopupID, true); + await JSRuntime.InvokeVoidAsync("Radzen.focusElement", isFilter ? UniqueID : SearchID); + } - if (list != null) + if (list != null && JSRuntime != null) { await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", search, list, selectedIndex); } } - internal bool preventKeydown = false; + internal bool preventKeydown; /// /// Handles the key press. @@ -627,9 +649,9 @@ namespace Radzen /// The instance containing the event data. /// if set to true [is filter]. /// Should select item on item change with keyboard. - protected virtual async System.Threading.Tasks.Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null) + protected virtual async Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null) { - if (Disabled || Data == null) + if (Disabled || Data == null || args == null) return; List items = Enumerable.Empty().ToList(); @@ -649,7 +671,7 @@ namespace Radzen } else { - items = View.Cast().ToList(); + items = View != null ? View.Cast().ToList() : Enumerable.Empty().ToList(); } } @@ -661,16 +683,19 @@ namespace Radzen try { - selectedIndex = await JSRuntime.InvokeAsync("Radzen.focusListItem", search, list, key == "ArrowDown" || key == "ArrowRight", selectedIndex); - - var popupOpened = await JSRuntime.InvokeAsync("Radzen.popupOpened", PopupID); - - if (!Multiple && !popupOpened && shouldSelectOnChange != false) + if (JSRuntime != null) { - var itemToSelect = items.ElementAtOrDefault(selectedIndex); - if (itemToSelect != null) + selectedIndex = await JSRuntime.InvokeAsync("Radzen.focusListItem", search, list, key == "ArrowDown" || key == "ArrowRight", selectedIndex); + + var popupOpened = await JSRuntime.InvokeAsync("Radzen.popupOpened", PopupID); + + if (!Multiple && !popupOpened && shouldSelectOnChange != false) { - await OnSelectItem(itemToSelect, true); + var itemToSelect = items.ElementAtOrDefault(selectedIndex); + if (itemToSelect != null) + { + await OnSelectItem(itemToSelect, true); + } } } } @@ -683,37 +708,43 @@ namespace Radzen { preventKeydown = true; - if (selectedIndex == -1 && items.Count() == 1) + if (selectedIndex == -1 && items.Count == 1) { selectedIndex = 0; } - if (selectedIndex >= 0 && selectedIndex <= items.Count() - 1) + if (JSRuntime != null) { - var itemToSelect = items.ElementAtOrDefault(selectedIndex); - - await JSRuntime.InvokeAsync("Radzen.setInputValue", search, $"{searchText}".Trim()); - - if (itemToSelect != null) + if (selectedIndex >= 0 && selectedIndex <= items.Count - 1) { - await OnSelectItem(itemToSelect, true); + var itemToSelect = items.ElementAtOrDefault(selectedIndex); + + await JSRuntime.InvokeAsync("Radzen.setInputValue", search, $"{searchText}".Trim()); + + if (itemToSelect != null) + { + await OnSelectItem(itemToSelect, true); + } } - } - var popupOpened = await JSRuntime.InvokeAsync("Radzen.popupOpened", PopupID); + var popupOpened = await JSRuntime.InvokeAsync("Radzen.popupOpened", PopupID); - if (!popupOpened) - { - if(key != "Space") + if (!popupOpened) { - await OpenPopup(key, isFilter); + if (key != "Space") + { + await OpenPopup(key, isFilter); + } } - } - else - { - if (!Multiple && (!isFilter || key != "Space")) + else { - await ClosePopup(key); + if (!Multiple && (!isFilter || key != "Space")) + { + if (!Multiple && !isFilter) + { + await ClosePopup(key); + } + } } } } @@ -758,9 +789,20 @@ namespace Radzen else if (args.Key.Length == 1 && !args.CtrlKey && !args.AltKey && !args.ShiftKey) { // searching for element - var filteredItems = Query.Where(TextProperty, args.Key, StringFilterOperator.StartsWith, FilterCaseSensitivity.CaseInsensitive) - .Cast(Query.ElementType).Cast().ToList(); - + if (Query == null) + { + return; + } + var query = Query; + var elementType = query.ElementType; + if (elementType == null) + { + return; + } + var filteredItems = (!string.IsNullOrEmpty(TextProperty) ? + query.Where(TextProperty, args.Key, StringFilterOperator.StartsWith, FilterCaseSensitivity.CaseInsensitive) : + query) + .Cast(elementType).Cast().ToList(); if (previousKey != args.Key) { @@ -768,7 +810,7 @@ namespace Radzen itemIndex = -1; } - itemIndex = itemIndex + 1 >= filteredItems.Count() ? 0 : itemIndex + 1; + itemIndex = itemIndex + 1 >= filteredItems.Count ? 0 : itemIndex + 1; var itemToSelect = filteredItems.ElementAtOrDefault(itemIndex); if (itemToSelect is not null) @@ -785,7 +827,10 @@ namespace Radzen { selectedIndex = result.Index; } - await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", list, list, result.Index); + if (JSRuntime != null) + { + await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", list, list, result.Index); + } } } @@ -795,14 +840,17 @@ namespace Radzen internal virtual async Task ClosePopup(string key) { - await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID); + if (JSRuntime != null) + { + await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID); + } } int itemIndex; - string previousKey; + string? previousKey; /// - /// Handles the event. + /// Handles the FilterKeyPress event. /// /// The instance containing the event data. protected virtual async System.Threading.Tasks.Task OnFilterKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args) @@ -852,13 +900,15 @@ namespace Radzen if (Multiple) selectedIndex = -1; - - await JSRuntime.InvokeAsync("Radzen.repositionPopup", Element, PopupID); + if (JSRuntime != null) + { + await JSRuntime.InvokeAsync("Radzen.repositionPopup", Element, PopupID); + } await InvokeAsync(() => SearchTextChanged.InvokeAsync(SearchText)); } /// - /// Handles the event. + /// Handles the KeyPress event. /// /// The instance containing the event data. /// Should select item on item change with keyboard. @@ -872,13 +922,13 @@ namespace Radzen /// /// The item. /// if set to true [is from key]. - protected virtual async System.Threading.Tasks.Task OnSelectItem(object item, bool isFromKey = false) + protected virtual async System.Threading.Tasks.Task OnSelectItem(object? item, bool isFromKey = false) { await SelectItem(item); } /// - /// Handles the event. + /// Handles the Filter event. /// /// The instance containing the event data. protected virtual async System.Threading.Tasks.Task OnFilter(ChangeEventArgs args) @@ -946,7 +996,7 @@ namespace Radzen if (valueChanged) { internalValue = parameters.GetValueOrDefault(nameof(Value)); - if (PreserveCollectionOnSelection) + if (PreserveCollectionOnSelection && internalValue != null) { collectionAssignment = new ReferenceGenericCollectionAssignment((T)internalValue); } @@ -969,7 +1019,7 @@ namespace Radzen } var shouldClose = visibleChanged && !Visible; - if (shouldClose && !firstRender) + if (shouldClose && !firstRender && JSRuntime != null) { await JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", PopupID); } @@ -991,18 +1041,24 @@ namespace Radzen } } - SelectItemFromValue(internalValue); + if (internalValue != null) + { + SelectItemFromValue(internalValue); + } return base.OnParametersSetAsync(); } /// - /// Handles the event. + /// Handles the Change event. /// /// The instance containing the event data. protected void OnChange(ChangeEventArgs args) { - internalValue = args.Value; + if (args != null) + { + internalValue = args.Value; + } } /// @@ -1014,7 +1070,8 @@ namespace Radzen { if (!string.IsNullOrEmpty(ValueProperty)) { - return IsItemSelectedByValue(GetItemOrValueFromProperty(item, ValueProperty)); + var value = GetItemOrValueFromProperty(item, ValueProperty); + return value != null ? IsItemSelectedByValue(value) : false; } else { @@ -1034,7 +1091,7 @@ namespace Radzen /// /// The selected item. [Parameter] - public object SelectedItem + public object? SelectedItem { get { @@ -1072,13 +1129,13 @@ namespace Radzen /// Gets the view. /// /// The view. - protected override IEnumerable View + protected override IEnumerable? View { get { if (_view == null && Query != null) { - _view = Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity); + _view = Query.Where(TextProperty ?? string.Empty, searchText, FilterOperator, FilterCaseSensitivity); } return _view; @@ -1090,7 +1147,7 @@ namespace Radzen /// void SetSelectedIndexFromSelectedItem() { - if (selectedItem != null) + if (selectedItem != null && View != null) { if (typeof(EnumerableQuery).IsAssignableFrom(View.GetType())) { @@ -1112,17 +1169,17 @@ namespace Radzen /// /// The item. /// if set to true [raise change]. - internal async System.Threading.Tasks.Task SelectItemInternal(object item, bool raiseChange = true) + internal async Task SelectItemInternal(object item, bool raiseChange = true) { await SelectItem(item, raiseChange); } - internal object internalValue; + internal object? internalValue; /// /// Will add/remove selected items from a bound ICollection<T>, instead of replacing it. /// - protected bool PreserveCollectionOnSelection = false; + protected bool PreserveCollectionOnSelection; private DefaultCollectionAssignment collectionAssignment = new(); /// @@ -1130,7 +1187,7 @@ namespace Radzen /// /// The item. /// if set to true [raise change]. - public async System.Threading.Tasks.Task SelectItem(object item, bool raiseChange = true) + public async Task SelectItem(object? item, bool raiseChange = true) { if (disabledPropertyGetter != null && item != null && disabledPropertyGetter(item) as bool? == true) { @@ -1143,7 +1200,7 @@ namespace Radzen return; selectedItem = item; - if (!string.IsNullOrEmpty(ValueProperty)) + if (!string.IsNullOrEmpty(ValueProperty) && item != null) { internalValue = PropertyAccess.GetItemOrValueFromProperty(item, ValueProperty); } @@ -1158,16 +1215,26 @@ namespace Radzen } else { - UpdateSelectedItems(item); + if (item != null) + { + UpdateSelectedItems(item); + } - if (!string.IsNullOrEmpty(ValueProperty)) + if (!string.IsNullOrEmpty(ValueProperty) && Data != null) { var elementType = PropertyAccess.GetElementType(Data.GetType()); - System.Reflection.PropertyInfo pi = PropertyAccess.GetProperty(elementType, ValueProperty); - internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType); + System.Reflection.PropertyInfo? pi = PropertyAccess.GetProperty(elementType, ValueProperty); + if(pi != null) + { + internalValue = selectedItems.Select(i => GetItemOrValueFromProperty(i, ValueProperty)).AsQueryable().Cast(pi.PropertyType); + } } else { + if (Data == null) + { + return; + } var query = Data.AsQueryable(); var elementType = query.ElementType; @@ -1196,11 +1263,14 @@ namespace Radzen { if (Multiple) { - await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged); + if (internalValue != null) + { + await collectionAssignment.MakeAssignment((IEnumerable)internalValue, ValueChanged); + } } else { - await ValueChanged.InvokeAsync(internalValue != null ? (T)internalValue : default); + await ValueChanged.InvokeAsync(internalValue != null ? (T)internalValue : default(T)!); } } @@ -1212,7 +1282,7 @@ namespace Radzen } /// - public override object GetValue() + public override object? GetValue() { return internalValue; } @@ -1223,7 +1293,7 @@ namespace Radzen { var value = GetItemOrValueFromProperty(item, ValueProperty); - if (!IsItemSelectedByValue(value)) + if (value != null && !IsItemSelectedByValue(value)) { selectedItems.Add(item); } @@ -1292,7 +1362,7 @@ namespace Radzen if (typeof(EnumerableQuery).IsAssignableFrom(view.GetType())) { - item = view.OfType().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).FirstOrDefault(); + item = view.OfType().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).FirstOrDefault()!; } else { @@ -1305,7 +1375,7 @@ namespace Radzen } }, LogicalFilterOperator.And, - FilterCaseSensitivity.Default).FirstOrDefault(); + FilterCaseSensitivity.Default).FirstOrDefault()!; } if (!object.Equals(item, null) && !selectedItems.AsQueryable().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).Any()) @@ -1330,7 +1400,7 @@ namespace Radzen /// /// For lists of objects, an IEqualityComparer to control how selected items are determined /// - [Parameter] public IEqualityComparer ItemComparer { get; set; } + [Parameter] public IEqualityComparer? ItemComparer { get; set; } internal bool IsItemSelectedByValue(object v) { @@ -1353,6 +1423,8 @@ namespace Radzen base.Dispose(); keys.Clear(); + + GC.SuppressFinalize(this); } private class DefaultCollectionAssignment @@ -1363,42 +1435,55 @@ namespace Radzen { if (object.Equals(selectedItems, null)) { - await valueChanged.InvokeAsync(default(T)); + await valueChanged.InvokeAsync(default(T)!); } else { - var list = (IList)Activator.CreateInstance(typeof(T)); - foreach (var i in (IEnumerable)selectedItems) + var list = (IList?)Activator.CreateInstance(); + if (list != null) { - list.Add(i); + foreach (var i in (IEnumerable)selectedItems) + { + list.Add(i); + } + await valueChanged.InvokeAsync((T)(object)list); + } + else + { + await valueChanged.InvokeAsync(default(T)!); } - await valueChanged.InvokeAsync((T)(object)list); } } else if (typeof(T).IsGenericType && typeof(ICollection<>).MakeGenericType(typeof(T).GetGenericArguments()[0]).IsAssignableFrom(typeof(T))) { if (object.Equals(selectedItems, null)) { - await valueChanged.InvokeAsync(default(T)); + await valueChanged.InvokeAsync(default(T)!); } else { - - var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(T).GetGenericArguments()[0])); + var list = (IList?)Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(T).GetGenericArguments()[0])); + if (list != null) + { foreach (var i in (IEnumerable)selectedItems) { list.Add(i); } await valueChanged.InvokeAsync((T)(object)list); + } + else + { + await valueChanged.InvokeAsync(default(T)!); + } } } else { - await valueChanged.InvokeAsync(object.Equals(selectedItems, null) ? default(T) : (T)selectedItems); + await valueChanged.InvokeAsync(object.Equals(selectedItems, null) ? default(T)! : (T)selectedItems); } } - public virtual T GetCleared() + public virtual T? GetCleared() { return default(T); } @@ -1408,9 +1493,9 @@ namespace Radzen { private readonly T originalCollection; private readonly bool canHandle; - private readonly System.Reflection.MethodInfo clearMethod; - private readonly System.Reflection.MethodInfo addMethod; - private readonly System.Reflection.MethodInfo removeMethod; + private readonly System.Reflection.MethodInfo? clearMethod; + private readonly System.Reflection.MethodInfo? addMethod; + private readonly System.Reflection.MethodInfo? removeMethod; public ReferenceGenericCollectionAssignment(T originalCollection) { @@ -1437,34 +1522,34 @@ namespace Radzen public override async Task MakeAssignment(IEnumerable selectedItems, EventCallback valueChanged) { - if (!canHandle) + if (!canHandle || originalCollection == null) { - // Fallback to default behavior when we can't handle the type + // Fallback to default behavior when we can't handle the type or originalCollection is null await base.MakeAssignment(selectedItems, valueChanged); return; } var currentItems = selectedItems.Cast().ToHashSet(); - var existingItems = ((IEnumerable)originalCollection).Cast().ToHashSet(); + var existingItems = (originalCollection as IEnumerable)?.Cast().ToHashSet() ?? new HashSet(); foreach (var i in currentItems) { if (!existingItems.Contains(i)) - addMethod.Invoke(originalCollection, [i]); + addMethod!.Invoke(originalCollection, [i]); } foreach (var i in existingItems) { if (!currentItems.Contains(i)) - removeMethod.Invoke(originalCollection, [i]); + removeMethod!.Invoke(originalCollection, [i]); } await valueChanged.InvokeAsync(originalCollection); } - public override T GetCleared() + public override T? GetCleared() { - if (canHandle) + if (canHandle && originalCollection != null) { - clearMethod.Invoke(originalCollection, null); + clearMethod!.Invoke(originalCollection, null); return originalCollection; } return base.GetCleared(); diff --git a/Radzen.Blazor/DropDownBaseItemRenderEventArgs.cs b/Radzen.Blazor/DropDownBaseItemRenderEventArgs.cs index b2c1c0bf..48450d3c 100644 --- a/Radzen.Blazor/DropDownBaseItemRenderEventArgs.cs +++ b/Radzen.Blazor/DropDownBaseItemRenderEventArgs.cs @@ -10,7 +10,7 @@ public class DropDownBaseItemRenderEventArgs /// /// Gets the data item. /// - public object Item { get; internal set; } + public object? Item { get; internal set; } /// /// Gets or sets a value indicating whether this item is visible. diff --git a/Radzen.Blazor/DropDownItemRenderEventArgs.cs b/Radzen.Blazor/DropDownItemRenderEventArgs.cs index eb17c870..1b044192 100644 --- a/Radzen.Blazor/DropDownItemRenderEventArgs.cs +++ b/Radzen.Blazor/DropDownItemRenderEventArgs.cs @@ -10,6 +10,6 @@ public class DropDownItemRenderEventArgs : DropDownBaseItemRenderEventAr /// /// Gets the DropDown. /// - public RadzenDropDown DropDown { get; internal set; } + public RadzenDropDown? DropDown { get; internal set; } } diff --git a/Radzen.Blazor/DynamicExtensions.cs b/Radzen.Blazor/DynamicExtensions.cs index ae1e9ef1..7d0a950c 100644 --- a/Radzen.Blazor/DynamicExtensions.cs +++ b/Radzen.Blazor/DynamicExtensions.cs @@ -9,8 +9,13 @@ namespace System.Linq.Dynamic.Core /// public static class DynamicExtensions { - static readonly Func typeLocator = type => AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => a.GetTypes()).FirstOrDefault(t => t.FullName.Replace("+", ".") == type); + static readonly Func typeLocator = type => AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .FirstOrDefault(t => + { + var fullName = t.FullName; + return fullName != null && fullName.Replace("+", ".", StringComparison.Ordinal) == type; + }); /// /// Filters using the specified filter descriptors. @@ -18,28 +23,30 @@ namespace System.Linq.Dynamic.Core public static IQueryable Where( this IQueryable source, string predicate, - object[] parameters = null, object[] otherParameters = null) + object[]? parameters = null, object[]? otherParameters = null) { + ArgumentNullException.ThrowIfNull(source); + try { if (parameters != null && !string.IsNullOrEmpty(predicate)) { predicate = Regex.Replace(predicate, @"@(\d+)", match => { - int index = int.Parse(match.Groups[1].Value); + int index = int.Parse(match.Groups[1].Value, System.Globalization.CultureInfo.InvariantCulture); if (index >= parameters.Length) throw new InvalidOperationException($"No parameter provided for {match.Value}"); - return ExpressionSerializer.FormatValue(parameters[index]); + return ExpressionSerializer.FormatValue(parameters[index]) ?? string.Empty; }); } - predicate = (predicate == "true" ? "" : predicate) - .Replace("DateTime(", "DateTime.Parse(") - .Replace("DateTimeOffset(", "DateTimeOffset.Parse(") - .Replace("DateOnly(", "DateOnly.Parse(") - .Replace("Guid(", "Guid.Parse(") - .Replace(" = ", " == "); + predicate = (predicate == "true" ? "" : predicate ?? string.Empty) + .Replace("DateTime(", "DateTime.Parse(", StringComparison.Ordinal) + .Replace("DateTimeOffset(", "DateTimeOffset.Parse(", StringComparison.Ordinal) + .Replace("DateOnly(", "DateOnly.Parse(", StringComparison.Ordinal) + .Replace("Guid(", "Guid.Parse(", StringComparison.Ordinal) + .Replace(" = ", " == ", StringComparison.Ordinal); return !string.IsNullOrEmpty(predicate) ? source.Where(ExpressionParser.ParsePredicate(predicate, typeLocator)) : source; @@ -56,8 +63,10 @@ namespace System.Linq.Dynamic.Core public static IOrderedQueryable OrderBy( this IQueryable source, string selector, - object[] parameters = null) + object[]? parameters = null) { + ArgumentNullException.ThrowIfNull(source); + try { return QueryableExtension.OrderBy(source, selector); @@ -71,8 +80,10 @@ namespace System.Linq.Dynamic.Core /// /// Projects each element of a sequence into a collection of property values. /// - public static IQueryable Select(this IQueryable source, string selector, object[] parameters = null) + public static IQueryable Select(this IQueryable source, string selector, object[]? parameters = null) { + ArgumentNullException.ThrowIfNull(source); + if (source.ElementType == typeof(object)) { var elementType = source.ElementType; @@ -95,8 +106,10 @@ namespace System.Linq.Dynamic.Core /// /// Projects each element of a sequence into a collection of property values. /// - public static IQueryable Select(this IQueryable source, string selector, object[] parameters = null) + public static IQueryable Select(this IQueryable source, string selector, object[]? parameters = null) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(selector); return source.Select(selector, expression => ExpressionParser.ParseLambda(expression, source.ElementType)); } @@ -109,18 +122,27 @@ namespace System.Linq.Dynamic.Core return source; } - if (!selector.Contains("=>")) + if (!selector.Contains("=>", StringComparison.Ordinal)) { var properties = selector - .Replace("new (", "").Replace(")", "").Replace("new {", "").Replace("}", "").Trim() + .Replace("new (", "", StringComparison.Ordinal).Replace(")", "", StringComparison.Ordinal).Replace("new {", "", StringComparison.Ordinal).Replace("}", "", StringComparison.Ordinal).Trim() .Split(",", StringSplitOptions.RemoveEmptyEntries); selector = string.Join(", ", properties - .Select(s => (s.Contains(" as ") ? s.Split(" as ").LastOrDefault().Trim().Replace(".", "_") : s.Trim().Replace(".", "_")) + - " = " + $"it.{s.Split(" as ").FirstOrDefault().Replace(".", "?.").Trim()}")); + .Select(s => + { + var parts = s.Split(" as ", StringSplitOptions.RemoveEmptyEntries); + var sourcePart = (parts.FirstOrDefault() ?? s).Trim(); + var targetPart = (parts.Length > 1 ? parts.Last() : sourcePart).Trim(); + + var safeTarget = targetPart.Replace(".", "_", StringComparison.Ordinal); + var safeSource = sourcePart.Replace(".", "?.", StringComparison.Ordinal); + + return $"{safeTarget} = it.{safeSource}"; + })); } - var lambda = lambdaCreator(selector.Contains("=>") ? selector : $"it => new {{ {selector} }}"); + var lambda = lambdaCreator(selector.Contains("=>", StringComparison.Ordinal) ? selector : $"it => new {{ {selector} }}"); return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), nameof(Queryable.Select), [source.ElementType, lambda.Body.Type], source.Expression, Expression.Quote(lambda))); diff --git a/Radzen.Blazor/DynamicTypeFactory.cs b/Radzen.Blazor/DynamicTypeFactory.cs index 7c3c8a78..59c4dd93 100644 --- a/Radzen.Blazor/DynamicTypeFactory.cs +++ b/Radzen.Blazor/DynamicTypeFactory.cs @@ -39,7 +39,7 @@ static class DynamicTypeFactory "set_" + propertyNames[i], MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, - [propertyTypes[i]]); + new[] { propertyTypes[i] }); var setterIl = setterMethod.GetILGenerator(); setterIl.Emit(OpCodes.Ldarg_0); @@ -51,6 +51,10 @@ static class DynamicTypeFactory } var dynamicType = typeBuilder.CreateType(); + if (dynamicType == null) + { + throw new InvalidOperationException("Failed to create dynamic type."); + } return dynamicType; } } \ No newline at end of file diff --git a/Radzen.Blazor/ExpressionParser.cs b/Radzen.Blazor/ExpressionParser.cs index 7de9f7b9..b18bd5cc 100644 --- a/Radzen.Blazor/ExpressionParser.cs +++ b/Radzen.Blazor/ExpressionParser.cs @@ -28,7 +28,7 @@ public class ExpressionParser /// public static Expression> ParseLambda(string expression, Func? typeResolver = null) { - var lambda = ParseLambda(expression, typeof(T), typeResolver); + var lambda = ParseLambda(expression, typeResolver); return Expression.Lambda>(lambda.Body, lambda.Parameters[0]); } @@ -52,7 +52,7 @@ public class ExpressionParser } private readonly List tokens; - private int position = 0; + private int position; private readonly Func? typeResolver; private readonly Stack parameterStack = new(); diff --git a/Radzen.Blazor/ExpressionSerializer.cs b/Radzen.Blazor/ExpressionSerializer.cs index a41743b6..395b971f 100644 --- a/Radzen.Blazor/ExpressionSerializer.cs +++ b/Radzen.Blazor/ExpressionSerializer.cs @@ -30,6 +30,7 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitLambda(Expression node) { + ArgumentNullException.ThrowIfNull(node); if (node.Parameters.Count > 1) { _sb.Append("("); @@ -52,6 +53,7 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitParameter(ParameterExpression node) { + ArgumentNullException.ThrowIfNull(node); _sb.Append(node.Name); return node; } @@ -59,10 +61,11 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitMember(MemberExpression node) { + ArgumentNullException.ThrowIfNull(node); if (node.Expression != null) { Visit(node.Expression); - _sb.Append($".{node.Member.Name}"); + _sb.Append(CultureInfo.InvariantCulture, $".{node.Member.Name}"); } else { @@ -74,14 +77,15 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitMethodCall(MethodCallExpression node) { + ArgumentNullException.ThrowIfNull(node); if (node.Method.IsStatic && node.Arguments.Count > 0 && - (node.Method.DeclaringType == typeof(Enumerable) || + (node.Method.DeclaringType == typeof(Enumerable) || node.Method.DeclaringType == typeof(Queryable))) { Visit(node.Arguments[0]); - _sb.Append($".{node.Method.Name}("); + _sb.Append(CultureInfo.InvariantCulture, $".{node.Method.Name}("); - for (int i = 1; i < node.Arguments.Count; i++) + for (int i = 1; i < node.Arguments.Count; i++) { if (i > 1) _sb.Append(", "); @@ -99,7 +103,7 @@ public class ExpressionSerializer : ExpressionVisitor } else if (node.Method.IsStatic) { - _sb.Append($"{node.Method.DeclaringType.Name}.{node.Method.Name}("); + _sb.Append(CultureInfo.InvariantCulture, $"{node.Method.DeclaringType?.Name}.{node.Method.Name}("); for (int i = 0; i < node.Arguments.Count; i++) { @@ -114,11 +118,11 @@ public class ExpressionSerializer : ExpressionVisitor if (node.Object != null) { Visit(node.Object); - _sb.Append($".{node.Method.Name}("); + _sb.Append(CultureInfo.InvariantCulture, $".{node.Method.Name}("); } else { - _sb.Append($"{node.Method.Name}("); + _sb.Append(CultureInfo.InvariantCulture, $"{node.Method.Name}("); } for (int i = 0; i < node.Arguments.Count; i++) @@ -136,17 +140,18 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitUnary(UnaryExpression node) { + ArgumentNullException.ThrowIfNull(node); if (node.NodeType == ExpressionType.Not) { _sb.Append("(!("); Visit(node.Operand); _sb.Append("))"); } - else if (node.NodeType == ExpressionType.Convert) + else if (node.NodeType == ExpressionType.Convert) { if (node.Operand is IndexExpression indexExpr) { - _sb.Append($"({node.Type.DisplayName(true).Replace("+",".")})"); + _sb.Append(CultureInfo.InvariantCulture, $"({node.Type.DisplayName(true).Replace("+", ".", StringComparison.Ordinal)})"); Visit(indexExpr.Object); @@ -175,20 +180,18 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitConstant(ConstantExpression node) { + ArgumentNullException.ThrowIfNull(node); _sb.Append(FormatValue(node.Value)); return node; } - internal static string FormatValue(object value) + internal static string? FormatValue(object? value) { - if (value == null) - return "null"; - return value switch { - string s when s == string.Empty => @"""""", + string s when s.Length == 0 => @"""""", null => "null", - string s => @$"""{s.Replace("\"", "\\\"")}""", + string s => @$"""{s.Replace("\"", "\\\"", StringComparison.Ordinal)}""", char c => $"'{c}'", bool b => b.ToString().ToLowerInvariant(), DateTime dt => FormatDateTime(dt), @@ -198,7 +201,7 @@ public class ExpressionSerializer : ExpressionVisitor Guid guid => $"Guid.Parse(\"{guid.ToString("D", CultureInfo.InvariantCulture)}\")", IEnumerable enumerable when value is not string => FormatEnumerable(enumerable), _ => value.GetType().IsEnum - ? $"({value.GetType().FullName.Replace("+", ".")})" + Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture).ToString() + ? $"({value.GetType()?.FullName?.Replace("+", ".", StringComparison.Ordinal)})" + Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture).ToString() : Convert.ToString(value, CultureInfo.InvariantCulture) }; } @@ -214,14 +217,15 @@ public class ExpressionSerializer : ExpressionVisitor private static string FormatEnumerable(IEnumerable enumerable) { var arrayType = enumerable.AsQueryable().ElementType; - + var items = enumerable.Cast().Select(FormatValue); - return $"new {(Nullable.GetUnderlyingType(arrayType) != null ? arrayType.DisplayName(true).Replace("+", ".") : "")}[] {{ {string.Join(", ", items)} }}"; + return $"new {(Nullable.GetUnderlyingType(arrayType) != null ? arrayType.DisplayName(true).Replace("+", ".", StringComparison.Ordinal) : "")}[] {{ {string.Join(", ", items)} }}"; } /// protected override Expression VisitNewArray(NewArrayExpression node) { + ArgumentNullException.ThrowIfNull(node); bool needsParentheses = node.NodeType == ExpressionType.NewArrayInit && (node.Expressions.Count > 1 || node.Expressions[0].NodeType != ExpressionType.Constant); @@ -245,9 +249,10 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitBinary(BinaryExpression node) { + ArgumentNullException.ThrowIfNull(node); _sb.Append("("); Visit(node.Left); - _sb.Append($" {GetOperator(node.NodeType)} "); + _sb.Append(CultureInfo.InvariantCulture, $" {GetOperator(node.NodeType)} "); Visit(node.Right); _sb.Append(")"); return node; @@ -256,6 +261,7 @@ public class ExpressionSerializer : ExpressionVisitor /// protected override Expression VisitConditional(ConditionalExpression node) { + ArgumentNullException.ThrowIfNull(node); _sb.Append("("); Visit(node.Test); _sb.Append(" ? "); @@ -290,7 +296,7 @@ public class ExpressionSerializer : ExpressionVisitor ExpressionType.Coalesce => "??", _ => throw new NotSupportedException($"Unsupported operator: {type}") }; - } + } } /// @@ -333,6 +339,7 @@ public static class SharedTypeExtensions /// A string representing the type name. public static string DisplayName(this Type type, bool fullName = true, bool compilable = false) { + ArgumentNullException.ThrowIfNull(type); var stringBuilder = new StringBuilder(); ProcessType(stringBuilder, type, fullName, compilable); return stringBuilder.ToString(); @@ -443,7 +450,7 @@ public static class SharedTypeExtensions } } - var genericPartIndex = type.Name.IndexOf('`'); + var genericPartIndex = type.Name.IndexOf('`', StringComparison.Ordinal); if (genericPartIndex <= 0) { builder.Append(type.Name); diff --git a/Radzen.Blazor/Extensions.cs b/Radzen.Blazor/Extensions.cs index 683d0624..62cfc2b2 100644 --- a/Radzen.Blazor/Extensions.cs +++ b/Radzen.Blazor/Extensions.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -18,8 +19,9 @@ namespace Radzen.Blazor /// /// Gets enum description. /// - public static string GetDisplayDescription(this Enum enumValue, Func translationFunction = null) + public static string GetDisplayDescription(this Enum enumValue, Func? translationFunction = null) { + ArgumentNullException.ThrowIfNull(enumValue); var enumValueAsString = enumValue.ToString(); var val = enumValue.GetType().GetMember(enumValueAsString).FirstOrDefault(); var enumVal = val?.GetCustomAttribute()?.GetDescription() ?? enumValueAsString; @@ -33,10 +35,12 @@ namespace Radzen.Blazor /// /// Converts Enum to IEnumerable of Value/Text. /// - public static IEnumerable EnumAsKeyValuePair(Type enumType, Func translationFunction = null) + public static IEnumerable EnumAsKeyValuePair(Type enumType, Func? translationFunction = null) { + ArgumentNullException.ThrowIfNull(enumType); + Type underlyingType = Enum.GetUnderlyingType(enumType); - return Enum.GetValues(enumType).Cast().Distinct().Select(val => new { Value = Convert.ChangeType(val, underlyingType), Text = val.GetDisplayDescription(translationFunction) }); + return Enum.GetValues(enumType).Cast().Distinct().Select(val => new { Value = Convert.ChangeType(val, underlyingType, CultureInfo.InvariantCulture), Text = val.GetDisplayDescription(translationFunction) }); } /// @@ -67,7 +71,7 @@ namespace Radzen.Blazor var value = typeValue.ToString(); value = Regex.Replace(value, "([^A-Z])([A-Z])", "$1-$2"); return Regex.Replace(value, "([A-Z]+)([A-Z][^A-Z$])", "$1-$2") - .Trim().ToLower(); + .Trim().ToLowerInvariant(); } } } diff --git a/Radzen.Blazor/FileInfo.cs b/Radzen.Blazor/FileInfo.cs index 5ce598f3..6824f47d 100644 --- a/Radzen.Blazor/FileInfo.cs +++ b/Radzen.Blazor/FileInfo.cs @@ -17,7 +17,7 @@ public class FileInfo : IBrowserFile // } - private IBrowserFile source; + private IBrowserFile? source; /// /// Creates FileInfo with IBrowserFile as source. @@ -28,7 +28,9 @@ public class FileInfo : IBrowserFile this.source = source; } - private string name; + private string? name; + private DateTimeOffset? lastModified; + private string? contentType; /// /// Gets the name of the selected file. @@ -37,7 +39,7 @@ public class FileInfo : IBrowserFile { get { - return name ?? source.Name; + return name ?? source?.Name ?? string.Empty; } set { @@ -65,17 +67,25 @@ public class FileInfo : IBrowserFile /// /// Gets the IBrowserFile source. /// - public IBrowserFile Source => source; + public IBrowserFile? Source => source; /// /// Gets the LastModified. /// - public DateTimeOffset LastModified => source.LastModified; + public DateTimeOffset LastModified + { + get => lastModified ?? source?.LastModified ?? default; + set => lastModified = value; + } /// /// Gets the ContentType. /// - public string ContentType => source.ContentType; + public string ContentType + { + get => contentType ?? source?.ContentType ?? string.Empty; + set => contentType = value; + } /// /// Open read stream. @@ -85,6 +95,11 @@ public class FileInfo : IBrowserFile /// The stream. public System.IO.Stream OpenReadStream(long maxAllowedSize = 512000, CancellationToken cancellationToken = default) { + if (source == null) + { + throw new InvalidOperationException("No underlying browser file is associated with this FileInfo instance."); + } + return source.OpenReadStream(maxAllowedSize, cancellationToken); } } diff --git a/Radzen.Blazor/FilterDescriptor.cs b/Radzen.Blazor/FilterDescriptor.cs index c1c93ebe..3e42a4f6 100644 --- a/Radzen.Blazor/FilterDescriptor.cs +++ b/Radzen.Blazor/FilterDescriptor.cs @@ -12,26 +12,26 @@ public class FilterDescriptor /// Gets or sets the name of the filtered property. /// /// The property. - public string Property { get; set; } + public string? Property { get; set; } /// /// Gets or sets the property type. /// /// The property type. [JsonIgnore] - public Type Type { get; set; } + public Type? Type { get; set; } /// /// Gets or sets the name of the filtered property. /// /// The property. - public string FilterProperty { get; set; } + public string? FilterProperty { get; set; } /// /// Gets or sets the value to filter by. /// /// The filter value. - public object FilterValue { get; set; } + public object? FilterValue { get; set; } /// /// Gets or sets the operator which will compare the property value with . @@ -43,7 +43,7 @@ public class FilterDescriptor /// Gets or sets a second value to filter by. /// /// The second filter value. - public object SecondFilterValue { get; set; } + public object? SecondFilterValue { get; set; } /// /// Gets or sets the operator which will compare the property value with . diff --git a/Radzen.Blazor/FormComponent.cs b/Radzen.Blazor/FormComponent.cs index ed2e0226..d30c06d6 100644 --- a/Radzen.Blazor/FormComponent.cs +++ b/Radzen.Blazor/FormComponent.cs @@ -39,8 +39,30 @@ namespace Radzen /// AutoCompleteType. public virtual string AutoCompleteAttribute { - get => Attributes != null && Attributes.ContainsKey("AutoComplete") && $"{Attributes["AutoComplete"]}".ToLower() == "false" ? DefaultAutoCompleteAttribute : - Attributes != null && Attributes.ContainsKey("AutoComplete") ? Attributes["AutoComplete"] as string ?? AutoCompleteType.GetAutoCompleteValue() : AutoCompleteType.GetAutoCompleteValue(); + get + { + if (Attributes != null && Attributes.TryGetValue("AutoComplete", out var value)) + { + var v = (object?)value; + var autoCompleteValue = v switch + { + bool boolValue => boolValue + ? AutoCompleteType.GetAutoCompleteValue() + : DefaultAutoCompleteAttribute, + string stringValue when bool.TryParse(stringValue, out var boolValue) => boolValue + ? AutoCompleteType.GetAutoCompleteValue() + : DefaultAutoCompleteAttribute, + AutoCompleteType typeValue => typeValue.GetAutoCompleteValue(), + _ => value != null ? value.ToString() ?? AutoCompleteType.GetAutoCompleteValue() : AutoCompleteType.GetAutoCompleteValue() + }; + + return string.Equals(autoCompleteValue, "false", StringComparison.OrdinalIgnoreCase) + ? DefaultAutoCompleteAttribute + : autoCompleteValue; + } + + return AutoCompleteType.GetAutoCompleteValue(); + } } /// @@ -48,7 +70,7 @@ namespace Radzen /// public virtual string DefaultAutoCompleteAttribute { get; set; } = "off"; - object ariaAutoComplete; + object? ariaAutoComplete; /// public override async Task SetParametersAsync(ParameterView parameters) @@ -56,10 +78,10 @@ namespace Radzen parameters = parameters.TryGetValue("aria-autocomplete", out ariaAutoComplete) ? ParameterView.FromDictionary(parameters .ToDictionary().Where(i => i.Key != "aria-autocomplete").ToDictionary(i => i.Key, i => i.Value) - .ToDictionary(i => i.Key, i => i.Value)) + .ToDictionary(i => i.Key, i => (object?)i.Value)) : parameters; - await base.SetParametersAsync(parameters); + await base.SetParametersAsync(parameters).ConfigureAwait(false); } /// @@ -70,9 +92,11 @@ namespace Radzen /// /// Gets the aria-autocomplete attribute's string value. /// - public virtual string AriaAutoCompleteAttribute + public virtual string? AriaAutoCompleteAttribute { - get => AutoCompleteAttribute == DefaultAutoCompleteAttribute ? DefaultAriaAutoCompleteAttribute : ariaAutoComplete as string; + get => string.Equals(AutoCompleteAttribute, DefaultAutoCompleteAttribute, StringComparison.Ordinal) + ? DefaultAriaAutoCompleteAttribute + : ariaAutoComplete as string; } } @@ -96,7 +120,7 @@ namespace Radzen /// /// The component name. [Parameter] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the tab order index for keyboard navigation. @@ -112,7 +136,7 @@ namespace Radzen /// /// The placeholder. [Parameter] - public string Placeholder { get; set; } + public string? Placeholder { get; set; } /// /// Gets or sets a value indicating whether this is disabled. @@ -124,21 +148,21 @@ namespace Radzen /// /// The form /// - IRadzenForm _form; + IRadzenForm? _form; /// /// Gets or sets the edit context. /// /// The edit context. [CascadingParameter] - public EditContext EditContext { get; set; } + public EditContext? EditContext { get; set; } /// /// Gets or sets the form. /// /// The form. [CascadingParameter] - public IRadzenForm Form + public IRadzenForm? Form { get { @@ -189,14 +213,14 @@ namespace Radzen /// /// The value /// - protected T _value; + protected T? _value; /// /// Gets or sets the value. /// /// The value. [Parameter] - public virtual T Value + public virtual T? Value { get { @@ -230,7 +254,7 @@ namespace Radzen /// /// The value expression. [Parameter] - public Expression> ValueExpression { get; set; } + public Expression>? ValueExpression { get; set; } /// /// Sets the parameters asynchronous. /// @@ -262,7 +286,7 @@ namespace Radzen /// /// The sender. /// The instance containing the event data. - private void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e) + private void ValidationStateChanged(object? sender, ValidationStateChangedEventArgs e) { StateHasChanged(); } @@ -280,19 +304,21 @@ namespace Radzen } Form?.RemoveComponent(this); + + GC.SuppressFinalize(this); } /// /// Gets the value. /// /// System.Object. - public object GetValue() + public object? GetValue() { return Value; } /// - /// Handles the event. + /// Handles the ContextMenu event. /// /// The instance containing the event data. /// Task. @@ -318,10 +344,10 @@ namespace Radzen /// Provides support for RadzenFormField integration. [CascadingParameter] - public IFormFieldContext FormFieldContext { get; set; } + public IFormFieldContext? FormFieldContext { get; set; } /// Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField. - protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder; + protected string? CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder; /// public virtual async ValueTask FocusAsync() diff --git a/Radzen.Blazor/FormInvalidSubmitEventArgs.cs b/Radzen.Blazor/FormInvalidSubmitEventArgs.cs index 08209de6..a8aa0c13 100644 --- a/Radzen.Blazor/FormInvalidSubmitEventArgs.cs +++ b/Radzen.Blazor/FormInvalidSubmitEventArgs.cs @@ -10,6 +10,6 @@ public class FormInvalidSubmitEventArgs /// /// Gets the validation errors. /// - public IEnumerable Errors { get; set; } + public IEnumerable? Errors { get; set; } } diff --git a/Radzen.Blazor/GaugeBase.cs b/Radzen.Blazor/GaugeBase.cs index 990383b1..d9846219 100644 --- a/Radzen.Blazor/GaugeBase.cs +++ b/Radzen.Blazor/GaugeBase.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -18,7 +19,7 @@ namespace Radzen.Blazor /// /// The child content. [Parameter] - public RenderFragment ChildContent + public RenderFragment? ChildContent { get; set; } @@ -38,7 +39,7 @@ namespace Radzen.Blazor /// /// The width and height are set /// - bool widthAndHeightAreSet = false; + bool widthAndHeightAreSet; /// /// The first render /// @@ -57,7 +58,7 @@ namespace Radzen.Blazor { visibleChanged = false; - if (Visible) + if (Visible && JSRuntime != null) { var rect = await JSRuntime.InvokeAsync("Radzen.createGauge", Element, Reference); @@ -117,23 +118,20 @@ namespace Radzen.Blazor double width = 0; double height = 0; - if (CurrentStyle.ContainsKey("height")) + if (CurrentStyle.TryGetValue("height", out var pixelHeight)) { - var pixelHeight = CurrentStyle["height"]; - - if (pixelHeight.EndsWith("px")) + if (pixelHeight.EndsWith("px", StringComparison.Ordinal)) { - height = Convert.ToDouble(pixelHeight.TrimEnd("px".ToCharArray())); + height = Convert.ToDouble(pixelHeight.TrimEnd("px".ToCharArray()), CultureInfo.InvariantCulture); } } - if (CurrentStyle.ContainsKey("width")) + if (CurrentStyle.TryGetValue("width", out var pixelWidth)) { - var pixelWidth = CurrentStyle["width"]; - if (pixelWidth.EndsWith("px")) + if (pixelWidth.EndsWith("px", StringComparison.Ordinal)) { - width = Convert.ToDouble(pixelWidth.TrimEnd("px".ToCharArray())); + width = Convert.ToDouble(pixelWidth.TrimEnd("px".ToCharArray()), CultureInfo.InvariantCulture); } } @@ -149,7 +147,7 @@ namespace Radzen.Blazor /// /// The visible changed /// - private bool visibleChanged = false; + private bool visibleChanged; /// /// Set parameters as an asynchronous operation. @@ -166,7 +164,7 @@ namespace Radzen.Blazor if (visibleChanged && !firstRender) { - if (Visible == false) + if (Visible == false && JSRuntime != null) { await JSRuntime.InvokeVoidAsync("Radzen.destroyGauge", Element); } @@ -185,10 +183,12 @@ namespace Radzen.Blazor { base.Dispose(); - if (Visible) + if (Visible && JSRuntime != null) { JSRuntime.InvokeVoid("Radzen.destroyGauge", Element); } + + GC.SuppressFinalize(this); } /// diff --git a/Radzen.Blazor/GoogleMapClickEventArgs.cs b/Radzen.Blazor/GoogleMapClickEventArgs.cs index 0bbb3e69..bbddd9c3 100644 --- a/Radzen.Blazor/GoogleMapClickEventArgs.cs +++ b/Radzen.Blazor/GoogleMapClickEventArgs.cs @@ -8,6 +8,6 @@ public class GoogleMapClickEventArgs /// /// The position which represents the clicked map location. /// - public GoogleMapPosition Position { get; set; } + public GoogleMapPosition? Position { get; set; } } diff --git a/Radzen.Blazor/GoogleMapPosition.cs b/Radzen.Blazor/GoogleMapPosition.cs index a618183f..a8de1b51 100644 --- a/Radzen.Blazor/GoogleMapPosition.cs +++ b/Radzen.Blazor/GoogleMapPosition.cs @@ -20,7 +20,7 @@ public class GoogleMapPosition : IEquatable public double Lng { get; set; } /// - public bool Equals(GoogleMapPosition other) + public bool Equals(GoogleMapPosition? other) { if (other != null) { @@ -31,7 +31,7 @@ public class GoogleMapPosition : IEquatable } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return this.Equals(obj as GoogleMapPosition); } diff --git a/Radzen.Blazor/Group.cs b/Radzen.Blazor/Group.cs index 6c3305af..7cfae9ec 100644 --- a/Radzen.Blazor/Group.cs +++ b/Radzen.Blazor/Group.cs @@ -9,13 +9,13 @@ public class Group /// Gets or sets the data. /// /// The data. - public GroupResult Data { get; set; } + public GroupResult Data { get; set; } = new GroupResult(); /// /// Gets or sets the group descriptor. /// /// The group descriptor. - public GroupDescriptor GroupDescriptor { get; set; } + public GroupDescriptor? GroupDescriptor { get; set; } /// /// Gets or sets the level. diff --git a/Radzen.Blazor/GroupDescriptor.cs b/Radzen.Blazor/GroupDescriptor.cs index 17348688..1c3135c9 100644 --- a/Radzen.Blazor/GroupDescriptor.cs +++ b/Radzen.Blazor/GroupDescriptor.cs @@ -9,7 +9,7 @@ public class GroupDescriptor /// Gets or sets the property to group by. /// /// The property. - public string Property { get; set; } + public string Property { get; set; } = string.Empty; /// /// Gets or sets the sort order. @@ -21,13 +21,13 @@ public class GroupDescriptor /// Gets or sets the title displayed in the group. /// /// The title. - public string Title { get; set; } + public string Title { get; set; } = string.Empty; /// /// Gets or sets the format string used to display the key in the group. /// /// The format string. - public string FormatString { get; set; } + public string FormatString { get; set; } = string.Empty; /// /// Gets or sets a value indicating whether to show the footer for the group. diff --git a/Radzen.Blazor/GroupResult.cs b/Radzen.Blazor/GroupResult.cs index b7490a4b..055e925b 100644 --- a/Radzen.Blazor/GroupResult.cs +++ b/Radzen.Blazor/GroupResult.cs @@ -22,12 +22,12 @@ public class GroupResult /// /// The resulting elements in the group. /// - public IEnumerable Items { get; internal set; } + public IEnumerable? Items { get; internal set; } /// /// The resulting subgroups in the group. /// - public IEnumerable Subgroups { get; internal set; } + public IEnumerable? Subgroups { get; internal set; } /// /// Returns a showing the key of the group and the number of items in the group. diff --git a/Radzen.Blazor/GroupRowRenderEventArgs.cs b/Radzen.Blazor/GroupRowRenderEventArgs.cs index 121519d7..97aaeee9 100644 --- a/Radzen.Blazor/GroupRowRenderEventArgs.cs +++ b/Radzen.Blazor/GroupRowRenderEventArgs.cs @@ -15,7 +15,7 @@ public class GroupRowRenderEventArgs /// /// Gets the data item which the current row represents. /// - public Group Group { get; internal set; } + public Group? Group { get; internal set; } /// /// Gets or sets a value indicating whether this group row is expanded. diff --git a/Radzen.Blazor/HtmlEditorExecuteEventArgs.cs b/Radzen.Blazor/HtmlEditorExecuteEventArgs.cs index dfefc0fe..2cf1510a 100644 --- a/Radzen.Blazor/HtmlEditorExecuteEventArgs.cs +++ b/Radzen.Blazor/HtmlEditorExecuteEventArgs.cs @@ -24,6 +24,6 @@ public class HtmlEditorExecuteEventArgs /// /// Gets the name of the command which RadzenHtmlEditor is executing. /// - public string CommandName { get; set; } + public string? CommandName { get; set; } } diff --git a/Radzen.Blazor/HtmlEditorPasteEventArgs.cs b/Radzen.Blazor/HtmlEditorPasteEventArgs.cs index 92befdeb..3812aac6 100644 --- a/Radzen.Blazor/HtmlEditorPasteEventArgs.cs +++ b/Radzen.Blazor/HtmlEditorPasteEventArgs.cs @@ -9,6 +9,6 @@ public class HtmlEditorPasteEventArgs /// Gets or sets the HTML content that is pasted in RadzenHtmlEditor. Use the setter to filter unwanted markup from the pasted value. /// /// The HTML. - public string Html { get; set; } + public string? Html { get; set; } } diff --git a/Radzen.Blazor/HttpResponseMessageExtensions.cs b/Radzen.Blazor/HttpResponseMessageExtensions.cs index fe350286..4b7b633f 100644 --- a/Radzen.Blazor/HttpResponseMessageExtensions.cs +++ b/Radzen.Blazor/HttpResponseMessageExtensions.cs @@ -22,8 +22,9 @@ namespace Radzen /// /// Unable to parse the response. /// - public static async Task ReadAsync(this HttpResponseMessage response) + public static async Task ReadAsync(this HttpResponseMessage response) { + ArgumentNullException.ThrowIfNull(response); try { response.EnsureSuccessStatusCode(); @@ -42,7 +43,8 @@ namespace Radzen var responseAsString = await response.Content.ReadAsStringAsync(); if (!string.IsNullOrEmpty(responseAsString)) { - if (response.Content.Headers.ContentType.MediaType == "application/json") + var mediaType = response.Content.Headers.ContentType?.MediaType; + if (string.Equals(mediaType, "application/json", StringComparison.OrdinalIgnoreCase)) { JsonDocument json; try @@ -51,7 +53,7 @@ namespace Radzen } catch { - throw new Exception("Unable to parse the response."); + throw new InvalidOperationException("Unable to parse the response."); } JsonElement error; @@ -60,13 +62,14 @@ namespace Radzen JsonElement message; if (error.TryGetProperty("message", out message)) { - throw new Exception(message.GetString()); + var messageText = message.GetString(); + throw new InvalidOperationException(messageText ?? "An error occurred."); } } } else { - XElement error = null; + XElement? error = null; try { var xml = XDocument.Parse(responseAsString); @@ -82,12 +85,12 @@ namespace Radzen } catch { - throw new Exception("Unable to parse the response."); + throw new InvalidOperationException("Unable to parse the response."); } if (error != null) { - throw new Exception(error.Value); + throw new InvalidOperationException(error.Value); } } } diff --git a/Radzen.Blazor/IAIChatService.cs b/Radzen.Blazor/IAIChatService.cs index dbe9dfb0..7d43678b 100644 --- a/Radzen.Blazor/IAIChatService.cs +++ b/Radzen.Blazor/IAIChatService.cs @@ -23,14 +23,14 @@ public interface IAIChatService /// Optional API key to override the configured API key. /// Optional API key header name to override the configured header. /// An async enumerable that yields streaming response chunks from the AI model. - IAsyncEnumerable GetCompletionsAsync(string userInput, string sessionId = null, CancellationToken cancellationToken = default, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null, string endpoint = null, string proxy = null, string apiKey = null, string apiKeyHeader = null); + IAsyncEnumerable GetCompletionsAsync(string userInput, string? sessionId = null, CancellationToken cancellationToken = default, string? model = null, string? systemPrompt = null, double? temperature = null, int? maxTokens = null, string? endpoint = null, string? proxy = null, string? apiKey = null, string? apiKeyHeader = null); /// /// Gets or creates a conversation session. /// /// The session ID. If null, a new session will be created. /// The conversation session. - ConversationSession GetOrCreateSession(string sessionId = null); + ConversationSession GetOrCreateSession(string? sessionId = null); /// /// Clears the conversation history for a specific session. diff --git a/Radzen.Blazor/IRadzenFormComponent.cs b/Radzen.Blazor/IRadzenFormComponent.cs index 0540bb18..ec570dc5 100644 --- a/Radzen.Blazor/IRadzenFormComponent.cs +++ b/Radzen.Blazor/IRadzenFormComponent.cs @@ -25,13 +25,13 @@ public interface IRadzenFormComponent /// Gets the value of the component. /// /// the value of the component - for example the text of RadzenTextBox. - object GetValue(); + object? GetValue(); /// /// Gets or sets the name of the component. /// /// The name. - string Name { get; set; } + string? Name { get; set; } /// /// Gets the field identifier. @@ -58,6 +58,6 @@ public interface IRadzenFormComponent /// /// Sets the FormFieldContext of the component /// - IFormFieldContext FormFieldContext { get; } + IFormFieldContext? FormFieldContext { get; } } diff --git a/Radzen.Blazor/LegendClickEventArgs.cs b/Radzen.Blazor/LegendClickEventArgs.cs index e983b349..39e6cf8b 100644 --- a/Radzen.Blazor/LegendClickEventArgs.cs +++ b/Radzen.Blazor/LegendClickEventArgs.cs @@ -10,13 +10,13 @@ public class LegendClickEventArgs /// /// Gets the data at the clicked location. /// - public object Data { get; set; } + public object? Data { get; set; } /// /// Gets the title of the clicked series. Determined by . /// /// The title. - public string Title { get; set; } + public string? Title { get; set; } /// /// Gets the visibility of the clicked legend. Determined by . Always visible for Pie Charts. diff --git a/Radzen.Blazor/LinearScale.cs b/Radzen.Blazor/LinearScale.cs index 12233243..42a2c0ca 100644 --- a/Radzen.Blazor/LinearScale.cs +++ b/Radzen.Blazor/LinearScale.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Collections.Generic; @@ -20,10 +21,10 @@ namespace Radzen.Blazor if (String.IsNullOrEmpty(format)) { - return value.ToString(); + return value.ToString() ?? String.Empty; } - return string.Format(format, value); + return string.Format(CultureInfo.InvariantCulture, format, value); } public override double Scale(double value, bool padding) @@ -96,7 +97,7 @@ namespace Radzen.Blazor { if (Step is IConvertible) { - step = Convert.ToDouble(Step); + step = Convert.ToDouble(Step, CultureInfo.InvariantCulture); } } @@ -124,7 +125,7 @@ namespace Radzen.Blazor if (step == 0) { - throw new ArgumentOutOfRangeException("Step must be non-zero"); + throw new ArgumentOutOfRangeException(nameof(distance), "Step must be non-zero"); } return (start, end, Math.Abs(step)); diff --git a/Radzen.Blazor/ListBoxItemRenderEventArgs.cs b/Radzen.Blazor/ListBoxItemRenderEventArgs.cs index f751fd61..c0a92eb2 100644 --- a/Radzen.Blazor/ListBoxItemRenderEventArgs.cs +++ b/Radzen.Blazor/ListBoxItemRenderEventArgs.cs @@ -10,6 +10,6 @@ public class ListBoxItemRenderEventArgs : DropDownBaseItemRenderEventArg /// /// Gets the ListBox. /// - public RadzenListBox ListBox { get; internal set; } + public RadzenListBox? ListBox { get; internal set; } } diff --git a/Radzen.Blazor/LoadDataArgs.cs b/Radzen.Blazor/LoadDataArgs.cs index 3218789c..9b2ff7bc 100644 --- a/Radzen.Blazor/LoadDataArgs.cs +++ b/Radzen.Blazor/LoadDataArgs.cs @@ -23,11 +23,11 @@ public class LoadDataArgs /// /// Gets the sort expression as a string. /// - public string OrderBy { get; set; } + public string? OrderBy { get; set; } - private Func getFilter; + private Func? getFilter; - internal Func GetFilter + internal Func? GetFilter { get { @@ -40,19 +40,19 @@ public class LoadDataArgs } } - private string filter; + private string? filter; /// /// Gets the filter expression as a string. /// /// The filter. - public string Filter + public string? Filter { get { if (filter == null && GetFilter != null) { - filter = GetFilter(); + filter = GetFilter?.Invoke(); } return filter; } @@ -65,12 +65,12 @@ public class LoadDataArgs /// /// Gets the filter expression as a collection of filter descriptors. /// - public IEnumerable Filters { get; set; } + public IEnumerable? Filters { get; set; } /// /// Gets the sort expression as a collection of sort descriptors. /// /// The sorts. - public IEnumerable Sorts { get; set; } + public IEnumerable? Sorts { get; set; } } diff --git a/Radzen.Blazor/LoginArgs.cs b/Radzen.Blazor/LoginArgs.cs index e27e088a..dd0968f8 100644 --- a/Radzen.Blazor/LoginArgs.cs +++ b/Radzen.Blazor/LoginArgs.cs @@ -8,12 +8,12 @@ public class LoginArgs /// /// Gets or sets the username. /// - public string Username { get; set; } + public string? Username { get; set; } /// /// Gets or sets the password. /// - public string Password { get; set; } + public string? Password { get; set; } /// /// Gets or sets a value indicating whether the user wants to remember their credentials. diff --git a/Radzen.Blazor/MD5.cs b/Radzen.Blazor/MD5.cs index 201bef8c..c98b1733 100644 --- a/Radzen.Blazor/MD5.cs +++ b/Radzen.Blazor/MD5.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; namespace Radzen; @@ -59,6 +60,8 @@ public class MD5 /// The MD5 hash as a string. public static string Calculate(byte[] input) { + ArgumentNullException.ThrowIfNull(input); + uint a0 = 0x67452301; // A uint b0 = 0xefcdab89; // B uint c0 = 0x98badcfe; // C @@ -124,7 +127,7 @@ public class MD5 private static string GetByteString(uint x) { - return String.Join("", BitConverter.GetBytes(x).Select(y => y.ToString("x2"))); + return String.Join("", BitConverter.GetBytes(x).Select(y => y.ToString("x2", CultureInfo.InvariantCulture))); } } diff --git a/Radzen.Blazor/Markdown/BlazorMarkdownRenderer.cs b/Radzen.Blazor/Markdown/BlazorMarkdownRenderer.cs index 82efbc8c..2a1d365a 100644 --- a/Radzen.Blazor/Markdown/BlazorMarkdownRenderer.cs +++ b/Radzen.Blazor/Markdown/BlazorMarkdownRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; @@ -194,7 +195,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren /// public override void VisitLink(Link link) { - if (link.Destination.StartsWith("#")) + if (link.Destination?.StartsWith('#') == true) { builder.OpenComponent(0); builder.AddAttribute(0, "href", link.Destination); @@ -210,7 +211,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren { builder.OpenComponent(0); - if (!HtmlSanitizer.IsDangerousUrl(link.Destination)) + if (!string.IsNullOrEmpty(link.Destination) && !HtmlSanitizer.IsDangerousUrl(link.Destination)) { builder.AddAttribute(1, nameof(RadzenLink.Path), link.Destination); } @@ -230,7 +231,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren { builder.OpenComponent(0); - if (!HtmlSanitizer.IsDangerousUrl(image.Destination)) + if (!string.IsNullOrEmpty(image.Destination) && !HtmlSanitizer.IsDangerousUrl(image.Destination)) { builder.AddAttribute(1, nameof(RadzenImage.Path), image.Destination); } @@ -291,7 +292,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren if (match.Success) { - var markerId = Convert.ToInt32(match.Groups[1].Value); + var markerId = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); outlet(builder, markerId); } else if (options.AllowHtml) @@ -349,11 +350,12 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren /// public override void VisitHtmlInline(HtmlInline htmlInline) { + if (htmlInline.Value == null) return; var match = OutletRegex.Match(htmlInline.Value); if (match.Success) { - var markerId = Convert.ToInt32(match.Groups[1].Value); + var markerId = Convert.ToInt32(match.Groups[1].Value, CultureInfo.InvariantCulture); outlet(builder, markerId); return; } @@ -406,7 +408,7 @@ internal class BlazorMarkdownRenderer(BlazorMarkdownRendererOptions options, Ren } } - if (html.EndsWith("/>") || IsVoidElement(tagName)) + if (html.EndsWith("/>", StringComparison.Ordinal) || IsVoidElement(tagName)) { builder.CloseElement(); } diff --git a/Radzen.Blazor/Markdown/BlockContainer.cs b/Radzen.Blazor/Markdown/BlockContainer.cs index 898e92e9..ae92726e 100644 --- a/Radzen.Blazor/Markdown/BlockContainer.cs +++ b/Radzen.Blazor/Markdown/BlockContainer.cs @@ -1,4 +1,5 @@ +using System; using System.Collections.Generic; namespace Radzen.Blazor.Markdown; @@ -29,6 +30,7 @@ public abstract class BlockContainer : Block /// The added block. public virtual T Add(T block) where T : Block { + ArgumentNullException.ThrowIfNull(block); children.Add(block); block.Parent = this; @@ -43,6 +45,8 @@ public abstract class BlockContainer : Block /// The block to replace with. public void Replace(Block source, Block target) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(target); var index = children.IndexOf(source); if (index >= 0) diff --git a/Radzen.Blazor/Markdown/BlockQuote.cs b/Radzen.Blazor/Markdown/BlockQuote.cs index 9b3c0df8..6c3bb2d6 100644 --- a/Radzen.Blazor/Markdown/BlockQuote.cs +++ b/Radzen.Blazor/Markdown/BlockQuote.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; @@ -9,6 +11,7 @@ public class BlockQuote : BlockContainer /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitBlockQuote(this); } diff --git a/Radzen.Blazor/Markdown/Code.cs b/Radzen.Blazor/Markdown/Code.cs index 774cba1f..0f917d9c 100644 --- a/Radzen.Blazor/Markdown/Code.cs +++ b/Radzen.Blazor/Markdown/Code.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -14,6 +16,7 @@ public class Code(string value) : Inline /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitCode(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/Document.cs b/Radzen.Blazor/Markdown/Document.cs index 42b607dc..7b72be1a 100644 --- a/Radzen.Blazor/Markdown/Document.cs +++ b/Radzen.Blazor/Markdown/Document.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -17,6 +19,7 @@ public class Document : BlockContainer /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitDocument(this); } diff --git a/Radzen.Blazor/Markdown/Emphasis.cs b/Radzen.Blazor/Markdown/Emphasis.cs index 738edbba..8f0eec8c 100644 --- a/Radzen.Blazor/Markdown/Emphasis.cs +++ b/Radzen.Blazor/Markdown/Emphasis.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,6 +10,7 @@ public class Emphasis : InlineContainer /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitEmphasis(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/FencedCodeBlock.cs b/Radzen.Blazor/Markdown/FencedCodeBlock.cs index 972d61a7..97ebfefa 100644 --- a/Radzen.Blazor/Markdown/FencedCodeBlock.cs +++ b/Radzen.Blazor/Markdown/FencedCodeBlock.cs @@ -1,3 +1,4 @@ +using System; using System.Text.RegularExpressions; namespace Radzen.Blazor.Markdown; @@ -10,17 +11,18 @@ public class FencedCodeBlock : Leaf /// /// The delimiter used to start and end the code block. /// - public string Delimiter { get; private set; } + public string? Delimiter { get; private set; } internal int Indent { get; private set; } /// /// The info string of the code block. This is the first line of the code block and is used to specify the language of the code block. /// - public string Info { get; private set; } + public string? Info { get; private set; } /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitFencedCodeBlock(this); } @@ -29,7 +31,7 @@ public class FencedCodeBlock : Leaf base.Close(parser); // first line becomes info string - var newlinePos = Value.IndexOf('\n'); + var newlinePos = Value.IndexOf('\n', StringComparison.Ordinal); var firstLine = Value[..newlinePos]; Info = firstLine.Trim(); Value = Value[(newlinePos + 1)..]; @@ -43,7 +45,7 @@ public class FencedCodeBlock : Leaf var match = ClosingFenceRegex.Match(line); - if (indent <= 3 && parser.PeekNonSpace() == Delimiter[0] && match.Success && match.Length >= Delimiter.Length) + if (indent <= 3 && Delimiter != null && parser.PeekNonSpace() == Delimiter[0] && match.Success && match.Length >= Delimiter.Length) { // closing fence - we're at end of line, so we can return parser.LastLineLength = parser.Offset + indent + match.Length; diff --git a/Radzen.Blazor/Markdown/Heading.cs b/Radzen.Blazor/Markdown/Heading.cs index 5bd53e7e..86a24721 100644 --- a/Radzen.Blazor/Markdown/Heading.cs +++ b/Radzen.Blazor/Markdown/Heading.cs @@ -1,4 +1,6 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -14,6 +16,7 @@ public abstract class Heading : Leaf /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitHeading(this); } diff --git a/Radzen.Blazor/Markdown/HtmlBlock.cs b/Radzen.Blazor/Markdown/HtmlBlock.cs index 15ae65a5..a1346fb9 100644 --- a/Radzen.Blazor/Markdown/HtmlBlock.cs +++ b/Radzen.Blazor/Markdown/HtmlBlock.cs @@ -1,3 +1,4 @@ +using System; using System.Text.RegularExpressions; namespace Radzen.Blazor.Markdown; @@ -12,6 +13,7 @@ public class HtmlBlock : Leaf /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitHtmlBlock(this); } diff --git a/Radzen.Blazor/Markdown/HtmlInline.cs b/Radzen.Blazor/Markdown/HtmlInline.cs index 9e0bf66f..2541cc41 100644 --- a/Radzen.Blazor/Markdown/HtmlInline.cs +++ b/Radzen.Blazor/Markdown/HtmlInline.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,12 +10,13 @@ public class HtmlInline : Inline /// /// Gets or sets the HTML element value. /// - public string Value { get; set; } + public string? Value { get; set; } /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitHtmlInline(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/HtmlSanitizer.cs b/Radzen.Blazor/Markdown/HtmlSanitizer.cs index 68980c85..8a3822db 100644 --- a/Radzen.Blazor/Markdown/HtmlSanitizer.cs +++ b/Radzen.Blazor/Markdown/HtmlSanitizer.cs @@ -1,4 +1,5 @@ +using System; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -78,7 +79,7 @@ class HtmlSanitizer var safeAttributes = Regex.Replace(attributes, @"(\w+)\s*=\s*(""[^""]*""|'[^']*'|[^\s>]+)", SanitizeAttribute); - return $"<{(match.Value.StartsWith(""; + return $"<{(match.Value.StartsWith(""; } private string SanitizeAttribute(Match match) @@ -128,9 +129,9 @@ class HtmlSanitizer var decoded = HtmlDecode(value).Trim().ToLowerInvariant(); - return decoded.StartsWith("javascript:") || - decoded.StartsWith("vbscript:") || - decoded.StartsWith("data:text/html") || - decoded.Contains("expression("); + return decoded.StartsWith("javascript:", StringComparison.Ordinal) || + decoded.StartsWith("vbscript:", StringComparison.Ordinal) || + decoded.StartsWith("data:text/html", StringComparison.Ordinal) || + decoded.Contains("expression(", StringComparison.Ordinal); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/Image.cs b/Radzen.Blazor/Markdown/Image.cs index cd4983c5..4937dc66 100644 --- a/Radzen.Blazor/Markdown/Image.cs +++ b/Radzen.Blazor/Markdown/Image.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,16 +10,17 @@ public class Image : InlineContainer /// /// Gets or sets the destination (URL) of the image. /// - public string Destination { get; set; } + public string? Destination { get; set; } /// /// Gets or sets the alternative text of the image. /// - public string Title { get; set; } + public string? Title { get; set; } /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitImage(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/IndentedCodeBlock.cs b/Radzen.Blazor/Markdown/IndentedCodeBlock.cs index cf93a80c..6c1c64e3 100644 --- a/Radzen.Blazor/Markdown/IndentedCodeBlock.cs +++ b/Radzen.Blazor/Markdown/IndentedCodeBlock.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Text.RegularExpressions; @@ -11,6 +12,7 @@ public class IndentedCodeBlock : Leaf /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitIndentedCodeBlock(this); } diff --git a/Radzen.Blazor/Markdown/InlineParser.cs b/Radzen.Blazor/Markdown/InlineParser.cs index ba426840..4b92b11d 100644 --- a/Radzen.Blazor/Markdown/InlineParser.cs +++ b/Radzen.Blazor/Markdown/InlineParser.cs @@ -12,7 +12,7 @@ class InlineParser public char Char { get; set; } public int Length { get; set; } public int Position { get; set; } - public Text Node { get; set; } + public Text? Node { get; set; } public bool CanOpen { get; set; } public bool CanClose { get; set; } public bool Active { get; set; } = true; @@ -296,7 +296,7 @@ class InlineParser var url = destination.ToString(); - if (url.Contains(Space)) + if (url.Contains(Space, StringComparison.Ordinal)) { return false; } @@ -778,7 +778,7 @@ class InlineParser private void ReplaceOpener(int openerIndex, InlineContainer parent) { - var startIndex = inlines.FindIndex(delimiters[openerIndex].Node.Equals); + var startIndex = delimiters[openerIndex].Node != null ? inlines.FindIndex(node => node.Equals(delimiters[openerIndex].Node)) : -1; ParseEmphasisAndStrong(openerIndex); @@ -815,7 +815,7 @@ class InlineParser AddTextNode(); - var startIndex = inlines.FindIndex(delimiters[openerIndex].Node.Equals); + var startIndex = delimiters[openerIndex].Node != null ? inlines.FindIndex(node => node.Equals(delimiters[openerIndex].Node)) : -1; var endIndex = inlines.Count - startIndex; @@ -878,8 +878,8 @@ class InlineParser { var closer = delimiters[closerIndex]; var opener = delimiters[openerIndex]; - var startIndex = inlines.FindIndex(opener.Node.Equals); - var endIndex = inlines.FindIndex(closer.Node.Equals); + var startIndex = opener.Node != null ? inlines.FindIndex(opener.Node.Equals) : -1; + var endIndex = closer.Node != null ? inlines.FindIndex(closer.Node.Equals) : -1; if (startIndex >= 0 && endIndex >= 0) { @@ -896,7 +896,7 @@ class InlineParser opener.Length -= charsToConsume; - if (opener.Length > 0) + if (opener.Length > 0 && opener.Node != null) { opener.Node.Value = opener.Node.Value[..^charsToConsume]; startIndex += 1; @@ -904,7 +904,7 @@ class InlineParser closer.Length -= charsToConsume; - if (closer.Length > 0) + if (closer.Length > 0 && closer.Node != null) { closer.Node.Value = closer.Node.Value[..^charsToConsume]; endIndex -= 1; diff --git a/Radzen.Blazor/Markdown/LineBreak.cs b/Radzen.Blazor/Markdown/LineBreak.cs index 67673be5..1aa70f77 100644 --- a/Radzen.Blazor/Markdown/LineBreak.cs +++ b/Radzen.Blazor/Markdown/LineBreak.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,6 +10,7 @@ public class LineBreak : Inline /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitLineBreak(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/Link.cs b/Radzen.Blazor/Markdown/Link.cs index 88cc9ead..fcead34d 100644 --- a/Radzen.Blazor/Markdown/Link.cs +++ b/Radzen.Blazor/Markdown/Link.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,16 +10,17 @@ public class Link : InlineContainer /// /// Gets or sets the destination (URL) of the link. /// - public string Destination { get; set; } + public string? Destination { get; set; } /// /// Gets or sets the link title. /// - public string Title { get; set; } + public string? Title { get; set; } /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitLink(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/LinkReference.cs b/Radzen.Blazor/Markdown/LinkReference.cs index f7bc93bd..46e292ca 100644 --- a/Radzen.Blazor/Markdown/LinkReference.cs +++ b/Radzen.Blazor/Markdown/LinkReference.cs @@ -2,6 +2,6 @@ namespace Radzen.Blazor.Markdown; class LinkReference { - public string Destination { get; set; } - public string Title { get; set; } + public string? Destination { get; set; } + public string? Title { get; set; } } diff --git a/Radzen.Blazor/Markdown/ListBase.cs b/Radzen.Blazor/Markdown/ListBase.cs index 07039a7f..709a4e5b 100644 --- a/Radzen.Blazor/Markdown/ListBase.cs +++ b/Radzen.Blazor/Markdown/ListBase.cs @@ -17,7 +17,7 @@ public abstract class List : BlockContainer /// public bool Tight { get; set; } = true; internal int Padding { get; set; } - internal string Delimiter { get; set; } + internal string? Delimiter { get; set; } internal override BlockMatch Matches(BlockParser parser) { diff --git a/Radzen.Blazor/Markdown/ListItem.cs b/Radzen.Blazor/Markdown/ListItem.cs index 7c07a4c1..e5984496 100644 --- a/Radzen.Blazor/Markdown/ListItem.cs +++ b/Radzen.Blazor/Markdown/ListItem.cs @@ -1,3 +1,5 @@ +using System; +using System.Globalization; using System.Text.RegularExpressions; namespace Radzen.Blazor.Markdown; @@ -12,6 +14,7 @@ public class ListItem : BlockContainer /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitListItem(this); } @@ -126,7 +129,7 @@ public class ListItem : BlockContainer var list = new OrderedList { MarkerOffset = parser.Indent, - Start = int.Parse(match.Groups[1].Value), + Start = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture), Delimiter = match.Groups[2].Value }; data = list; diff --git a/Radzen.Blazor/Markdown/NodeVisitorBase.cs b/Radzen.Blazor/Markdown/NodeVisitorBase.cs index 5d7df187..1e849d4c 100644 --- a/Radzen.Blazor/Markdown/NodeVisitorBase.cs +++ b/Radzen.Blazor/Markdown/NodeVisitorBase.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Radzen.Blazor.Markdown; @@ -11,32 +12,56 @@ public abstract class NodeVisitorBase : INodeVisitor /// /// Visits a block quote by visiting its children. /// - public virtual void VisitBlockQuote(BlockQuote blockQuote) => VisitChildren(blockQuote.Children); + public virtual void VisitBlockQuote(BlockQuote blockQuote) + { + ArgumentNullException.ThrowIfNull(blockQuote); + VisitChildren(blockQuote.Children); + } /// /// Visits a document by visiting its children. /// - public virtual void VisitDocument(Document document) => VisitChildren(document.Children); + public virtual void VisitDocument(Document document) + { + ArgumentNullException.ThrowIfNull(document); + VisitChildren(document.Children); + } /// /// Visits a heading by visiting its children. /// - public virtual void VisitHeading(Heading heading) => VisitChildren(heading.Children); + public virtual void VisitHeading(Heading heading) + { + ArgumentNullException.ThrowIfNull(heading); + VisitChildren(heading.Children); + } /// /// Visits a list item by visiting its children. /// - public virtual void VisitListItem(ListItem listItem) => VisitChildren(listItem.Children); + public virtual void VisitListItem(ListItem listItem) + { + ArgumentNullException.ThrowIfNull(listItem); + VisitChildren(listItem.Children); + } /// /// Visits an ordered list by visiting its children. /// - public virtual void VisitOrderedList(OrderedList orderedList) => VisitChildren(orderedList.Children); + public virtual void VisitOrderedList(OrderedList orderedList) + { + ArgumentNullException.ThrowIfNull(orderedList); + VisitChildren(orderedList.Children); + } /// /// Visits a paragraph by visiting its children. /// - public virtual void VisitParagraph(Paragraph paragraph) => VisitChildren(paragraph.Children); + public virtual void VisitParagraph(Paragraph paragraph) + { + ArgumentNullException.ThrowIfNull(paragraph); + VisitChildren(paragraph.Children); + } /// /// Visits a thematic break. @@ -83,27 +108,47 @@ public abstract class NodeVisitorBase : INodeVisitor /// /// Visits an ordered list by visiting its children. /// - public virtual void VisitUnorderedList(UnorderedList unorderedList) => VisitChildren(unorderedList.Children); + public virtual void VisitUnorderedList(UnorderedList unorderedList) + { + ArgumentNullException.ThrowIfNull(unorderedList); + VisitChildren(unorderedList.Children); + } /// /// Visits an emphasis by visiting its children. /// - public virtual void VisitEmphasis(Emphasis emphasis) => VisitChildren(emphasis.Children); + public virtual void VisitEmphasis(Emphasis emphasis) + { + ArgumentNullException.ThrowIfNull(emphasis); + VisitChildren(emphasis.Children); + } /// /// Visits a strong by visiting its children. /// - public virtual void VisitStrong(Strong strong) => VisitChildren(strong.Children); + public virtual void VisitStrong(Strong strong) + { + ArgumentNullException.ThrowIfNull(strong); + VisitChildren(strong.Children); + } /// /// Visits a link by visiting its children. /// - public virtual void VisitLink(Link link) => VisitChildren(link.Children); + public virtual void VisitLink(Link link) + { + ArgumentNullException.ThrowIfNull(link); + VisitChildren(link.Children); + } /// /// Visits an image by visiting its children. /// - public virtual void VisitImage(Image image) => VisitChildren(image.Children); + public virtual void VisitImage(Image image) + { + ArgumentNullException.ThrowIfNull(image); + VisitChildren(image.Children); + } /// /// Visits a code block. @@ -129,28 +174,45 @@ public abstract class NodeVisitorBase : INodeVisitor /// /// Visits a table. /// - public virtual void VisitTable(Table table) => VisitChildren(table.Rows); + public virtual void VisitTable(Table table) + { + ArgumentNullException.ThrowIfNull(table); + VisitChildren(table.Rows); + } /// /// Visits a table header row by visiting its children. /// - public virtual void VisitTableHeaderRow(TableHeaderRow header) => VisitChildren(header.Cells); + public virtual void VisitTableHeaderRow(TableHeaderRow header) + { + ArgumentNullException.ThrowIfNull(header); + VisitChildren(header.Cells); + } /// /// Visits a table row by visiting its children. /// - public virtual void VisitTableRow(TableRow row) => VisitChildren(row.Cells); + public virtual void VisitTableRow(TableRow row) + { + ArgumentNullException.ThrowIfNull(row); + VisitChildren(row.Cells); + } /// /// Visits a table cell by visiting its children. /// - public virtual void VisitTableCell(TableCell cell) => VisitChildren(cell.Children); + public virtual void VisitTableCell(TableCell cell) + { + ArgumentNullException.ThrowIfNull(cell); + VisitChildren(cell.Children); + } /// /// Visits a collection of nodes. /// protected void VisitChildren(IEnumerable children) { + ArgumentNullException.ThrowIfNull(children); foreach (var node in children) { node.Accept(this); diff --git a/Radzen.Blazor/Markdown/OrderedList.cs b/Radzen.Blazor/Markdown/OrderedList.cs index 128a5843..ce0b8656 100644 --- a/Radzen.Blazor/Markdown/OrderedList.cs +++ b/Radzen.Blazor/Markdown/OrderedList.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,6 +10,7 @@ public class OrderedList : List /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitOrderedList(this); } diff --git a/Radzen.Blazor/Markdown/Paragraph.cs b/Radzen.Blazor/Markdown/Paragraph.cs index bb7e0123..888962e5 100644 --- a/Radzen.Blazor/Markdown/Paragraph.cs +++ b/Radzen.Blazor/Markdown/Paragraph.cs @@ -1,4 +1,6 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -9,6 +11,7 @@ public class Paragraph : Leaf /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitParagraph(this); } diff --git a/Radzen.Blazor/Markdown/SoftLineBreak.cs b/Radzen.Blazor/Markdown/SoftLineBreak.cs index be5601f7..86c214ba 100644 --- a/Radzen.Blazor/Markdown/SoftLineBreak.cs +++ b/Radzen.Blazor/Markdown/SoftLineBreak.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,6 +10,7 @@ public class SoftLineBreak : Inline /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitSoftLineBreak(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/Strong.cs b/Radzen.Blazor/Markdown/Strong.cs index f3b78f88..50014613 100644 --- a/Radzen.Blazor/Markdown/Strong.cs +++ b/Radzen.Blazor/Markdown/Strong.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,6 +10,7 @@ public class Strong : InlineContainer /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitStrong(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/Table.cs b/Radzen.Blazor/Markdown/Table.cs index c0c16f69..ef2cdd54 100644 --- a/Radzen.Blazor/Markdown/Table.cs +++ b/Radzen.Blazor/Markdown/Table.cs @@ -19,6 +19,7 @@ public class Table : Leaf /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitTable(this); } @@ -137,7 +138,7 @@ public class Table : Leaf // Check if the line contains a pipe character to be more specific about table delimiters // This helps avoid misinterpreting heading delimiters as table delimiters - if (!line.Contains('|') && !line.Contains(':')) + if (!line.Contains('|', StringComparison.Ordinal) && !line.Contains(':', StringComparison.Ordinal)) { return BlockStart.Skip; } diff --git a/Radzen.Blazor/Markdown/TableCell.cs b/Radzen.Blazor/Markdown/TableCell.cs index ce6ce4c7..052e6f2d 100644 --- a/Radzen.Blazor/Markdown/TableCell.cs +++ b/Radzen.Blazor/Markdown/TableCell.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Radzen.Blazor.Markdown; @@ -47,6 +48,7 @@ public class TableCell : INode, IBlockInlineContainer /// public void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitTableCell(this); } } diff --git a/Radzen.Blazor/Markdown/TableHeaderRow.cs b/Radzen.Blazor/Markdown/TableHeaderRow.cs index d27ae752..33ce51ae 100644 --- a/Radzen.Blazor/Markdown/TableHeaderRow.cs +++ b/Radzen.Blazor/Markdown/TableHeaderRow.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,6 +10,7 @@ public class TableHeaderRow : TableRow /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitTableHeaderRow(this); } } diff --git a/Radzen.Blazor/Markdown/TableRow.cs b/Radzen.Blazor/Markdown/TableRow.cs index 7ca0dd08..fc58ba2b 100644 --- a/Radzen.Blazor/Markdown/TableRow.cs +++ b/Radzen.Blazor/Markdown/TableRow.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Radzen.Blazor.Markdown; @@ -28,6 +29,7 @@ public class TableRow : INode /// public virtual void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitTableRow(this); } } diff --git a/Radzen.Blazor/Markdown/Text.cs b/Radzen.Blazor/Markdown/Text.cs index d1f1f589..d9afa90b 100644 --- a/Radzen.Blazor/Markdown/Text.cs +++ b/Radzen.Blazor/Markdown/Text.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -13,6 +15,7 @@ public class Text(string value) : Inline /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitText(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/Markdown/ThematicBreak.cs b/Radzen.Blazor/Markdown/ThematicBreak.cs index d951b52f..4cad93e9 100644 --- a/Radzen.Blazor/Markdown/ThematicBreak.cs +++ b/Radzen.Blazor/Markdown/ThematicBreak.cs @@ -1,3 +1,4 @@ +using System; using System.Text.RegularExpressions; namespace Radzen.Blazor.Markdown; @@ -12,6 +13,7 @@ public class ThematicBreak : Block /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitThematicBreak(this); } diff --git a/Radzen.Blazor/Markdown/UnorderedList.cs b/Radzen.Blazor/Markdown/UnorderedList.cs index e6d342db..db205b95 100644 --- a/Radzen.Blazor/Markdown/UnorderedList.cs +++ b/Radzen.Blazor/Markdown/UnorderedList.cs @@ -1,3 +1,5 @@ +using System; + namespace Radzen.Blazor.Markdown; /// @@ -8,6 +10,7 @@ public class UnorderedList : List /// public override void Accept(INodeVisitor visitor) { + ArgumentNullException.ThrowIfNull(visitor); visitor.VisitUnorderedList(this); } } \ No newline at end of file diff --git a/Radzen.Blazor/MenuItemEventArgs.cs b/Radzen.Blazor/MenuItemEventArgs.cs index ab3038c5..6cc5d770 100644 --- a/Radzen.Blazor/MenuItemEventArgs.cs +++ b/Radzen.Blazor/MenuItemEventArgs.cs @@ -10,16 +10,16 @@ public class MenuItemEventArgs : MouseEventArgs /// /// Gets text of the clicked item. /// - public string Text { get; internal set; } + public string? Text { get; internal set; } /// /// Gets the value of the clicked item. /// - public object Value { get; internal set; } + public object? Value { get; internal set; } /// /// Gets the path path of the clicked item. /// - public string Path { get; internal set; } + public string? Path { get; internal set; } } diff --git a/Radzen.Blazor/NonRenderingExtensions.cs b/Radzen.Blazor/NonRenderingExtensions.cs index 25d1c442..09d0f0be 100644 --- a/Radzen.Blazor/NonRenderingExtensions.cs +++ b/Radzen.Blazor/NonRenderingExtensions.cs @@ -34,6 +34,6 @@ static class NonRenderingExtensions private record ReceiverBase : IHandleEvent { - public Task HandleEventAsync(EventCallbackWorkItem item, object arg) => item.InvokeAsync(arg); + public Task HandleEventAsync(EventCallbackWorkItem item, object? arg) => item.InvokeAsync(arg); } } \ No newline at end of file diff --git a/Radzen.Blazor/NotificationService.cs b/Radzen.Blazor/NotificationService.cs index 22747d6d..2be85fec 100644 --- a/Radzen.Blazor/NotificationService.cs +++ b/Radzen.Blazor/NotificationService.cs @@ -31,10 +31,7 @@ namespace Radzen /// The message. public void Notify(NotificationMessage message) { - if (message is null) - { - throw new ArgumentNullException(nameof(message)); - } + ArgumentNullException.ThrowIfNull(message); if (!Messages.Contains(message)) { @@ -51,7 +48,7 @@ namespace Radzen /// The duration. /// The click event. public void Notify(NotificationSeverity severity, string summary, - string detail, TimeSpan duration, Action click = null) + string detail, TimeSpan duration, Action? click = null) { Notify(severity, summary, detail, duration.TotalMilliseconds, click); } @@ -67,7 +64,7 @@ namespace Radzen /// If true, then the notification will be closed when clicked on. /// Used to store a custom payload that can be retreived later in the click event handler. /// Action to be executed on close. - public void Notify(NotificationSeverity severity = NotificationSeverity.Info, string summary = "", string detail = "", double duration = 3000, Action click = null, bool closeOnClick = false, object payload = null, Action close = null) + public void Notify(NotificationSeverity severity = NotificationSeverity.Info, string summary = "", string detail = "", double duration = 3000, Action? click = null, bool closeOnClick = false, object? payload = null, Action? close = null) { var newMessage = new NotificationMessage() { @@ -122,11 +119,11 @@ namespace Radzen /// Gets or sets the click event. /// /// This event handler is called when the notification is clicked on. - public Action Click { get; set; } + public Action? Click { get; set; } /// /// Get or set the event for when the notification is closed /// - public Action Close { get; set; } + public Action? Close { get; set; } /// /// Gets or sets click on close action. /// @@ -136,7 +133,7 @@ namespace Radzen /// Gets or sets notification payload. /// /// Used to store a custom payload that can be retreived later in the click event handler. - public object Payload { get; set; } + public object? Payload { get; set; } /// /// Gets or sets if progress should be shown during duration. @@ -148,12 +145,12 @@ namespace Radzen /// Gets or sets the detail content. /// /// The detail content. - public RenderFragment DetailContent { get; set; } + public RenderFragment? DetailContent { get; set; } /// /// Gets or sets the summary content. /// /// The summary content. - public RenderFragment SummaryContent { get; set; } + public RenderFragment? SummaryContent { get; set; } #region Implementation of IEquatable and operators overloading @@ -163,7 +160,7 @@ namespace Radzen /// /// /// - public bool Equals(NotificationMessage other) + public bool Equals(NotificationMessage? other) { if(other == null) return false; @@ -187,7 +184,7 @@ namespace Radzen /// /// /// - public override bool Equals(object obj) => Equals(obj as NotificationMessage); + public override bool Equals(object? obj) => Equals(obj as NotificationMessage); /// /// Return a hash code for the current object @@ -201,7 +198,7 @@ namespace Radzen /// /// /// - public static bool operator ==(NotificationMessage message, NotificationMessage otherMessage) + public static bool operator ==(NotificationMessage? message, NotificationMessage? otherMessage) { if (message is null) { @@ -222,7 +219,7 @@ namespace Radzen /// /// /// - public static bool operator !=(NotificationMessage message, NotificationMessage otherMessage) + public static bool operator !=(NotificationMessage? message, NotificationMessage? otherMessage) { return !(message == otherMessage); } diff --git a/Radzen.Blazor/OData.cs b/Radzen.Blazor/OData.cs index b70bf926..1220d4c3 100644 --- a/Radzen.Blazor/OData.cs +++ b/Radzen.Blazor/OData.cs @@ -66,7 +66,7 @@ namespace Radzen /// Gets or sets the value. /// /// The value. - public IEnumerable Value { get; set; } + public IEnumerable? Value { get; set; } } /// @@ -81,9 +81,16 @@ namespace Radzen /// true if the specified type is complex; otherwise, false. static bool IsComplex(Type type) { + ArgumentNullException.ThrowIfNull(type); + var underlyingType = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(type) : type; - var baseType = underlyingType.IsGenericType ? underlyingType.GetGenericArguments().FirstOrDefault() : underlyingType; + var baseType = underlyingType != null && underlyingType.IsGenericType ? underlyingType.GetGenericArguments().FirstOrDefault() ?? underlyingType : underlyingType; + + if (baseType == null) + { + return false; + } return !baseType.IsPrimitive && !typeof(IEnumerable<>).IsAssignableFrom(baseType) && type != typeof(string) && type != typeof(decimal) && type.IsClass; @@ -96,6 +103,8 @@ namespace Radzen /// true if the specified type is enumerable; otherwise, false. static bool IsEnumerable(Type type) { + ArgumentNullException.ThrowIfNull(type); + return !typeof(string).IsAssignableFrom(type) && (typeof(IEnumerable<>).IsAssignableFrom(type) || typeof(IEnumerable).IsAssignableFrom(type)); } @@ -106,7 +115,7 @@ namespace Radzen /// The value. /// The options. /// System.String. - public static string Serialize(TValue value, JsonSerializerOptions options = null) + public static string Serialize(TValue value, JsonSerializerOptions? options = null) { if (options == null) { @@ -154,7 +163,7 @@ namespace Radzen /// T. public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return JsonSerializer.Deserialize(ref reader, options); + return JsonSerializer.Deserialize(ref reader, options)!; } /// @@ -165,6 +174,7 @@ namespace Radzen /// The options. public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { + ArgumentNullException.ThrowIfNull(writer); writer.WriteStartObject(); var valueOptions = new JsonSerializerOptions(); @@ -201,7 +211,7 @@ namespace Radzen /// DateTime. public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return DateTime.Parse(reader.GetString()); + return DateTime.Parse(reader.GetString() ?? string.Empty, CultureInfo.InvariantCulture); } /// @@ -212,6 +222,7 @@ namespace Radzen /// The options. public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { + ArgumentNullException.ThrowIfNull(writer); writer.WriteStringValue(value.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture)); } } @@ -234,14 +245,14 @@ namespace Radzen /// The apply. /// if set to true [count]. /// Uri. - public static Uri GetODataUri(this Uri uri, string filter = null, int? top = null, int? skip = null, string orderby = null, string expand = null, string select = null, string apply = null, bool? count = null) + public static Uri GetODataUri(this Uri uri, string? filter = null, int? top = null, int? skip = null, string? orderby = null, string? expand = null, string? select = null, string? apply = null, bool? count = null) { var uriBuilder = new UriBuilder(uri); var queryString = HttpUtility.ParseQueryString(uriBuilder.Query); if (!string.IsNullOrEmpty(filter)) { - queryString["$filter"] = $"{filter.Replace("\"", "'")}"; + queryString["$filter"] = $"{filter.Replace("\"", "'", StringComparison.Ordinal)}"; } if (top != null) @@ -276,7 +287,7 @@ namespace Radzen if (count != null) { - queryString["$count"] = $"{count}".ToLower(); + queryString["$count"] = $"{count}".ToLower(CultureInfo.InvariantCulture); } uriBuilder.Query = queryString.ToString(); diff --git a/Radzen.Blazor/OrdinalScale.cs b/Radzen.Blazor/OrdinalScale.cs index 88b700b5..a0a9c025 100644 --- a/Radzen.Blazor/OrdinalScale.cs +++ b/Radzen.Blazor/OrdinalScale.cs @@ -6,17 +6,17 @@ namespace Radzen.Blazor { internal class OrdinalScale : LinearScale { - public IList Data { get; set; } + public IList? Data { get; set; } public override object Value(double value) { - return Data.ElementAtOrDefault(Convert.ToInt32(value)); + return Data?.ElementAtOrDefault(Convert.ToInt32(value)) ?? default(int); } public override (double Start, double End, double Step) Ticks(int distance) { var start = -1; - var end = Data.Count; + var end = Data?.Count ?? 0; var step = 1; return (start, end, step); diff --git a/Radzen.Blazor/PagedDataBoundComponent.cs b/Radzen.Blazor/PagedDataBoundComponent.cs index 4b62d2fe..16cdcc4d 100644 --- a/Radzen.Blazor/PagedDataBoundComponent.cs +++ b/Radzen.Blazor/PagedDataBoundComponent.cs @@ -104,26 +104,26 @@ namespace Radzen /// /// The template. [Parameter] - public RenderFragment Template { get; set; } + public RenderFragment? Template { get; set; } /// /// Gets or sets the loading template. /// /// The loading template. [Parameter] - public RenderFragment LoadingTemplate { get; set; } + public RenderFragment? LoadingTemplate { get; set; } /// /// The data /// - IEnumerable _data; + IEnumerable? _data; /// /// Gets or sets the data. /// /// The data. [Parameter] - public IEnumerable Data + public IEnumerable? Data { get { @@ -149,7 +149,7 @@ namespace Radzen /// /// Called when INotifyCollectionChanged CollectionChanged is raised. /// - protected virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + protected virtual void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) { } @@ -163,6 +163,11 @@ namespace Radzen { ((INotifyCollectionChanged)_data).CollectionChanged -= OnCollectionChanged; } + + topPager?.Dispose(); + bottomPager?.Dispose(); + + GC.SuppressFinalize(this); } /// @@ -170,14 +175,14 @@ namespace Radzen /// /// The page size options. [Parameter] - public IEnumerable PageSizeOptions { get; set; } + public IEnumerable? PageSizeOptions { get; set; } /// /// Gets or sets the page size description text. /// /// The page size description text. [Parameter] - public string PageSizeText { get; set; } = "items per page"; + public string? PageSizeText { get; set; } = "items per page"; /// /// Gets or sets the pager summary visibility. @@ -191,7 +196,7 @@ namespace Radzen /// /// The pager summary format. [Parameter] - public string PagingSummaryFormat { get; set; } = "Page {0} of {1} ({2} items)"; + public string? PagingSummaryFormat { get; set; } = "Page {0} of {1} ({2} items)"; #nullable enable /// @@ -205,75 +210,75 @@ namespace Radzen /// Gets or sets the pager's first page button's title attribute. /// [Parameter] - public string FirstPageTitle { get; set; } = "First page."; + public string? FirstPageTitle { get; set; } = "First page."; /// /// Gets or sets the pager's first page button's aria-label attribute. /// [Parameter] - public string FirstPageAriaLabel { get; set; } = "Go to first page."; + public string? FirstPageAriaLabel { get; set; } = "Go to first page."; /// /// Gets or sets the pager's optional previous page button's label text. /// [Parameter] - public string PrevPageLabel { get; set; } + public string? PrevPageLabel { get; set; } /// /// Gets or sets the pager's previous page button's title attribute. /// [Parameter] - public string PrevPageTitle { get; set; } = "Previous page"; + public string? PrevPageTitle { get; set; } = "Previous page"; /// /// Gets or sets the pager's previous page button's aria-label attribute. /// [Parameter] - public string PrevPageAriaLabel { get; set; } = "Go to previous page."; + public string? PrevPageAriaLabel { get; set; } = "Go to previous page."; /// /// Gets or sets the pager's last page button's title attribute. /// [Parameter] - public string LastPageTitle { get; set; } = "Last page"; + public string? LastPageTitle { get; set; } = "Last page"; /// /// Gets or sets the pager's last page button's aria-label attribute. /// [Parameter] - public string LastPageAriaLabel { get; set; } = "Go to last page."; + public string? LastPageAriaLabel { get; set; } = "Go to last page."; /// /// Gets or sets the pager's optional next page button's label text. /// [Parameter] - public string NextPageLabel { get; set; } + public string? NextPageLabel { get; set; } /// /// Gets or sets the pager's next page button's title attribute. /// [Parameter] - public string NextPageTitle { get; set; } = "Next page"; + public string? NextPageTitle { get; set; } = "Next page"; /// /// Gets or sets the pager's next page button's aria-label attribute. /// [Parameter] - public string NextPageAriaLabel { get; set; } = "Go to next page."; + public string? NextPageAriaLabel { get; set; } = "Go to next page."; /// /// Gets or sets the pager's numeric page number buttons' title attributes. /// [Parameter] - public string PageTitleFormat { get; set; } = "Page {0}"; + public string? PageTitleFormat { get; set; } = "Page {0}"; /// /// Gets or sets the pager's numeric page number buttons' aria-label attributes. /// [Parameter] - public string PageAriaLabelFormat { get; set; } = "Go to page {0}."; + public string? PageAriaLabelFormat { get; set; } = "Go to page {0}."; - internal IQueryable _view = null; + internal IQueryable? _view; /// /// Gets the paged view. /// @@ -424,11 +429,11 @@ namespace Radzen /// /// The top pager /// - protected RadzenPager topPager; + protected RadzenPager topPager = default!; /// /// The bottom pager /// - protected RadzenPager bottomPager; + protected RadzenPager bottomPager = default!; /// /// Gets or sets the page callback. @@ -438,11 +443,12 @@ namespace Radzen public EventCallback Page { get; set; } /// - /// Handles the event. + /// Handles the page changed event. /// /// The instance containing the event data. protected async Task OnPageChanged(PagerEventArgs args) { + ArgumentNullException.ThrowIfNull(args); skip = args.Skip; CurrentPage = args.PageIndex; diff --git a/Radzen.Blazor/ParameterViewExtensions.cs b/Radzen.Blazor/ParameterViewExtensions.cs index 39ea1661..3dc275f2 100644 --- a/Radzen.Blazor/ParameterViewExtensions.cs +++ b/Radzen.Blazor/ParameterViewExtensions.cs @@ -18,7 +18,7 @@ public static class ParameterViewExtensions /// true if the parameter value has changed, false otherwise. public static bool DidParameterChange(this ParameterView parameters, string parameterName, T parameterValue) { - if (parameters.TryGetValue(parameterName, out T value)) + if (parameters.TryGetValue(parameterName, out T? value)) { return !EqualityComparer.Default.Equals(value, parameterValue); } diff --git a/Radzen.Blazor/PickListItemRenderEventArgs.cs b/Radzen.Blazor/PickListItemRenderEventArgs.cs index 9d6389db..c7a465a8 100644 --- a/Radzen.Blazor/PickListItemRenderEventArgs.cs +++ b/Radzen.Blazor/PickListItemRenderEventArgs.cs @@ -10,7 +10,7 @@ public class PickListItemRenderEventArgs /// /// Gets the data item. /// - public TItem Item { get; internal set; } + public TItem? Item { get; internal set; } /// /// Gets or sets a value indicating whether this item is visible. diff --git a/Radzen.Blazor/PreviewFileInfo.cs b/Radzen.Blazor/PreviewFileInfo.cs index 7b6af447..d0ef60f4 100644 --- a/Radzen.Blazor/PreviewFileInfo.cs +++ b/Radzen.Blazor/PreviewFileInfo.cs @@ -25,6 +25,6 @@ public class PreviewFileInfo : FileInfo /// /// Gets the URL of the previewed file. /// - public string Url { get; set; } + public string? Url { get; set; } } diff --git a/Radzen.Blazor/PropertyAccess.cs b/Radzen.Blazor/PropertyAccess.cs index 6eb1dfd3..c2a2d621 100644 --- a/Radzen.Blazor/PropertyAccess.cs +++ b/Radzen.Blazor/PropertyAccess.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -19,13 +20,15 @@ public static class PropertyAccess /// Name of the property to return. /// Type of the object. /// A function which return the specified property by its name. - public static Func Getter(string propertyName, Type type = null) + public static Func Getter(string propertyName, Type? type = null) { - if (propertyName.Contains("[")) + ArgumentNullException.ThrowIfNull(propertyName); + + if (propertyName.Contains('[', StringComparison.Ordinal)) { var arg = Expression.Parameter(typeof(TItem)); - return Expression.Lambda>(QueryableExtension.GetNestedPropertyExpression(arg, propertyName, type), arg).Compile(); + return Expression.Lambda>(QueryableExtension.GetNestedPropertyExpression(arg, propertyName ?? string.Empty, type), arg).Compile(); } else { @@ -43,7 +46,7 @@ public static class PropertyAccess if (body.Type.IsInterface) { body = Expression.Property(body, - new[] { body.Type }.Concat(body.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(member) != null), + new[] { body.Type }.Concat(body.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(member) != null)!, member ); } @@ -85,7 +88,7 @@ public static class PropertyAccess /// /// The source. /// true if the specified type is a DateTime instance or nullable DateTime; otherwise, false. - public static bool IsDate(Type source) + public static bool IsDate(Type? source) { if (source == null) return false; var type = source.IsGenericType ? source.GetGenericArguments()[0] : source; @@ -108,7 +111,7 @@ public static class PropertyAccess /// /// The source. /// true if the specified type is a DateOnly instance or nullable DateOnly; otherwise, false. - public static bool IsDateOnly(Type source) + public static bool IsDateOnly(Type? source) { if (source == null) return false; var type = source.IsGenericType ? source.GetGenericArguments()[0] : source; @@ -127,9 +130,9 @@ public static class PropertyAccess /// /// The source DateTime. /// DateOnly object or null. - public static object DateOnlyFromDateTime(DateTime source) + public static object? DateOnlyFromDateTime(DateTime source) { - object result = null; + object? result = null; #if NET6_0_OR_GREATER result = DateOnly.FromDateTime(source); #endif @@ -143,9 +146,12 @@ public static class PropertyAccess /// The type of the collection element. public static Type GetElementType(Type type) { + ArgumentNullException.ThrowIfNull(type); + if (type.IsArray) { - return type.GetElementType(); + var elementType = type.GetElementType(); + return elementType ?? typeof(object); } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) @@ -176,8 +182,15 @@ public static class PropertyAccess /// The value. /// The path. /// The value of the specified expression or if not found. - public static object GetValue(object value, string path) + public static object? GetValue(object? value, string path) { + ArgumentNullException.ThrowIfNull(path); + + if (value == null) + { + return null; + } + Type currentType = value.GetType(); foreach (string propertyName in path.Split('.')) @@ -185,11 +198,12 @@ public static class PropertyAccess var property = currentType.GetProperty(propertyName); if (property != null) { - if (value != null) + if (value == null) { - value = property.GetValue(value, null); + return null; } + value = property.GetValue(value, null); currentType = property.PropertyType; } } @@ -205,6 +219,9 @@ public static class PropertyAccess /// A function that returns the specified property. public static Func Getter(object data, string propertyName) { + ArgumentNullException.ThrowIfNull(data); + ArgumentNullException.ThrowIfNull(propertyName); + var type = data.GetType(); var arg = Expression.Parameter(typeof(object)); var body = Expression.Convert(Expression.Property(Expression.Convert(arg, type), propertyName), typeof(T)); @@ -222,7 +239,7 @@ public static class PropertyAccess /// true if successful, false otherwise. public static bool TryGetItemOrValueFromProperty(object item, string property, out T result) { - object r = GetItemOrValueFromProperty(item, property); + object? r = item != null ? GetItemOrValueFromProperty(item, property) : null; if (r != null) { @@ -231,7 +248,7 @@ public static class PropertyAccess } else { - result = default; + result = default(T)!; return false; } } @@ -242,7 +259,7 @@ public static class PropertyAccess /// The item. /// The property. /// System.Object. - public static object GetItemOrValueFromProperty(object item, string property) + public static object? GetItemOrValueFromProperty(object? item, string? property) { if (item == null) { @@ -293,7 +310,7 @@ public static class PropertyAccess /// /// The type. /// true if the specified source is an enum; otherwise, false. - public static bool IsEnum(Type source) + public static bool IsEnum(Type? source) { if (source == null) return false; @@ -306,10 +323,10 @@ public static class PropertyAccess /// /// The type. /// true if the specified source is an enum; otherwise, false. - public static bool IsNullableEnum(Type source) + public static bool IsNullableEnum(Type? source) { if (source == null) return false; - Type u = Nullable.GetUnderlyingType(source); + Type? u = Nullable.GetUnderlyingType(source); return (u != null) && u.IsEnum; } @@ -320,6 +337,8 @@ public static class PropertyAccess /// true if the specified type is anonymous; otherwise, false. public static bool IsAnonymous(this Type type) { + ArgumentNullException.ThrowIfNull(type); + if (type.IsGenericType) { var d = type.GetGenericTypeDefinition(); @@ -344,6 +363,9 @@ public static class PropertyAccess /// The modified string. public static string ReplaceFirst(this string text, string search, string replace) { + ArgumentNullException.ThrowIfNull(text); + ArgumentNullException.ThrowIfNull(search); + int pos = text.IndexOf(search, StringComparison.Ordinal); if (pos < 0) { @@ -358,27 +380,33 @@ public static class PropertyAccess /// The type. /// The property. /// Type. - public static Type GetPropertyType(Type type, string property) + public static Type? GetPropertyType(Type type, string property) { - if (property.Contains(".")) + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(property); + + if (property.Contains('.', StringComparison.Ordinal)) { var part = property.Split('.').FirstOrDefault(); - return GetPropertyType(GetPropertyTypeIncludeInterface(type, part), property.ReplaceFirst($"{part}.", "")); + var propertyType = GetPropertyTypeIncludeInterface(type, part); + if (propertyType == null) + return null; + return GetPropertyType(propertyType, property.ReplaceFirst($"{part}.", "")); } return GetPropertyTypeIncludeInterface(type, property); } - private static Type GetPropertyTypeIncludeInterface(Type type, string property) + private static Type? GetPropertyTypeIncludeInterface(Type? type, string? property) { if (type != null) { return !type.IsInterface ? - type.GetProperty(property)?.PropertyType : + type.GetProperty(property ?? "")?.PropertyType : new Type[] { type } .Concat(type.GetInterfaces()) - .FirstOrDefault(t => t.GetProperty(property) != null)? - .GetProperty(property)?.PropertyType; + .FirstOrDefault(t => t.GetProperty(property ?? "") != null)? + .GetProperty(property ?? "")?.PropertyType; } return null; @@ -390,8 +418,11 @@ public static class PropertyAccess /// The type. /// The property. /// PropertyInfo. - public static PropertyInfo GetProperty(Type type, string property) + public static PropertyInfo? GetProperty(Type type, string property) { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(property); + if (type.IsInterface) { var interfaces = type.GetInterfaces(); @@ -418,6 +449,9 @@ public static class PropertyAccess /// Dynamic property expression. public static string GetDynamicPropertyExpression(string name, Type type) { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(type); + var isEnum = type.IsEnum || Nullable.GetUnderlyingType(type)?.IsEnum == true; var typeName = isEnum ? "Enum" : (Nullable.GetUnderlyingType(type) ?? type).Name; var typeFunc = $@"{typeName}{(!isEnum && Nullable.GetUnderlyingType(type) != null ? "?" : "")}"; diff --git a/Radzen.Blazor/Query.cs b/Radzen.Blazor/Query.cs index bf6b5c6b..245dcfab 100644 --- a/Radzen.Blazor/Query.cs +++ b/Radzen.Blazor/Query.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.Encodings.Web; @@ -10,9 +11,9 @@ namespace Radzen; /// public class Query { - private Func getFilter; + private Func? getFilter; - internal Func GetFilter + internal Func? GetFilter { get { @@ -25,19 +26,19 @@ public class Query } } - private string filter; + private string? filter; /// /// Gets the filter expression as a string. /// /// The filter. - public string Filter + public string? Filter { get { if (filter == null && GetFilter != null) { - filter = GetFilter(); + filter = GetFilter?.Invoke(); } return filter; } @@ -51,37 +52,37 @@ public class Query /// Gets the filter expression as a collection of filter descriptors. /// /// The filter parameters. - public IEnumerable Filters { get; set; } + public IEnumerable? Filters { get; set; } /// /// Gets the sort expression as a collection of sort descriptors. /// /// The sorts. - public IEnumerable Sorts { get; set; } + public IEnumerable? Sorts { get; set; } /// /// Gets or sets the filter parameters. /// /// The filter parameters. - public object[] FilterParameters { get; set; } + public object[]? FilterParameters { get; set; } /// /// Gets or sets the order by. /// /// The order by. - public string OrderBy { get; set; } + public string? OrderBy { get; set; } /// /// Gets or sets the expand. /// /// The expand. - public string Expand { get; set; } + public string? Expand { get; set; } /// /// Gets or sets the select. /// /// The select. - public string Select { get; set; } + public string? Select { get; set; } /// /// Gets or sets the skip. @@ -134,7 +135,7 @@ public class Query queryParameters.Add("$select", Select); } - return string.Format("{0}{1}", url, queryParameters.Any() ? "?" + string.Join("&", queryParameters.Select(a => $"{a.Key}={a.Value}")) : ""); + return string.Format(CultureInfo.InvariantCulture, "{0}{1}", url, queryParameters.Count > 0 ? "?" + string.Join("&", queryParameters.Select(a => $"{a.Key}={a.Value}")) : ""); } } diff --git a/Radzen.Blazor/QueryStringThemeService.cs b/Radzen.Blazor/QueryStringThemeService.cs index c5d4b6c3..45fdc28c 100644 --- a/Radzen.Blazor/QueryStringThemeService.cs +++ b/Radzen.Blazor/QueryStringThemeService.cs @@ -39,20 +39,22 @@ namespace Radzen private readonly ThemeService themeService; #if NET7_0_OR_GREATER - private readonly IDisposable registration; + private readonly IDisposable? registration; #endif - private readonly QueryStringThemeServiceOptions options; - private readonly PropertyInfo hasAttachedJSRuntimeProperty; + private readonly QueryStringThemeServiceOptions? options; + private readonly PropertyInfo? hasAttachedJSRuntimeProperty; /// /// Initializes a new instance of the class. /// public QueryStringThemeService(NavigationManager navigationManager, ThemeService themeService, IOptions options) { + ArgumentNullException.ThrowIfNull(navigationManager); + ArgumentNullException.ThrowIfNull(themeService); + ArgumentNullException.ThrowIfNull(options); + this.navigationManager = navigationManager; - this.themeService = themeService; - this.options = options.Value; hasAttachedJSRuntimeProperty = navigationManager.GetType().GetProperty("HasAttachedJSRuntime"); @@ -84,7 +86,7 @@ namespace Radzen #endif } - private bool RequiresChange((string theme, bool? wcag, bool? rightToLeft) state) => + private bool RequiresChange((string? theme, bool? wcag, bool? rightToLeft) state) => (state.theme != null && !string.Equals(themeService.Theme, state.theme, StringComparison.OrdinalIgnoreCase)) || themeService.Wcag != state.wcag || themeService.RightToLeft != state.rightToLeft; @@ -104,36 +106,36 @@ namespace Radzen } #endif - private (string theme, bool? wcag, bool? rightToLeft) GetStateFromQueryString(string uri) + private (string? theme, bool? wcag, bool? rightToLeft) GetStateFromQueryString(string uri) { - var queryString = uri.Contains('?') ? uri[(uri.IndexOf('?') + 1)..] : string.Empty; + var queryString = uri.Contains('?', StringComparison.Ordinal) ? uri[(uri.IndexOf('?', StringComparison.Ordinal) + 1)..] : string.Empty; - var query = HttpUtility.ParseQueryString(queryString.Contains('#') ? queryString[..queryString.IndexOf('#')] : queryString); + var query = HttpUtility.ParseQueryString(queryString.Contains('#', StringComparison.Ordinal) ? queryString[..queryString.IndexOf('#', StringComparison.Ordinal)] : queryString); - bool? wcag = query.Get(options.WcagParameter) != null ? query.Get(options.WcagParameter) == "true" : null; - bool? rtl = query.Get(options.RightToLeftParameter) != null ? query.Get(options.RightToLeftParameter) == "true" : null; + bool? wcag = options?.WcagParameter != null ? (query.Get(options.WcagParameter) != null ? query.Get(options.WcagParameter) == "true" : null) : null; + bool? rtl = options?.RightToLeftParameter != null ? (query.Get(options.RightToLeftParameter) != null ? query.Get(options.RightToLeftParameter) == "true" : null) : null; - return (query.Get(options.ThemeParameter), wcag, rtl); + return (query?.Get(options?.ThemeParameter), wcag, rtl); } private string GetUriWithStateQueryParameters(string uri) { - var parameters = new Dictionary + var parameters = new Dictionary { - { options.ThemeParameter, themeService.Theme.ToLowerInvariant() }, + { options?.ThemeParameter ?? string.Empty, themeService?.Theme?.ToLowerInvariant() ?? string.Empty }, }; - if (themeService.Wcag.HasValue) + if (themeService?.Wcag != null && options?.WcagParameter != null) { parameters.Add(options.WcagParameter, themeService.Wcag.Value ? "true" : "false"); } - if (themeService.RightToLeft.HasValue) + if (themeService?.RightToLeft != null && options?.RightToLeftParameter != null) { parameters.Add(options.RightToLeftParameter, themeService.RightToLeft.Value ? "true" : "false"); } - return navigationManager.GetUriWithQueryParameters(uri, parameters); + return navigationManager.GetUriWithQueryParameters(uri, new Dictionary(parameters)); } private void OnThemeChanged() @@ -156,6 +158,8 @@ namespace Radzen #if NET7_0_OR_GREATER registration?.Dispose(); #endif + + GC.SuppressFinalize(this); } } diff --git a/Radzen.Blazor/QueryableExtension.cs b/Radzen.Blazor/QueryableExtension.cs index d6ffc9a3..2ebca07c 100644 --- a/Radzen.Blazor/QueryableExtension.cs +++ b/Radzen.Blazor/QueryableExtension.cs @@ -3,6 +3,7 @@ using Radzen.Blazor; using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -24,6 +25,9 @@ namespace Radzen /// public static IQueryable Select(this IQueryable source, string propertyName) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(propertyName); + var parameter = Expression.Parameter(source.ElementType, "x"); var property = GetNestedPropertyExpression(parameter, propertyName); @@ -43,6 +47,9 @@ namespace Radzen /// public static IQueryable SelectMany(this IQueryable source, string propertyName) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(propertyName); + var parameter = Expression.Parameter(source.ElementType, "x"); var property = GetNestedPropertyExpression(parameter, propertyName); @@ -68,6 +75,9 @@ namespace Radzen /// public static IQueryable GroupByMany(this IQueryable source, string[] properties) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(properties); + var parameter = Expression.Parameter(source.ElementType, "x"); return GroupByMany(source, @@ -106,15 +116,17 @@ namespace Radzen /// Sorts the elements of a sequence in ascending or descending order according to a key. /// /// A whose elements are sorted according to the specified . - public static IOrderedQueryable OrderBy(this IQueryable source, string selector = null) + public static IOrderedQueryable OrderBy(this IQueryable source, string? selector = null) { + ArgumentNullException.ThrowIfNull(source); + selector = $"{selector}"; - if (selector.Contains("=>")) + if (selector.Contains("=>", StringComparison.Ordinal)) { var identifierName = selector.Split("=>")[0]; - selector = selector.Replace($"{identifierName}=>", "").Trim(); + selector = selector.Replace($"{identifierName}=>", "", StringComparison.Ordinal).Trim(); string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; @@ -126,7 +138,7 @@ namespace Radzen var lambda = ExpressionParser.ParseLambda($"{identifierName.Trim()} => {part}"); expression = Expression.Call( - typeof(Queryable), part.Trim().ToLower().Contains(" desc") ? methodDesc : methodAsc, + typeof(Queryable), part.Trim().ToLower(CultureInfo.InvariantCulture).Contains(" desc", StringComparison.Ordinal) ? methodDesc : methodAsc, new Type[] { source.ElementType, lambda.ReturnType }, expression, Expression.Quote(lambda)); @@ -144,11 +156,16 @@ namespace Radzen /// Sorts the elements of a sequence in ascending or descending order according to a key. /// /// A whose elements are sorted according to the specified . - public static IQueryable OrderBy(this IQueryable source, string selector = null) + public static IQueryable OrderBy(this IQueryable source, string? selector = null) { - selector = selector.Contains("=>") ? RemoveVariableReference(selector) : selector; + ArgumentNullException.ThrowIfNull(source); + + selector = !string.IsNullOrEmpty(selector) && selector.Contains("=>", StringComparison.Ordinal) + ? RemoveVariableReference(selector) + : selector; var parameters = new ParameterExpression[] { Expression.Parameter(source.ElementType, "x") }; + var parameterExpression = parameters[0]; Expression expression = source.Expression; @@ -162,8 +179,9 @@ namespace Radzen var name = string.Join(" ", nameAndOrder.Split(' ').Where(i => !sortStrings.Contains(i.Trim()))).Trim(); var order = nameAndOrder.Split(' ').FirstOrDefault(i => sortStrings.Contains(i.Trim())) ?? sortStrings.First(); - Expression property = !string.IsNullOrEmpty(name) && name != "x" && name != "it" ? - GetNestedPropertyExpression(parameters.FirstOrDefault(), name) : parameters.FirstOrDefault(); + Expression property = !string.IsNullOrEmpty(name) && name != "x" && name != "it" + ? GetNestedPropertyExpression(parameterExpression, name) + : parameterExpression; expression = Expression.Call( typeof(Queryable), order.Equals(sortStrings.First(), StringComparison.OrdinalIgnoreCase) ? methodAsc : methodDesc, @@ -184,9 +202,14 @@ namespace Radzen /// default if source is empty; otherwise, the first element in source. public static dynamic FirstOrDefault(this IQueryable source) { - return source.Provider.Execute(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.FirstOrDefault)).FirstOrDefault(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1).MakeGenericMethod(source.ElementType), - source.Expression)); + ArgumentNullException.ThrowIfNull(source); + var methodInfo = typeof(Queryable) + .GetTypeInfo() + .GetDeclaredMethods(nameof(Queryable.FirstOrDefault)) + .First(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1) + .MakeGenericMethod(source.ElementType); + + return source.Provider.Execute(Expression.Call(null, methodInfo, source.Expression))!; } /// @@ -196,9 +219,14 @@ namespace Radzen /// default if source is empty; otherwise, the last element in source. public static dynamic LastOrDefault(this IQueryable source) { - return source.Provider.Execute(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.LastOrDefault)).FirstOrDefault(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1).MakeGenericMethod(source.ElementType), - source.Expression)); + ArgumentNullException.ThrowIfNull(source); + var methodInfo = typeof(Queryable) + .GetTypeInfo() + .GetDeclaredMethods(nameof(Queryable.LastOrDefault)) + .First(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1) + .MakeGenericMethod(source.ElementType); + + return source.Provider.Execute(Expression.Call(null, methodInfo, source.Expression))!; } /// @@ -209,9 +237,16 @@ namespace Radzen /// An that contains each element of the source sequence converted to the specified type. public static IQueryable Cast(this IQueryable source, Type type) { - return source.Provider.CreateQuery(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.Cast)).FirstOrDefault(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1).MakeGenericMethod(type), - source.Expression)); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(type); + + var methodInfo = typeof(Queryable) + .GetTypeInfo() + .GetDeclaredMethods(nameof(Queryable.Cast)) + .First(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1) + .MakeGenericMethod(type); + + return source.Provider.CreateQuery(Expression.Call(null, methodInfo, source.Expression)); } /// @@ -222,9 +257,15 @@ namespace Radzen /// The result. public static dynamic Sum(this IQueryable source, Type type) { - return source.Provider.Execute(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.Sum)).FirstOrDefault(mi => mi.GetParameters().Length == 1 && mi.ReturnType == type), - source.Expression)); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(type); + + var methodInfo = typeof(Queryable) + .GetTypeInfo() + .GetDeclaredMethods(nameof(Queryable.Sum)) + .First(mi => mi.GetParameters().Length == 1 && mi.ReturnType == type); + + return source.Provider.Execute(Expression.Call(null, methodInfo, source.Expression))!; } /// @@ -235,10 +276,19 @@ namespace Radzen /// The result. public static dynamic Average(this IQueryable source, Type type) { - return source.Provider.Execute(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.Average)) - .FirstOrDefault(mi => mi.GetParameters().Length == 1 && mi.GetParameters()[0].ParameterType.IsGenericType && mi.GetParameters()[0].ParameterType.GetGenericArguments()[0] == type && mi.ReturnType == type), - source.Expression)); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(type); + + var methodInfo = typeof(Queryable) + .GetTypeInfo() + .GetDeclaredMethods(nameof(Queryable.Average)) + .First(mi => + mi.GetParameters().Length == 1 && + mi.GetParameters()[0].ParameterType.IsGenericType && + mi.GetParameters()[0].ParameterType.GetGenericArguments()[0] == type && + mi.ReturnType == type); + + return source.Provider.Execute(Expression.Call(null, methodInfo, source.Expression))!; } /// @@ -249,9 +299,15 @@ namespace Radzen /// The result. public static dynamic Min(this IQueryable source, Type type) { - return source.Provider.Execute(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.Min)).FirstOrDefault(mi => mi.GetParameters().Length == 1 && mi.ReturnType == type), - source.Expression)); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(type); + + var methodInfo = typeof(Queryable) + .GetTypeInfo() + .GetDeclaredMethods(nameof(Queryable.Min)) + .First(mi => mi.GetParameters().Length == 1 && mi.ReturnType == type); + + return source.Provider.Execute(Expression.Call(null, methodInfo, source.Expression))!; } /// @@ -262,9 +318,15 @@ namespace Radzen /// The result. public static dynamic Max(this IQueryable source, Type type) { - return source.Provider.Execute(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.Max)).FirstOrDefault(mi => mi.GetParameters().Length == 1 && mi.ReturnType == type), - source.Expression)); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(type); + + var maxMethod = typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.Max)).FirstOrDefault(mi => mi.GetParameters().Length == 1 && mi.ReturnType == type); + if (maxMethod == null) + { + throw new InvalidOperationException($"Max method not found for type {type}"); + } + return source.Provider.Execute(Expression.Call(null, maxMethod, source.Expression))!; } /// @@ -274,8 +336,16 @@ namespace Radzen /// An that contains distinct elements from the source sequence. public static IQueryable Distinct(this IQueryable source) { - return source.Provider.CreateQuery(Expression.Call(null, - typeof(Queryable).GetTypeInfo().GetDeclaredMethods(nameof(Queryable.Distinct)).FirstOrDefault(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1).MakeGenericMethod(source.ElementType), + ArgumentNullException.ThrowIfNull(source); + + var distinctMethod = typeof(Queryable).GetTypeInfo() + .GetDeclaredMethods(nameof(Queryable.Distinct)) + .FirstOrDefault(mi => mi.IsGenericMethod && mi.GetParameters().Length == 1) + ?? throw new InvalidOperationException("Unable to locate Queryable.Distinct method."); + + return source.Provider.CreateQuery(Expression.Call( + null, + distinctMethod.MakeGenericMethod(source.ElementType), source.Expression)); } @@ -288,12 +358,22 @@ namespace Radzen LogicalFilterOperator logicalFilterOperator, FilterCaseSensitivity filterCaseSensitivity) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(filters); + var whereMethod = typeof(QueryableExtension) .GetMethods() .First(m => m.Name == "Where" && m.IsGenericMethodDefinition && m.GetParameters().Any(p => p.ParameterType == typeof(IEnumerable))) .MakeGenericMethod(source.ElementType); - return (IQueryable)whereMethod.Invoke(null, new object[] { source, filters, logicalFilterOperator, filterCaseSensitivity }); + var result = whereMethod.Invoke(null, new object[] { source, filters, logicalFilterOperator, filterCaseSensitivity }) as IQueryable; + + if (result == null) + { + throw new InvalidOperationException("Queryable.Where invocation returned null."); + } + + return result; } /// @@ -302,15 +382,24 @@ namespace Radzen public static IQueryable Where(this IQueryable source, IEnumerable filters, LogicalFilterOperator logicalFilterOperator, FilterCaseSensitivity filterCaseSensitivity) { - if (filters == null || !filters.Any()) + ArgumentNullException.ThrowIfNull(source); + + if (filters == null) + { + return source; + } + + var filterList = filters as ICollection ?? filters.ToList(); + + if (filterList.Count == 0) return source; var parameter = Expression.Parameter(typeof(T), "x"); - Expression combinedExpression = null; + Expression? combinedExpression = null; - foreach (var filter in filters) + foreach (var filter in filterList) { - var expression = GetExpression(parameter, filter, filterCaseSensitivity, filter.Type); + var expression = GetExpression(parameter, filter, filterCaseSensitivity, filter.Type ?? typeof(object)); if (expression == null) continue; combinedExpression = combinedExpression == null @@ -323,14 +412,14 @@ namespace Radzen if (combinedExpression == null) return source; - var lambda = Expression.Lambda>(combinedExpression, parameter); + var lambda = Expression.Lambda>(combinedExpression!, parameter); return source.Where(lambda); } - internal static Expression GetNestedPropertyExpression(Expression expression, string property, Type type = null) + internal static Expression GetNestedPropertyExpression(Expression expression, string property, Type? type = null) { - var parts = property.Split(new char[] { '.' }, 2); + var parts = property.Split(separator, 2); string currentPart = parts[0]; Expression member; @@ -340,16 +429,16 @@ namespace Radzen var key = currentPart.Split('"')[1]; var typeString = currentPart.Split('(')[0]; - var indexer = typeof(System.Data.DataRow).IsAssignableFrom(expression.Type) ? - Expression.Property(expression, expression.Type.GetProperty("Item", new[] { typeof(string) }), Expression.Constant(key)) : - Expression.Property(expression, expression.Type.GetProperty("Item"), Expression.Constant(key)); + var indexer = typeof(System.Data.DataRow).IsAssignableFrom(expression.Type) ? + Expression.Property(expression, expression.Type.GetProperty("Item", new[] { typeof(string) })!, Expression.Constant(key)) : + Expression.Property(expression, expression.Type.GetProperty("Item")!, Expression.Constant(key)); member = Expression.Convert( indexer, - parts.Length > 1 ? indexer.Type : type ?? Type.GetType(typeString.EndsWith("?") ? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]" : $"System.{typeString}") ?? typeof(object)); + parts.Length > 1 ? indexer.Type : type ?? Type.GetType(typeString.EndsWith('?') ? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]" : $"System.{typeString}") ?? typeof(object)); } - else if (currentPart.Contains("[")) // Handle array or list indexing + else if (currentPart.Contains('[', StringComparison.Ordinal)) // Handle array or list indexing { - var indexStart = currentPart.IndexOf('['); + var indexStart = currentPart.IndexOf('[', StringComparison.Ordinal); var propertyName = currentPart.Substring(0, indexStart); var indexString = currentPart.Substring(indexStart + 1, currentPart.Length - indexStart - 2); @@ -376,48 +465,56 @@ namespace Radzen throw new ArgumentException($"Invalid index format: {indexString}"); } } - else if (expression.Type.IsInterface) + else if (expression != null && expression.Type != null && expression.Type.IsInterface) { member = Expression.Property(expression, - new[] { expression.Type }.Concat(expression.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(currentPart) != null), + new[] { expression.Type }.Concat(expression.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(currentPart) != null)!, currentPart ); } else { - var p = expression.Type.GetProperty(currentPart, BindingFlags.Public | BindingFlags.Instance); - member = p != null ? Expression.Property(expression, p) : Expression.PropertyOrField(expression, currentPart); + if (expression == null || string.IsNullOrEmpty(currentPart)) + { + return Expression.Constant(null, typeof(object)); } - if (expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) == null) + var p = expression.Type?.GetProperty(currentPart, BindingFlags.Public | BindingFlags.Instance); + member = p != null ? Expression.Property(expression, p) : Expression.PropertyOrField(expression, currentPart); + } + + if (expression != null && expression.Type != null && expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) == null) { expression = Expression.Convert(expression, typeof(object)); } return parts.Length > 1 ? GetNestedPropertyExpression(member, parts[1], type) : (Nullable.GetUnderlyingType(member.Type) != null || member.Type == typeof(string)) ? - Expression.Condition(Expression.Equal(expression, Expression.Constant(null)), Expression.Constant(null, member.Type), member) : + expression != null ? Expression.Condition(Expression.Equal(expression, Expression.Constant(null)), Expression.Constant(null, member.Type), member) : member : member; } internal static Expression GetExpression(ParameterExpression parameter, FilterDescriptor filter, FilterCaseSensitivity filterCaseSensitivity, Type type) { - Type valueType = filter.FilterValue != null ? filter.FilterValue.GetType() : null; + Type? valueType = filter.FilterValue != null ? filter.FilterValue.GetType() : null; var isEnumerable = valueType != null && IsEnumerable(valueType) && valueType != typeof(string); - Type secondValueType = filter.SecondFilterValue != null ? filter.SecondFilterValue.GetType() : null; + Type? secondValueType = filter.SecondFilterValue != null ? filter.SecondFilterValue.GetType() : null; - Expression p = GetNestedPropertyExpression(parameter, filter.Property, type); + Expression p = !string.IsNullOrEmpty(filter.Property) ? GetNestedPropertyExpression(parameter, filter.Property, type) : Expression.Constant(null); - Expression property = GetNestedPropertyExpression(parameter, !isEnumerable && !IsEnumerable(p.Type) ? filter.FilterProperty ?? filter.Property : filter.Property, type); + var propertyName = !isEnumerable && !IsEnumerable(p.Type) ? (!string.IsNullOrWhiteSpace(filter.FilterProperty) ? filter.FilterProperty : filter.Property) : filter.Property; + Expression property = !string.IsNullOrEmpty(propertyName) ? GetNestedPropertyExpression(parameter, propertyName, type) : Expression.Constant(null); - Type collectionItemType = IsEnumerable(property.Type) && property.Type.IsGenericType ? property.Type.GetGenericArguments()[0] : null; + Type? collectionItemType = IsEnumerable(property.Type) && property.Type.IsGenericType ? property.Type.GetGenericArguments()[0] : null; - ParameterExpression collectionItemTypeParameter = collectionItemType != null ? Expression.Parameter(collectionItemType, "x") : null; + ParameterExpression? collectionItemTypeParameter = collectionItemType != null ? Expression.Parameter(collectionItemType, "x") : null; if (collectionItemType != null && filter.Property != filter.FilterProperty) { - property = !string.IsNullOrEmpty(filter.FilterProperty) ? GetNestedPropertyExpression(collectionItemTypeParameter, filter.FilterProperty) : collectionItemTypeParameter; + property = !string.IsNullOrEmpty(filter.FilterProperty) + ? GetNestedPropertyExpression(collectionItemTypeParameter!, filter.FilterProperty) + : collectionItemTypeParameter!; filter.FilterOperator = filter.FilterOperator == FilterOperator.In ? FilterOperator.Contains : filter.FilterOperator == FilterOperator.NotIn ? FilterOperator.DoesNotContain : filter.FilterOperator; @@ -428,23 +525,66 @@ namespace Radzen var isEnumerableProperty = IsEnumerable(property.Type) && property.Type != typeof(string); - var constant = Expression.Constant(caseInsensitive ? - $"{filter.FilterValue}".ToLowerInvariant() : - isEnum && !isEnumerable && filter.FilterValue != null ? Enum.ToObject(Nullable.GetUnderlyingType(property.Type) ?? property.Type, filter.FilterValue) : filter.FilterValue, - !isEnum && isEnumerable ? valueType : isEnumerableProperty ? valueType: property.Type); + var constantValue = caseInsensitive + ? $"{filter.FilterValue}".ToLowerInvariant() + : isEnum && !isEnumerable && filter.FilterValue != null + ? Enum.ToObject(Nullable.GetUnderlyingType(property.Type) ?? property.Type, filter.FilterValue) + : filter.FilterValue; + + Type constantType; + if (!isEnum && isEnumerable) + { + constantType = valueType ?? typeof(object); + } + else if (isEnumerableProperty) + { + constantType = valueType ?? property.Type; + } + else + { + constantType = property.Type; + } + + if (constantValue == null && constantType.IsValueType && Nullable.GetUnderlyingType(constantType) == null) + { + constantType = typeof(object); + } + + var constant = Expression.Constant(constantValue, constantType); if (caseInsensitive && !isEnumerable) { - property = Expression.Call(notNullCheck(property), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes)); + property = Expression.Call(notNullCheck(property), typeof(string).GetMethod("ToLower", Type.EmptyTypes)!); } - var secondConstant = filter.SecondFilterValue != null ? - Expression.Constant(caseInsensitive ? - $"{filter.SecondFilterValue}".ToLowerInvariant() : - isEnum && filter.SecondFilterValue != null ? Enum.ToObject(Nullable.GetUnderlyingType(property.Type) ?? property.Type, filter.SecondFilterValue) : filter.SecondFilterValue, - secondValueType != null && !isEnum && IsEnumerable(secondValueType) ? secondValueType : property.Type) : null; + Expression? secondConstant = null; + if (filter.SecondFilterValue != null) + { + var secondValue = caseInsensitive + ? $"{filter.SecondFilterValue}".ToLowerInvariant() + : isEnum + ? Enum.ToObject(Nullable.GetUnderlyingType(property.Type) ?? property.Type, filter.SecondFilterValue) + : filter.SecondFilterValue; - Expression primaryExpression = filter.FilterOperator switch + Type secondConstantType; + if (secondValueType != null && !isEnum && IsEnumerable(secondValueType)) + { + secondConstantType = secondValueType; + } + else + { + secondConstantType = property.Type; + } + + if (secondValue == null && secondConstantType.IsValueType && Nullable.GetUnderlyingType(secondConstantType) == null) + { + secondConstantType = typeof(object); + } + + secondConstant = Expression.Constant(secondValue, secondConstantType); + } + + Expression? primaryExpression = filter.FilterOperator switch { FilterOperator.Equals => Expression.Equal(notNullCheck(property), constant), FilterOperator.NotEquals => Expression.NotEqual(notNullCheck(property), constant), @@ -455,23 +595,23 @@ namespace Radzen FilterOperator.Contains => isEnumerable ? Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new Type[] { property.Type }, constant, notNullCheck(property)) : isEnumerableProperty ? - Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new Type[] { collectionItemType }, notNullCheck(property), constant) : - Expression.Call(notNullCheck(property), typeof(string).GetMethod("Contains", new[] { typeof(string) }), constant), + Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new Type[] { collectionItemType! }, notNullCheck(property), constant) : + Expression.Call(notNullCheck(property), typeof(string).GetMethod("Contains", new[] { typeof(string) })!, constant), FilterOperator.In => isEnumerable && isEnumerableProperty ? - Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new Type[] { collectionItemType }, - Expression.Call(typeof(Enumerable), nameof(Enumerable.Intersect), new Type[] { collectionItemType }, constant, notNullCheck(property))) : Expression.Constant(true), + Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new Type[] { collectionItemType! }, + Expression.Call(typeof(Enumerable), nameof(Enumerable.Intersect), new Type[] { collectionItemType! }, constant, notNullCheck(property))) : Expression.Constant(true), FilterOperator.DoesNotContain => isEnumerable ? Expression.Not(Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new Type[] { property.Type }, constant, notNullCheck(property))) : isEnumerableProperty ? - Expression.Not(Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new Type[] { collectionItemType }, notNullCheck(property), constant)) : - Expression.Not(Expression.Call(notNullCheck(property), typeof(string).GetMethod("Contains", new[] { typeof(string) }), constant)), + Expression.Not(Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new Type[] { collectionItemType! }, notNullCheck(property), constant)) : + Expression.Not(Expression.Call(notNullCheck(property), typeof(string).GetMethod("Contains", new[] { typeof(string) })!, constant)), FilterOperator.NotIn => isEnumerable && isEnumerableProperty ? - Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new Type[] { collectionItemType }, - Expression.Call(typeof(Enumerable), nameof(Enumerable.Except), new Type[] { collectionItemType }, constant, notNullCheck(property))) : Expression.Constant(true), - FilterOperator.StartsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), constant), - FilterOperator.EndsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("EndsWith", new[] { typeof(string) }), constant), + Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new Type[] { collectionItemType! }, + Expression.Call(typeof(Enumerable), nameof(Enumerable.Except), new Type[] { collectionItemType! }, constant, notNullCheck(property))) : Expression.Constant(true), + FilterOperator.StartsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!, constant), + FilterOperator.EndsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("EndsWith", new[] { typeof(string) })!, constant), FilterOperator.IsNull => Expression.Equal(property, Expression.Constant(null, property.Type)), FilterOperator.IsNotNull => Expression.NotEqual(property, Expression.Constant(null, property.Type)), FilterOperator.IsEmpty => Expression.Equal(property, Expression.Constant(String.Empty)), @@ -479,14 +619,17 @@ namespace Radzen _ => null }; - if (collectionItemType != null && primaryExpression != null && - !(filter.FilterOperator == FilterOperator.In || filter.FilterOperator == FilterOperator.NotIn)) + if (collectionItemType != null && primaryExpression != null && + !(filter.FilterOperator == FilterOperator.In || filter.FilterOperator == FilterOperator.NotIn)) + { + if (filter.Property != null) { - primaryExpression = Expression.Call(typeof(Enumerable), filter.CollectionFilterMode == CollectionFilterMode.Any ? nameof(Enumerable.Any) : nameof(Enumerable.All), new Type[] { collectionItemType }, - GetNestedPropertyExpression(parameter, filter.Property), Expression.Lambda(primaryExpression, collectionItemTypeParameter)); + primaryExpression = Expression.Call(typeof(Enumerable), filter.CollectionFilterMode == CollectionFilterMode.Any ? nameof(Enumerable.Any) : nameof(Enumerable.All), new Type[] { collectionItemType! }, + GetNestedPropertyExpression(parameter, filter.Property), Expression.Lambda(primaryExpression, collectionItemTypeParameter!)); + } } - Expression secondExpression = null; + Expression? secondExpression = null; if (secondConstant != null) { secondExpression = filter.SecondFilterOperator switch @@ -497,10 +640,10 @@ namespace Radzen FilterOperator.LessThanOrEquals => Expression.LessThanOrEqual(notNullCheck(property), secondConstant), FilterOperator.GreaterThan => Expression.GreaterThan(notNullCheck(property), secondConstant), FilterOperator.GreaterThanOrEquals => Expression.GreaterThanOrEqual(notNullCheck(property), secondConstant), - FilterOperator.Contains => Expression.Call(notNullCheck(property), typeof(string).GetMethod("Contains", new[] { typeof(string) }), secondConstant), - FilterOperator.DoesNotContain => Expression.Not(Expression.Call(notNullCheck(property), property.Type.GetMethod("Contains", new[] { typeof(string) }), secondConstant)), - FilterOperator.StartsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), secondConstant), - FilterOperator.EndsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("EndsWith", new[] { typeof(string) }), secondConstant), + FilterOperator.Contains => Expression.Call(notNullCheck(property), typeof(string).GetMethod("Contains", new[] { typeof(string) })!, secondConstant), + FilterOperator.DoesNotContain => Expression.Not(Expression.Call(notNullCheck(property), property.Type.GetMethod("Contains", new[] { typeof(string) })!, secondConstant)), + FilterOperator.StartsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!, secondConstant), + FilterOperator.EndsWith => Expression.Call(notNullCheck(property), typeof(string).GetMethod("EndsWith", new[] { typeof(string) })!, secondConstant), FilterOperator.IsNull => Expression.Equal(property, Expression.Constant(null, property.Type)), FilterOperator.IsNotNull => Expression.NotEqual(property, Expression.Constant(null, property.Type)), FilterOperator.IsEmpty => Expression.Equal(property, Expression.Constant(String.Empty)), @@ -509,11 +652,14 @@ namespace Radzen }; } - if (collectionItemType != null && secondExpression != null && - !(filter.SecondFilterOperator == FilterOperator.In || filter.SecondFilterOperator == FilterOperator.NotIn)) + if (collectionItemType != null && secondExpression != null && + !(filter.SecondFilterOperator == FilterOperator.In || filter.SecondFilterOperator == FilterOperator.NotIn)) + { + if (filter.Property != null) { - secondExpression = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new Type[] { collectionItemType }, - GetNestedPropertyExpression(parameter, filter.Property), Expression.Lambda(secondExpression, collectionItemTypeParameter)); + secondExpression = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new Type[] { collectionItemType! }, + GetNestedPropertyExpression(parameter, filter.Property), Expression.Lambda(secondExpression, collectionItemTypeParameter!)); + } } if (primaryExpression != null && secondExpression != null) @@ -526,7 +672,7 @@ namespace Radzen }; } - return primaryExpression; + return primaryExpression!; } /// @@ -576,6 +722,7 @@ namespace Radzen {FilterOperator.NotIn, "in"}, {FilterOperator.Custom, ""} }; + private static readonly char[] separator = new char[] { '.' }; /// /// Converts to list. @@ -584,9 +731,21 @@ namespace Radzen /// IList. public static IList ToList(IQueryable query) { - var genericToList = typeof(Enumerable).GetMethod("ToList") - .MakeGenericMethod(new Type[] { query.ElementType }); - return (IList)genericToList.Invoke(null, new[] { query }); + ArgumentNullException.ThrowIfNull(query); + + var toListMethod = typeof(Enumerable).GetMethods() + .FirstOrDefault(m => m.Name == nameof(Enumerable.ToList) && m.GetParameters().Length == 1) + ?? throw new InvalidOperationException("Unable to locate Enumerable.ToList method."); + + var genericToList = toListMethod.MakeGenericMethod(query.ElementType); + var result = genericToList.Invoke(null, new object[] { query }); + + if (result is IList list) + { + return list; + } + + throw new InvalidOperationException("Failed to convert queryable to list."); } /// @@ -595,10 +754,10 @@ namespace Radzen /// /// The columns. /// System.String. - public static string ToFilterString(this IEnumerable> columns) + public static string ToFilterString(this IEnumerable> columns) where T : notnull { Func, bool> canFilter = (c) => c.Filterable && c.FilterPropertyType != null && - (!(c.GetFilterValue() == null || c.GetFilterValue() as string == string.Empty) + (!string.IsNullOrEmpty(c.GetFilterValue() as string) || c.GetFilterOperator() == FilterOperator.IsNotNull || c.GetFilterOperator() == FilterOperator.IsNull || c.GetFilterOperator() == FilterOperator.IsEmpty || c.GetFilterOperator() == FilterOperator.IsNotEmpty) && c.GetFilterProperty() != null; @@ -607,8 +766,8 @@ namespace Radzen (c.GetFilterOperator() == FilterOperator.Custom && c.GetCustomFilterExpression() != null) && c.GetFilterProperty() != null; - var columnsToFilter = columns.Where(canFilter); - var columnsWithCustomFilter = columns.Where(canFilterCustom); + var columnsToFilter = columns.Where(canFilter).ToList(); + var columnsWithCustomFilter = columns.Where(canFilterCustom).ToList(); var grid = columns.FirstOrDefault()?.Grid; var gridLogicalFilterOperator = grid != null ? grid.LogicalFilterOperator : LogicalFilterOperator.And; @@ -617,7 +776,7 @@ namespace Radzen var serializer = new ExpressionSerializer(); var filterExpression = ""; - if (columnsToFilter.Any()) + if (columnsToFilter.Count > 0) { var filters = columnsToFilter.Select(c => new FilterDescriptor() { @@ -630,16 +789,16 @@ namespace Radzen SecondFilterOperator = c.GetSecondFilterOperator(), LogicalFilterOperator = c.GetLogicalFilterOperator(), CollectionFilterMode = c.GetCollectionFilterMode() - }); + }).ToList(); - if (filters.Any()) + if (filters.Count > 0) { - var parameter = Expression.Parameter(typeof(T), columnsWithCustomFilter.Any() ? "it" : "x"); - Expression combinedExpression = null; + var parameter = Expression.Parameter(typeof(T), columnsWithCustomFilter.Count > 0 ? "it" : "x"); + Expression? combinedExpression = null; foreach (var filter in filters) { - var expression = GetExpression(parameter, filter, gridFilterCaseSensitivity, filter.Type); + var expression = GetExpression(parameter, filter, gridFilterCaseSensitivity, filter.Type ?? typeof(object)); if (expression == null) continue; combinedExpression = combinedExpression == null @@ -659,9 +818,9 @@ namespace Radzen var customFilterExpression = ""; - if (columnsWithCustomFilter.Any()) + if (columnsWithCustomFilter.Count > 0) { - var expressions = columnsWithCustomFilter.Select(c => (c.GetCustomFilterExpression() ?? "").Replace(" or ", " || ").Replace(" and ", " && ")).Where(e => !string.IsNullOrEmpty(e)).ToList(); + var expressions = columnsWithCustomFilter.Select(c => (c.GetCustomFilterExpression() ?? "").Replace(" or ", " || ", StringComparison.Ordinal).Replace(" and ", " && ", StringComparison.Ordinal)).Where(e => !string.IsNullOrEmpty(e)).ToList(); customFilterExpression = string.Join($"{(gridLogicalFilterOperator == LogicalFilterOperator.And ? " && " : " || ")}", expressions); return !string.IsNullOrEmpty(filterExpression) && !string.IsNullOrEmpty(customFilterExpression) ? @@ -680,13 +839,16 @@ namespace Radzen /// A Linq-compatible filter string public static string ToFilterString(this RadzenDataFilter dataFilter) { + ArgumentNullException.ThrowIfNull(dataFilter); Func canFilter = (c) => dataFilter.properties.Where(col => col.Property == c.Property).FirstOrDefault()?.FilterPropertyType != null && - (!(c.FilterValue == null || c.FilterValue as string == string.Empty) + (!string.IsNullOrEmpty(c.FilterValue as string) || c.FilterOperator == FilterOperator.IsNotNull || c.FilterOperator == FilterOperator.IsNull || c.FilterOperator == FilterOperator.IsEmpty || c.FilterOperator == FilterOperator.IsNotEmpty) && c.Property != null; - if (dataFilter.Filters.Concat(dataFilter.Filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).Any()) + var applicableFilters = dataFilter.Filters.Concat(dataFilter.Filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).ToList(); + + if (applicableFilters.Count > 0) { var serializer = new ExpressionSerializer(); @@ -699,7 +861,7 @@ namespace Radzen AddWhereExpression(parameter, filter, ref filterExpressions, dataFilter.FilterCaseSensitivity); } - Expression combinedExpression = null; + Expression? combinedExpression = null; foreach (var expression in filterExpressions) { @@ -731,13 +893,16 @@ namespace Radzen LogicalFilterOperator logicalFilterOperator = LogicalFilterOperator.And, FilterCaseSensitivity filterCaseSensitivity = FilterCaseSensitivity.Default) { + ArgumentNullException.ThrowIfNull(filters); Func canFilter = (c) => - (!(c.FilterValue == null || c.FilterValue as string == string.Empty) + (!string.IsNullOrEmpty(c.FilterValue as string) || c.FilterOperator == FilterOperator.IsNotNull || c.FilterOperator == FilterOperator.IsNull || c.FilterOperator == FilterOperator.IsEmpty || c.FilterOperator == FilterOperator.IsNotEmpty) && c.Property != null; - if (filters.Concat(filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).Any()) + var applicableFilters = filters.Concat(filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).ToList(); + + if (applicableFilters.Count > 0) { var serializer = new ExpressionSerializer(); @@ -750,7 +915,7 @@ namespace Radzen AddWhereExpression(parameter, filter, ref filterExpressions, filterCaseSensitivity); } - Expression combinedExpression = null; + Expression? combinedExpression = null; foreach (var expression in filterExpressions) { @@ -778,26 +943,32 @@ namespace Radzen /// The specific value to filter by /// The operator used to compare to /// System.String. - internal static string GetColumnODataFilter(RadzenDataGridColumn column, object filterValue, FilterOperator columnFilterOperator) + internal static string GetColumnODataFilter(RadzenDataGridColumn column, object? filterValue, FilterOperator columnFilterOperator) where T : notnull { - var property = column.GetFilterProperty().Replace('.', '/'); + var filterProperty = column.GetFilterProperty(); + if (string.IsNullOrEmpty(filterProperty)) + { + return string.Empty; + } + + var property = filterProperty.Replace('.', '/'); var odataFilterOperator = ODataFilterOperators[columnFilterOperator]; - var value = IsEnumerable(column.FilterPropertyType) && column.FilterPropertyType != typeof(string) + var value = column.FilterPropertyType != null && IsEnumerable(column.FilterPropertyType) && column.FilterPropertyType != typeof(string) ? null : (string)Convert.ChangeType(filterValue is DateTimeOffset ? ((DateTimeOffset)filterValue).UtcDateTime : filterValue is DateOnly ? ((DateOnly)filterValue).ToString("yyy-MM-dd", CultureInfo.InvariantCulture) : filterValue is Guid ? ((Guid)filterValue).ToString() : - filterValue, typeof(string), CultureInfo.InvariantCulture); + filterValue!, typeof(string), CultureInfo.InvariantCulture); if (column.Grid.FilterCaseSensitivity == FilterCaseSensitivity.CaseInsensitive && column.FilterPropertyType == typeof(string)) { property = $"tolower({property})"; } - if (PropertyAccess.IsEnum(column.FilterPropertyType) || PropertyAccess.IsNullableEnum(column.FilterPropertyType)) + if (column.FilterPropertyType != null && (PropertyAccess.IsEnum(column.FilterPropertyType) || PropertyAccess.IsNullableEnum(column.FilterPropertyType))) { return $"{property} {odataFilterOperator} '{value}'"; } @@ -876,7 +1047,7 @@ namespace Radzen return $"not({column.Property}/any(i: {enumerableValueAsStringOrForAny}))"; } } - else if (PropertyAccess.IsNumeric(column.FilterPropertyType)) + else if (column != null && column.FilterPropertyType != null && PropertyAccess.IsNumeric(column.FilterPropertyType)) { if (columnFilterOperator == FilterOperator.IsNull || columnFilterOperator == FilterOperator.IsNotNull) { @@ -887,7 +1058,7 @@ namespace Radzen return $"{property} {odataFilterOperator} {value}"; } } - else if (column.FilterPropertyType == typeof(bool) || column.FilterPropertyType == typeof(bool?)) + else if (column != null && (column.FilterPropertyType == typeof(bool) || column.FilterPropertyType == typeof(bool?))) { if (columnFilterOperator == FilterOperator.IsNull || columnFilterOperator == FilterOperator.IsNotNull) { @@ -899,15 +1070,20 @@ namespace Radzen } else { - return $"{property} eq {value.ToLower()}"; + if (string.IsNullOrEmpty(value)) + { + return $"{property} {odataFilterOperator} null"; + } + + return $"{property} eq {value.ToLowerInvariant()}"; } } - else if (column.FilterPropertyType == typeof(DateTime) || + else if (column != null && (column.FilterPropertyType == typeof(DateTime) || column.FilterPropertyType == typeof(DateTime?) || column.FilterPropertyType == typeof(DateTimeOffset) || column.FilterPropertyType == typeof(DateTimeOffset?) || column.FilterPropertyType == typeof(DateOnly) || - column.FilterPropertyType == typeof(DateOnly?)) + column.FilterPropertyType == typeof(DateOnly?))) { if (columnFilterOperator == FilterOperator.IsNull || columnFilterOperator == FilterOperator.IsNotNull) { @@ -919,10 +1095,10 @@ namespace Radzen } else { - return $"{property} {odataFilterOperator} {(column.FilterPropertyType == typeof(DateOnly) || column.FilterPropertyType == typeof(DateOnly?) ? value : DateTime.Parse(value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind).ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture))}"; + return $"{property} {odataFilterOperator} {(column.FilterPropertyType == typeof(DateOnly) || column.FilterPropertyType == typeof(DateOnly?) ? value : (value != null ? DateTime.Parse(value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind).ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture) : ""))}"; } } - else if (column.FilterPropertyType == typeof(Guid) || column.FilterPropertyType == typeof(Guid?)) + else if (column?.FilterPropertyType == typeof(Guid) || column?.FilterPropertyType == typeof(Guid?)) { return $"{property} {odataFilterOperator} {value}"; } @@ -936,11 +1112,11 @@ namespace Radzen /// /// The columns. /// System.String. - public static string ToODataFilterString(this IEnumerable> columns) + public static string ToODataFilterString(this IEnumerable> columns) where T : notnull { var columnsWithFilter = GetFilterableColumns(columns); - if (columnsWithFilter.Any()) + if (columnsWithFilter.Count > 0) { var gridLogicalFilterOperator = columns.FirstOrDefault()?.Grid?.LogicalFilterOperator; var gridBooleanOperator = gridLogicalFilterOperator == LogicalFilterOperator.And ? "and" : "or"; @@ -992,9 +1168,9 @@ namespace Radzen /// /// Gets if type is IEnumerable. /// - public static bool IsEnumerable(Type type) + public static bool IsEnumerable(Type? type) { - return (typeof(IEnumerable).IsAssignableFrom(type) || typeof(IEnumerable<>).IsAssignableFrom(type)) && type != typeof(string); + return type != null && (typeof(IEnumerable).IsAssignableFrom(type) || typeof(IEnumerable<>).IsAssignableFrom(type)) && type != typeof(string); } /// @@ -1004,10 +1180,13 @@ namespace Radzen /// The source. /// The columns. /// IQueryable<T>. - public static IQueryable Where(this IQueryable source, IEnumerable> columns) + public static IQueryable Where(this IQueryable source, IEnumerable> columns) where T : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(columns); + Func, bool> canFilter = (c) => c.Filterable && c.FilterPropertyType != null && - (!(c.GetFilterValue() == null || c.GetFilterValue() as string == string.Empty) + (!(c.GetFilterValue() == null || (c.GetFilterValue() as string)?.Length == 0) || c.GetFilterOperator() == FilterOperator.IsNotNull || c.GetFilterOperator() == FilterOperator.IsNull || c.GetFilterOperator() == FilterOperator.IsEmpty || c.GetFilterOperator() == FilterOperator.IsNotEmpty) && c.GetFilterProperty() != null; @@ -1016,12 +1195,12 @@ namespace Radzen (c.GetFilterOperator() == FilterOperator.Custom && c.GetCustomFilterExpression() != null) && c.GetFilterProperty() != null; - var columnsToFilter = columns.Where(canFilter); + var columnsToFilter = columns.Where(canFilter).ToList(); var grid = columns.FirstOrDefault()?.Grid; var gridLogicalFilterOperator = grid != null ? grid.LogicalFilterOperator : LogicalFilterOperator.And; var gridFilterCaseSensitivity = grid != null ? grid.FilterCaseSensitivity : FilterCaseSensitivity.Default; - if (columnsToFilter.Any()) + if (columnsToFilter.Count > 0) { source = source.Where(columnsToFilter.Select(c => new FilterDescriptor() { @@ -1037,12 +1216,12 @@ namespace Radzen }), gridLogicalFilterOperator, gridFilterCaseSensitivity); } - var columnsWithCustomFilter = columns.Where(canFilterCustom); + var columnsWithCustomFilter = columns.Where(canFilterCustom).ToList(); - if (columnsToFilter.Any()) + if (columnsToFilter.Count > 0) { - var expressions = columnsWithCustomFilter.Select(c => (c.GetCustomFilterExpression() ?? "").Replace(" or ", " || ").Replace(" and ", " && ")).Where(e => !string.IsNullOrEmpty(e)).ToList(); - source = expressions.Any() ? + var expressions = columnsWithCustomFilter.Select(c => (c.GetCustomFilterExpression() ?? "").Replace(" or ", " || ", StringComparison.Ordinal).Replace(" and ", " && ", StringComparison.Ordinal)).Where(e => !string.IsNullOrEmpty(e)).ToList(); + source = expressions.Count > 0 ? System.Linq.Dynamic.Core.DynamicExtensions.Where(source, "it => " + string.Join($"{(gridLogicalFilterOperator == LogicalFilterOperator.And ? " && " : " || ")}", expressions)) : source; } @@ -1058,13 +1237,18 @@ namespace Radzen /// IQueryable<T>. public static IQueryable Where(this IQueryable source, RadzenDataFilter dataFilter) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(dataFilter); + Func canFilter = (c) => dataFilter.properties.Where(col => col.Property == c.Property).FirstOrDefault()?.FilterPropertyType != null && - (!(c.FilterValue == null || c.FilterValue as string == string.Empty) + (!(c.FilterValue == null || (c.FilterValue as string)?.Length == 0) || c.FilterOperator == FilterOperator.IsNotNull || c.FilterOperator == FilterOperator.IsNull || c.FilterOperator == FilterOperator.IsEmpty || c.FilterOperator == FilterOperator.IsNotEmpty) && c.Property != null; - if (dataFilter.Filters.Concat(dataFilter.Filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).Any()) + var applicableFilters = dataFilter.Filters.Concat(dataFilter.Filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).ToList(); + + if (applicableFilters.Count > 0) { var filterExpressions = new List(); @@ -1075,7 +1259,7 @@ namespace Radzen AddWhereExpression(parameter, filter, ref filterExpressions, dataFilter.FilterCaseSensitivity); } - Expression combinedExpression = null; + Expression? combinedExpression = null; foreach (var expression in filterExpressions) { @@ -1103,14 +1287,19 @@ namespace Radzen /// IQueryable<T>. public static IQueryable Where(this IQueryable source, IEnumerable filters, LogicalFilterOperator logicalFilterOperator, FilterCaseSensitivity filterCaseSensitivity) { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(filters); + Func canFilter = (c) => - (!(c.FilterValue == null || c.FilterValue as string == string.Empty) + (!(c.FilterValue == null || (c.FilterValue as string)?.Length == 0) || c.FilterOperator == FilterOperator.IsNotNull || c.FilterOperator == FilterOperator.IsNull || c.FilterOperator == FilterOperator.IsEmpty || c.FilterOperator == FilterOperator.IsNotEmpty - || c.Filters.Any()) + || (c.Filters != null && c.Filters.Any())) && c.Property != null; - if (filters.Where(canFilter).Any()) + var applicableFilters = filters.Where(canFilter).ToList(); + + if (applicableFilters.Count > 0) { var filterExpressions = new List(); @@ -1121,7 +1310,7 @@ namespace Radzen AddWhereExpression(parameter, filter, ref filterExpressions, filterCaseSensitivity); } - Expression combinedExpression = null; + Expression? combinedExpression = null; foreach (var expression in filterExpressions) { @@ -1153,9 +1342,9 @@ namespace Radzen AddWhereExpression(parameter, f, ref innerFilterExpressions, filterCaseSensitivity); } - if (innerFilterExpressions.Any()) + if (innerFilterExpressions.Count > 0) { - Expression combinedExpression = null; + Expression? combinedExpression = null; foreach (var expression in innerFilterExpressions) { @@ -1186,12 +1375,12 @@ namespace Radzen Property = filter.Property, FilterProperty = filter.FilterProperty, FilterValue = filter.FilterValue, - FilterOperator = filter.FilterOperator ?? FilterOperator.Equals, + FilterOperator = filter.FilterOperator.HasValue ? filter.FilterOperator.Value : default(FilterOperator), LogicalFilterOperator = filter.LogicalFilterOperator, Type = filter.Type }; - var expression = GetExpression(parameter, f, filterCaseSensitivity, f.Type); + var expression = GetExpression(parameter, f, filterCaseSensitivity, f.Type ?? typeof(object)); if (expression != null) { filterExpressions.Add(expression); @@ -1208,8 +1397,13 @@ namespace Radzen /// The StringFilterOperator. /// The FilterCaseSensitivity. /// IQueryable<T>. - public static IQueryable Where(this IQueryable source, string property, string value, StringFilterOperator op, FilterCaseSensitivity cs) + public static IQueryable Where(this IQueryable source, string property, string? value, StringFilterOperator op, FilterCaseSensitivity cs) { + ArgumentNullException.ThrowIfNull(source); + + property ??= string.Empty; + value ??= string.Empty; + IQueryable result; if (!string.IsNullOrEmpty(value)) @@ -1231,31 +1425,34 @@ namespace Radzen propertyExpression = Expression.Call(string.IsNullOrEmpty(property) && inMemory ? notNullCheck(parameter) : notNullCheck(propertyExpression), "ToString", Type.EmptyTypes); } - if (ignoreCase) + if (ignoreCase && propertyExpression != null) { - propertyExpression = Expression.Call(notNullCheck(propertyExpression), "ToLower", Type.EmptyTypes); + propertyExpression = Expression.Call(notNullCheck(propertyExpression!), "ToLower", Type.EmptyTypes); } - var constantExpression = Expression.Constant(ignoreCase ? value.ToLower() : value, typeof(string)); - Expression comparisonExpression = null; + var constantExpression = Expression.Constant(ignoreCase ? value.ToLower(CultureInfo.InvariantCulture) : value, typeof(string)); + Expression? comparisonExpression = null; - switch (op) + if (propertyExpression != null) { - case StringFilterOperator.Contains: - comparisonExpression = Expression.Call(notNullCheck(propertyExpression), "Contains", null, constantExpression); - break; - case StringFilterOperator.StartsWith: - comparisonExpression = Expression.Call(notNullCheck(propertyExpression), "StartsWith", null, constantExpression); - break; - case StringFilterOperator.EndsWith: - comparisonExpression = Expression.Call(notNullCheck(propertyExpression), "EndsWith", null, constantExpression); - break; - default: - comparisonExpression = Expression.Equal(propertyExpression, constantExpression); - break; + switch (op) + { + case StringFilterOperator.Contains: + comparisonExpression = Expression.Call(notNullCheck(propertyExpression), "Contains", null, constantExpression); + break; + case StringFilterOperator.StartsWith: + comparisonExpression = Expression.Call(notNullCheck(propertyExpression), "StartsWith", null, constantExpression); + break; + case StringFilterOperator.EndsWith: + comparisonExpression = Expression.Call(notNullCheck(propertyExpression), "EndsWith", null, constantExpression); + break; + default: + comparisonExpression = Expression.Equal(propertyExpression, constantExpression); + break; + } } - var lambda = Expression.Lambda(comparisonExpression, parameter); + var lambda = Expression.Lambda(comparisonExpression!, parameter); result = source.Provider.CreateQuery(Expression.Call( typeof(Queryable), "Where", @@ -1279,13 +1476,16 @@ namespace Radzen /// System.String. public static string ToODataFilterString(this RadzenDataFilter dataFilter) { + ArgumentNullException.ThrowIfNull(dataFilter); Func canFilter = (c) => dataFilter.properties.Where(col => col.Property == c.Property).FirstOrDefault()?.FilterPropertyType != null && - (!(c.FilterValue == null || c.FilterValue as string == string.Empty) + (!string.IsNullOrEmpty(c.FilterValue as string) || c.FilterOperator == FilterOperator.IsNotNull || c.FilterOperator == FilterOperator.IsNull || c.FilterOperator == FilterOperator.IsEmpty || c.FilterOperator == FilterOperator.IsNotEmpty) && c.Property != null; - if (dataFilter.Filters.Concat(dataFilter.Filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).Any()) + var applicableFilters = dataFilter.Filters.Concat(dataFilter.Filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).ToList(); + + if (applicableFilters.Count > 0) { var filterExpressions = new List(); @@ -1300,8 +1500,8 @@ namespace Radzen AddODataExpression(canFilter, filter, ref filterExpressions, dataFilter.LogicalFilterOperator, dataFilter.FilterCaseSensitivity); } - return filterExpressions.Any() ? - string.Join($" {dataFilter.LogicalFilterOperator.ToString().ToLower()} ", filterExpressions) + return filterExpressions.Count > 0 ? + string.Join($" {dataFilter.LogicalFilterOperator.ToString().ToLower(CultureInfo.InvariantCulture)} ", filterExpressions) : ""; } return ""; @@ -1318,13 +1518,16 @@ namespace Radzen LogicalFilterOperator logicalFilterOperator = LogicalFilterOperator.And, FilterCaseSensitivity filterCaseSensitivity = FilterCaseSensitivity.Default) { + ArgumentNullException.ThrowIfNull(filters); Func canFilter = (c) => - (!(c.FilterValue == null || c.FilterValue as string == string.Empty) + (!string.IsNullOrEmpty(c.FilterValue as string) || c.FilterOperator == FilterOperator.IsNotNull || c.FilterOperator == FilterOperator.IsNull || c.FilterOperator == FilterOperator.IsEmpty || c.FilterOperator == FilterOperator.IsNotEmpty) && c.Property != null; - if (filters.Concat(filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).Any()) + var applicableFilters = filters.Concat(filters.SelectManyRecursive(i => i.Filters ?? Enumerable.Empty())).Where(canFilter).ToList(); + + if (applicableFilters.Count > 0) { var filterExpressions = new List(); @@ -1333,8 +1536,8 @@ namespace Radzen AddODataExpression(canFilter, filter, ref filterExpressions, logicalFilterOperator, filterCaseSensitivity); } - return filterExpressions.Any() ? - string.Join($" {logicalFilterOperator.ToString().ToLower()} ", filterExpressions) + return filterExpressions.Count > 0 ? + string.Join($" {logicalFilterOperator.ToString().ToLower(CultureInfo.InvariantCulture)} ", filterExpressions) : ""; } return ""; @@ -1354,9 +1557,9 @@ namespace Radzen AddODataExpression(canFilter, f, ref innerFilterExpressions, logicalFilterOperator, filterCaseSensitivity); } - if (innerFilterExpressions.Any()) + if (innerFilterExpressions.Count > 0) { - filterExpressions.Add("(" + string.Join($" {filter.LogicalFilterOperator.ToString().ToLower()} ", innerFilterExpressions) + ")"); + filterExpressions.Add("(" + string.Join($" {filter.LogicalFilterOperator.ToString().ToLower(CultureInfo.InvariantCulture)} ", innerFilterExpressions) + ")"); } } else @@ -1440,7 +1643,7 @@ namespace Radzen { try { - value = Convert.ToDateTime(filter.FilterValue).ToString(CultureInfo.InvariantCulture); + value = Convert.ToDateTime(filter.FilterValue, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture); } catch { @@ -1451,7 +1654,7 @@ namespace Radzen } else if (filterPropertyType == typeof(bool) || filterPropertyType == typeof(bool?)) { - value = $"{value?.ToLower()}"; + value = value.ToLower(CultureInfo.InvariantCulture); } filterExpressions.Add($@"{property} {ODataFilterOperators[filter.FilterOperator.Value]} {value}"); @@ -1487,12 +1690,12 @@ namespace Radzen return result.Concat(result.SelectManyRecursive(selector)); } - private static List> GetFilterableColumns(IEnumerable> columns) + private static List> GetFilterableColumns(IEnumerable> columns) where T : notnull { return columns .Where(c => c.Filterable && c.FilterPropertyType != null - && (!(c.GetFilterValue() == null || c.GetFilterValue() as string == string.Empty) + && (!string.IsNullOrEmpty(c.GetFilterValue() as string) || !c.CanSetFilterValue() || c.HasCustomFilter()) && c.GetFilterProperty() != null) diff --git a/Radzen.Blazor/Radzen.Blazor.csproj b/Radzen.Blazor/Radzen.Blazor.csproj index 57bf2b29..de6d4584 100644 --- a/Radzen.Blazor/Radzen.Blazor.csproj +++ b/Radzen.Blazor/Radzen.Blazor.csproj @@ -3,6 +3,7 @@ true snupkg BL9993;BL0007;BL0005 + true net6.0;net7.0;net8.0;net9.0;net10.0 7.0 latest diff --git a/Radzen.Blazor/RadzenAIChat.razor.cs b/Radzen.Blazor/RadzenAIChat.razor.cs index 00b5dd58..e900bd62 100644 --- a/Radzen.Blazor/RadzenAIChat.razor.cs +++ b/Radzen.Blazor/RadzenAIChat.razor.cs @@ -22,18 +22,18 @@ namespace Radzen.Blazor { private List Messages { get; set; } = new(); private string CurrentInput { get; set; } = string.Empty; - private bool IsLoading { get; set; } = false; - private bool preventDefault = false; + private bool IsLoading { get; set; } + private bool preventDefault; private ElementReference inputElement; private ElementReference messagesContainer; private CancellationTokenSource cts = new(); - private string currentSessionId; + private string? currentSessionId; /// /// Gets or sets the session ID for maintaining conversation memory. If null, a new session will be created. /// [Parameter] - public string SessionId { get; set; } + public string? SessionId { get; set; } /// /// Event callback that is invoked when a session ID is created or retrieved. @@ -46,13 +46,13 @@ namespace Radzen.Blazor /// /// The attributes. [Parameter] - public IReadOnlyDictionary InputAttributes { get; set; } + public IReadOnlyDictionary? InputAttributes { get; set; } /// /// Gets or sets the title displayed in the chat header. /// [Parameter] - public string Title { get; set; } + public string? Title { get; set; } /// /// Gets or sets the placeholder text for the input field. @@ -82,13 +82,13 @@ namespace Radzen.Blazor /// Gets or sets the model name. /// [Parameter] - public string Model { get; set; } + public string? Model { get; set; } /// /// Gets or sets the system prompt. /// [Parameter] - public string SystemPrompt { get; set; } + public string? SystemPrompt { get; set; } /// /// Gets or sets the temperature. @@ -106,25 +106,25 @@ namespace Radzen.Blazor /// Gets or sets the endpoint URL for the AI service. /// [Parameter] - public string Endpoint { get; set; } + public string? Endpoint { get; set; } /// /// Gets or sets the proxy URL for the AI service. /// [Parameter] - public string Proxy { get; set; } + public string? Proxy { get; set; } /// /// Gets or sets the API key for authentication. /// [Parameter] - public string ApiKey { get; set; } + public string? ApiKey { get; set; } /// /// Gets or sets the API key header name. /// [Parameter] - public string ApiKeyHeader { get; set; } + public string? ApiKeyHeader { get; set; } /// /// Gets or sets whether to show the clear chat button. @@ -149,14 +149,14 @@ namespace Radzen.Blazor /// /// The message template. [Parameter] - public RenderFragment MessageTemplate { get; set; } + public RenderFragment? MessageTemplate { get; set; } /// /// Gets or sets the empty template shown when there are no messages. /// /// The empty template. [Parameter] - public RenderFragment EmptyTemplate { get; set; } + public RenderFragment? EmptyTemplate { get; set; } /// /// Gets or sets the maximum number of messages to keep in the chat. @@ -196,7 +196,7 @@ namespace Radzen.Blazor /// /// Gets the current session ID. /// - public string GetSessionId() => currentSessionId; + public string? GetSessionId() => currentSessionId; /// /// Adds a message to the chat. @@ -277,7 +277,7 @@ namespace Radzen.Blazor /// Optional proxy URL to override the configured proxy. /// Optional API key to override the configured API key. /// Optional API key header name to override the configured header. - public async Task SendMessage(string content, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null, string endpoint = null, string proxy = null, string apiKey = null, string apiKeyHeader = null) + public async Task SendMessage(string content, string? model = null, string? systemPrompt = null, double? temperature = null, int? maxTokens = null, string? endpoint = null, string? proxy = null, string? apiKey = null, string? apiKeyHeader = null) { if (string.IsNullOrWhiteSpace(content) || Disabled || IsLoading) return; @@ -317,60 +317,17 @@ namespace Radzen.Blazor await InvokeAsync(StateHasChanged); } - private async Task GetAIResponse(string userInput) - { - if (string.IsNullOrWhiteSpace(userInput)) - return; - - IsLoading = true; - cts.Cancel(); - cts = new CancellationTokenSource(); - - // Ensure we have a session ID - if (string.IsNullOrEmpty(currentSessionId)) - { - currentSessionId = SessionId ?? Guid.NewGuid().ToString(); - await SessionIdChanged.InvokeAsync(currentSessionId); - } - - // Add assistant message placeholder - var assistantMessage = AddMessage("", false); - assistantMessage.IsStreaming = true; - - try - { - var response = ""; - await foreach (var token in ChatService.GetCompletionsAsync(userInput, currentSessionId, cts.Token, Model, SystemPrompt, Temperature, MaxTokens, Endpoint, Proxy, ApiKey, ApiKeyHeader)) - { - response += token; - assistantMessage.Content = response; - await InvokeAsync(StateHasChanged); - } - - assistantMessage.IsStreaming = false; - await ResponseReceived.InvokeAsync(response); - await MessageAdded.InvokeAsync(assistantMessage); - } - catch (Exception ex) - { - assistantMessage.Content = $"Sorry, I encountered an error: {ex.Message}"; - assistantMessage.IsStreaming = false; - await InvokeAsync(StateHasChanged); - } - finally - { - IsLoading = false; - await InvokeAsync(StateHasChanged); - } - } - - private async Task GetAIResponse(string userInput, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null, string endpoint = null, string proxy = null, string apiKey = null, string apiKeyHeader = null) + private async Task GetAIResponse(string userInput, string? model = null, string? systemPrompt = null, double? temperature = null, int? maxTokens = null, string? endpoint = null, string? proxy = null, string? apiKey = null, string? apiKeyHeader = null) { if (string.IsNullOrWhiteSpace(userInput)) return; IsLoading = true; +#if NET8_0_OR_GREATER + await cts.CancelAsync(); +#else cts.Cancel(); +#endif cts = new CancellationTokenSource(); // Ensure we have a session ID @@ -432,7 +389,7 @@ namespace Radzen.Blazor // Update session ID if it changed if (!string.IsNullOrEmpty(SessionId) && SessionId != currentSessionId) { - currentSessionId = SessionId ?? Guid.NewGuid().ToString(); + currentSessionId = SessionId; await SessionIdChanged.InvokeAsync(currentSessionId); // Load conversation history for the new session @@ -448,7 +405,7 @@ namespace Radzen.Blazor private async Task OnKeyDown(KeyboardEventArgs e) { - if (e.Key == "Enter" && !e.ShiftKey) + if (e.Key == "Enter" && !e.ShiftKey && JSRuntime != null) { await JSRuntime.InvokeAsync("Radzen.setInputValue", inputElement, ""); preventDefault = true; @@ -476,7 +433,7 @@ namespace Radzen.Blazor /// protected override async Task OnAfterRenderAsync(bool firstRender) { - if (!firstRender && messagesContainer.Context != null) + if (!firstRender && messagesContainer.Context != null && JSRuntime != null) { // Scroll to bottom when new messages are added await JSRuntime.InvokeVoidAsync("eval", @@ -492,5 +449,16 @@ namespace Radzen.Blazor { return ClassList.Create("rz-chat").ToString(); } + + /// + public override void Dispose() + { + base.Dispose(); + + cts?.Cancel(); + cts?.Dispose(); + + GC.SuppressFinalize(this); + } } } diff --git a/Radzen.Blazor/RadzenAccordion.razor.cs b/Radzen.Blazor/RadzenAccordion.razor.cs index 140d9dff..dd02cd25 100644 --- a/Radzen.Blazor/RadzenAccordion.razor.cs +++ b/Radzen.Blazor/RadzenAccordion.razor.cs @@ -99,7 +99,7 @@ namespace Radzen.Blazor /// /// The items render fragment containing accordion item definitions. [Parameter] - public RenderFragment Items { get; set; } + public RenderFragment? Items { get; set; } List items = new List(); @@ -109,6 +109,8 @@ namespace Radzen.Blazor /// The item. public void AddItem(RadzenAccordionItem item) { + ArgumentNullException.ThrowIfNull(item); + if (items.IndexOf(item) == -1) { if (item.GetSelected()) @@ -127,9 +129,8 @@ namespace Radzen.Blazor /// The item. public void RemoveItem(RadzenAccordionItem item) { - if (items.Contains(item)) + if (items.Remove(item)) { - items.Remove(item); if (!disposed) { try { InvokeAsync(StateHasChanged); } catch { } @@ -158,6 +159,8 @@ namespace Radzen.Blazor /// true if the specified index is selected; otherwise, false. protected bool IsSelected(int index, RadzenAccordionItem item) { + ArgumentNullException.ThrowIfNull(item); + return item.GetSelected() == true; } @@ -170,6 +173,8 @@ namespace Radzen.Blazor /// If the relevant title is null or whitespace this method returns "Expand" or "Collapse". protected string ItemTitle(int index, RadzenAccordionItem item) { + ArgumentNullException.ThrowIfNull(item); + if (IsSelected(index, item)) { return string.IsNullOrWhiteSpace(item.CollapseTitle) ? "Collapse" : item.CollapseTitle; @@ -186,6 +191,8 @@ namespace Radzen.Blazor /// If the relevant aria-label is null or whitespace this method returns "Expand" or "Collapse". protected string ItemAriaLabel(int index, RadzenAccordionItem item) { + ArgumentNullException.ThrowIfNull(item); + if (IsSelected(index, item)) { return string.IsNullOrWhiteSpace(item.CollapseAriaLabel) ? "Collapse" : item.CollapseAriaLabel; diff --git a/Radzen.Blazor/RadzenAccordionItem.cs b/Radzen.Blazor/RadzenAccordionItem.cs index 5e7ec11a..4fe5dfe8 100644 --- a/Radzen.Blazor/RadzenAccordionItem.cs +++ b/Radzen.Blazor/RadzenAccordionItem.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components; +using System; using System.Threading.Tasks; namespace Radzen.Blazor @@ -13,21 +14,21 @@ namespace Radzen.Blazor /// /// The text. [Parameter] - public string Text { get; set; } + public string? Text { get; set; } /// /// Gets or sets the icon. /// /// The icon. [Parameter] - public string Icon { get; set; } + public string? Icon { get; set; } /// /// Gets or sets the icon color. /// /// The icon color. [Parameter] - public string IconColor { get; set; } + public string? IconColor { get; set; } /// /// Gets or sets a value indicating whether this is selected. @@ -65,42 +66,42 @@ namespace Radzen.Blazor /// /// The title attribute value of the expand button. [Parameter] - public string ExpandTitle { get; set; } + public string? ExpandTitle { get; set; } /// /// Gets or sets the title attribute of the collapse button. /// /// The title attribute value of the collapse button. [Parameter] - public string CollapseTitle { get; set; } + public string? CollapseTitle { get; set; } /// /// Gets or sets the aria-label attribute of the expand button. /// /// The aria-label attribute value of the expand button. [Parameter] - public string ExpandAriaLabel { get; set; } + public string? ExpandAriaLabel { get; set; } /// /// Gets or sets the aria-label attribute of the collapse button. /// /// The aria-label attribute value of the collapse button. [Parameter] - public string CollapseAriaLabel { get; set; } + public string? CollapseAriaLabel { get; set; } /// /// Gets or sets the child content. /// /// The child content. [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } /// /// Gets or sets the header content. /// /// The header content. [Parameter] - public RenderFragment Template { get; set; } + public RenderFragment? Template { get; set; } private bool visible = true; /// @@ -127,14 +128,14 @@ namespace Radzen.Blazor } } - RadzenAccordion _accordion; + RadzenAccordion? _accordion; /// /// Gets or sets the accordion. /// /// The accordion. [CascadingParameter] - public RadzenAccordion Accordion + public RadzenAccordion? Accordion { get { @@ -145,7 +146,10 @@ namespace Radzen.Blazor if (_accordion != value) { _accordion = value; - _accordion.AddItem(this); + if (_accordion != null) + { + _accordion.AddItem(this); + } } } } @@ -187,7 +191,7 @@ namespace Radzen.Blazor if (shouldRefresh) { - Accordion.Refresh(); + Accordion?.Refresh(); } } @@ -199,16 +203,18 @@ namespace Radzen.Blazor base.Dispose(); Accordion?.RemoveItem(this); + + GC.SuppressFinalize(this); } - internal string GetItemId() + internal string? GetItemId() { return GetId(); } internal string GetItemCssClass() { - return $"{GetCssClass()} {(Accordion.IsFocused(this) ? "rz-state-focused" : "")}".Trim(); + return $"{GetCssClass()} {(Accordion?.IsFocused(this) == true ? "rz-state-focused" : "")}".Trim(); } /// diff --git a/Radzen.Blazor/RadzenAlert.razor.cs b/Radzen.Blazor/RadzenAlert.razor.cs index 83c4a720..900c9921 100644 --- a/Radzen.Blazor/RadzenAlert.razor.cs +++ b/Radzen.Blazor/RadzenAlert.razor.cs @@ -67,7 +67,7 @@ namespace Radzen.Blazor /// /// The alert title. [Parameter] - public string Title { get; set; } + public string? Title { get; set; } /// /// Gets or sets the body text of the alert. @@ -75,7 +75,7 @@ namespace Radzen.Blazor /// /// The alert text content. [Parameter] - public string Text { get; set; } + public string? Text { get; set; } /// /// Gets or sets a custom Material icon name to display instead of the default contextual icon. @@ -84,7 +84,7 @@ namespace Radzen.Blazor /// /// The custom Material icon name. [Parameter] - public string Icon { get; set; } + public string? Icon { get; set; } /// /// Gets or sets a custom color for the alert icon. @@ -92,7 +92,7 @@ namespace Radzen.Blazor /// /// The icon color as a CSS color value. [Parameter] - public string IconColor { get; set; } + public string? IconColor { get; set; } /// /// Gets or sets the semantic style/severity of the alert. @@ -193,12 +193,27 @@ namespace Radzen.Blazor } /// - protected override string GetComponentCssClass() => ClassList.Create("rz-alert") - .Add($"rz-alert-{GetAlertSize()}") - .AddVariant(Variant) - .Add($"rz-{Enum.GetName(typeof(AlertStyle), AlertStyle).ToLowerInvariant()}") - .AddShade(Shade) - .ToString(); + protected override string GetComponentCssClass() + { + var classList = ClassList.Create("rz-alert") + .Add($"rz-alert-{GetAlertSize()}") + .AddVariant(Variant) + .AddShade(Shade); + + var alertStyleClass = GetAlertStyleClass(); + if (!string.IsNullOrEmpty(alertStyleClass)) + { + classList.Add(alertStyleClass); + } + + return classList.ToString(); + } + + string? GetAlertStyleClass() + { + var styleName = Enum.GetName(AlertStyle); + return !string.IsNullOrEmpty(styleName) ? $"rz-{styleName.ToLowerInvariant()}" : null; + } string GetIcon() => !string.IsNullOrEmpty(Icon) ? Icon diff --git a/Radzen.Blazor/RadzenAppearanceToggle.razor.cs b/Radzen.Blazor/RadzenAppearanceToggle.razor.cs index 7dbb75bb..f683b68b 100644 --- a/Radzen.Blazor/RadzenAppearanceToggle.razor.cs +++ b/Radzen.Blazor/RadzenAppearanceToggle.razor.cs @@ -10,7 +10,7 @@ namespace Radzen.Blazor public partial class RadzenAppearanceToggle : RadzenComponent { [Inject] - private ThemeService ThemeService { get; set; } + private ThemeService? ThemeService { get; set; } /// /// Gets or sets the switch button variant. /// @@ -43,15 +43,15 @@ namespace Radzen.Blazor /// Gets or sets the light theme. Not set by default - the component uses the light version of the current theme. /// [Parameter] - public string LightTheme { get; set; } + public string? LightTheme { get; set; } /// /// Gets or sets the dark theme. Not set by default - the component uses the dark version of the current theme. /// [Parameter] - public string DarkTheme { get; set; } + public string? DarkTheme { get; set; } - private string CurrentLightTheme => LightTheme ?? ThemeService.Theme?.ToLowerInvariant() switch + private string CurrentLightTheme => LightTheme ?? ThemeService?.Theme?.ToLowerInvariant() switch { "dark" => "default", "material-dark" => "material", @@ -60,10 +60,10 @@ namespace Radzen.Blazor "software-dark" => "software", "humanistic-dark" => "humanistic", "standard-dark" => "standard", - _ => ThemeService.Theme, + _ => ThemeService?.Theme ?? string.Empty, }; - private string CurrentDarkTheme => DarkTheme ?? ThemeService.Theme?.ToLowerInvariant() switch + private string CurrentDarkTheme => DarkTheme ?? ThemeService?.Theme?.ToLowerInvariant() switch { "default" => "dark", "material" => "material-dark", @@ -72,7 +72,7 @@ namespace Radzen.Blazor "software" => "software-dark", "humanistic" => "humanistic-dark", "standard" => "standard-dark", - _ => ThemeService.Theme, + _ => ThemeService?.Theme ?? string.Empty, }; private bool value; @@ -82,21 +82,27 @@ namespace Radzen.Blazor { base.OnInitialized(); - ThemeService.ThemeChanged += OnThemeChanged; + if (ThemeService != null) + { + ThemeService.ThemeChanged += OnThemeChanged; - value = ThemeService.Theme != CurrentDarkTheme; + value = ThemeService.Theme != CurrentDarkTheme; + } } private void OnThemeChanged() { - value = ThemeService.Theme != CurrentDarkTheme; + if (ThemeService != null) + { + value = ThemeService.Theme != CurrentDarkTheme; + } StateHasChanged(); } void OnChange(bool value) { - ThemeService.SetTheme(value ? CurrentLightTheme : CurrentDarkTheme); + ThemeService?.SetTheme(value ? CurrentLightTheme : CurrentDarkTheme); } private string Icon => value ? "dark_mode" : "light_mode"; @@ -106,7 +112,12 @@ namespace Radzen.Blazor { base.Dispose(); - ThemeService.ThemeChanged -= OnThemeChanged; + if (ThemeService != null) + { + ThemeService.ThemeChanged -= OnThemeChanged; + } + + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/Radzen.Blazor/RadzenArcGauge.razor b/Radzen.Blazor/RadzenArcGauge.razor index 91c1e4ae..3f971225 100644 --- a/Radzen.Blazor/RadzenArcGauge.razor +++ b/Radzen.Blazor/RadzenArcGauge.razor @@ -43,7 +43,13 @@ string ValueStyle(RadzenArcGaugeScaleValue value) { - return $"left: {value.Scale.CurrentCenter.X.ToInvariantString()}px; top: {value.Scale.CurrentCenter.Y.ToInvariantString()}px"; + var scale = value?.Scale; + if (scale == null) + { + return "left: 0px; top: 0px"; + } + + return $"left: {scale.CurrentCenter.X.ToInvariantString()}px; top: {scale.CurrentCenter.Y.ToInvariantString()}px"; } internal void AddValue(RadzenArcGaugeScaleValue value) diff --git a/Radzen.Blazor/RadzenArcGaugeScale.razor.cs b/Radzen.Blazor/RadzenArcGaugeScale.razor.cs index 0d8c0324..2281d58b 100644 --- a/Radzen.Blazor/RadzenArcGaugeScale.razor.cs +++ b/Radzen.Blazor/RadzenArcGaugeScale.razor.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Components; using Radzen.Blazor.Rendering; using System; +using System.Globalization; namespace Radzen.Blazor { @@ -14,14 +15,14 @@ namespace Radzen.Blazor /// /// The gauge. [CascadingParameter] - public RadzenArcGauge Gauge { get; set; } + public RadzenArcGauge? Gauge { get; set; } /// /// Gets or sets the stroke. /// /// The stroke. [Parameter] - public string Stroke { get; set; } + public string Stroke { get; set; } = string.Empty; /// /// Gets or sets the width of the stroke. @@ -35,7 +36,7 @@ namespace Radzen.Blazor /// /// The child content. [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } /// /// Gets or sets the length of the tick. @@ -63,14 +64,14 @@ namespace Radzen.Blazor /// /// The format string. [Parameter] - public string FormatString { get; set; } + public string FormatString { get; set; } = string.Empty; /// /// Gets or sets the fill. /// /// The fill. [Parameter] - public string Fill { get; set; } + public string Fill { get; set; } = string.Empty; /// /// Gets or sets the height. @@ -84,7 +85,7 @@ namespace Radzen.Blazor /// /// The formatter. [Parameter] - public Func Formatter { get; set; } = value => value.ToString(); + public Func Formatter { get; set; } = value => value.ToString(CultureInfo.CurrentCulture); /// /// Gets or sets the start angle. @@ -209,13 +210,19 @@ namespace Radzen.Blazor { get { - var radius = Math.Min(Gauge.Width.Value, Gauge.Height.Value) / 2 - Margin * 2; + var gauge = Gauge; + if (gauge?.Width == null || gauge.Height == null) + { + return 0; + } + + var radius = Math.Min(gauge.Width.Value, gauge.Height.Value) / 2 - Margin * 2; radius *= Radius; if (TickPosition == GaugeTickPosition.Outside) { - radius -= TextMeasurer.TextWidth(Max.ToString(), 16); + radius -= TextMeasurer.TextWidth(Max.ToString(CultureInfo.InvariantCulture), 16); } return radius; @@ -243,11 +250,17 @@ namespace Radzen.Blazor { get { - var radius = Math.Min(Gauge.Width.Value, Gauge.Height.Value) / 2 - Margin * 2; + var gauge = Gauge; + if (gauge?.Width == null || gauge.Height == null) + { + return 0; + } + + var radius = Math.Min(gauge.Width.Value, gauge.Height.Value) / 2 - Margin * 2; if (TickPosition == GaugeTickPosition.Outside) { - radius -= TextMeasurer.TextWidth(Max.ToString(), 16); + radius -= TextMeasurer.TextWidth(Max.ToString(CultureInfo.InvariantCulture), 16); } return radius * Height; @@ -262,8 +275,14 @@ namespace Radzen.Blazor { get { - var x = X * Gauge.Width; - var y = Y * Gauge.Height; + var gauge = Gauge; + if (gauge?.Width == null || gauge.Height == null) + { + return new Point(); + } + + var x = X * gauge.Width; + var y = Y * gauge.Height; return new Point { X = x.Value, Y = y.Value }; } diff --git a/Radzen.Blazor/RadzenArcGaugeScaleValue.razor b/Radzen.Blazor/RadzenArcGaugeScaleValue.razor index 7aca0ec3..905777f2 100644 --- a/Radzen.Blazor/RadzenArcGaugeScaleValue.razor +++ b/Radzen.Blazor/RadzenArcGaugeScaleValue.razor @@ -2,18 +2,24 @@ @using Radzen.Blazor.Rendering - +@{ + var scale = Scale; +} +@if (scale != null) + { + + } diff --git a/Radzen.Blazor/RadzenArcGaugeScaleValue.razor.cs b/Radzen.Blazor/RadzenArcGaugeScaleValue.razor.cs index 4e152ffb..29ea0038 100644 --- a/Radzen.Blazor/RadzenArcGaugeScaleValue.razor.cs +++ b/Radzen.Blazor/RadzenArcGaugeScaleValue.razor.cs @@ -20,14 +20,14 @@ namespace Radzen.Blazor /// /// The scale. [CascadingParameter] - public RadzenArcGaugeScale Scale { get; set; } + public RadzenArcGaugeScale? Scale { get; set; } /// /// Gets or sets the stroke. /// /// The stroke. [Parameter] - public string Stroke { get; set; } + public string Stroke { get; set; } = string.Empty; /// /// Gets or sets the width of the stroke. @@ -41,7 +41,7 @@ namespace Radzen.Blazor /// /// The fill. [Parameter] - public string Fill { get; set; } + public string Fill { get; set; } = string.Empty; /// /// Gets or sets a value indicating whether to show value. @@ -55,28 +55,28 @@ namespace Radzen.Blazor /// /// The format string. [Parameter] - public string FormatString { get; set; } + public string FormatString { get; set; } = string.Empty; /// /// Gets or sets the template. /// /// The template. [Parameter] - public RenderFragment Template { get; set; } + public RenderFragment? Template { get; set; } /// /// Gets or sets the gauge. /// /// The gauge. [CascadingParameter] - public RadzenArcGauge Gauge { get; set; } + public RadzenArcGauge? Gauge { get; set; } /// protected override void OnInitialized() { base.OnInitialized(); - Gauge.AddValue(this); + Gauge?.AddValue(this); } /// public override async Task SetParametersAsync(ParameterView parameters) @@ -92,7 +92,7 @@ namespace Radzen.Blazor if (shouldRefresh) { - Gauge.Reload(); + Gauge?.Reload(); } } } diff --git a/Radzen.Blazor/RadzenAreaSeries.razor b/Radzen.Blazor/RadzenAreaSeries.razor index 310071a9..02004900 100644 --- a/Radzen.Blazor/RadzenAreaSeries.razor +++ b/Radzen.Blazor/RadzenAreaSeries.razor @@ -8,6 +8,7 @@ @code { public override RenderFragment Render(ScaleBase categoryScale, ScaleBase valueScale) { + var chart = RequireChart(); var category = ComposeCategory(categoryScale); var value = ComposeValue(valueScale); @@ -22,8 +23,8 @@ return new Point { X = x, Y = y, Data = item }; }); - var ticks = Chart.CategoryScale.Ticks(Chart.CategoryAxis.TickDistance); - var index = Chart.Series.IndexOf(this); + var ticks = chart.CategoryScale.Ticks(chart.CategoryAxis.TickDistance); + var index = chart.Series.IndexOf(this); var className = $"rz-area-series rz-series-{index}"; return @@ -32,10 +33,10 @@ { var x1 = category(Items.First()).ToInvariantString(); var x2 = category(Items.Last()).ToInvariantString(); - var valueTicks = Chart.ValueScale.Ticks(Chart.ValueAxis.TickDistance); + var valueTicks = chart.ValueScale.Ticks(chart.ValueAxis.TickDistance); var start = Math.Max(0, valueTicks.Start); - var y = Chart.ValueScale.Scale(start).ToInvariantString(); - var style = $"clip-path: url(#{Chart.ClipPath}); -webkit-clip-path: url(#{Chart.ClipPath});"; + var y = chart.ValueScale.Scale(start).ToInvariantString(); + var style = $"clip-path: url(#{chart.ClipPath}); -webkit-clip-path: url(#{chart.ClipPath});"; var path = pathGenerator.Path(data); var area = $"M {x1} {y} {path} L {x2} {y}"; var line = $"M {path}"; diff --git a/Radzen.Blazor/RadzenAreaSeries.razor.cs b/Radzen.Blazor/RadzenAreaSeries.razor.cs index 50a12b8e..e3308361 100644 --- a/Radzen.Blazor/RadzenAreaSeries.razor.cs +++ b/Radzen.Blazor/RadzenAreaSeries.razor.cs @@ -16,14 +16,14 @@ namespace Radzen.Blazor /// /// The stroke. [Parameter] - public string Stroke { get; set; } + public string? Stroke { get; set; } /// /// Specifies the fill (background color) of the area series. /// /// The fill. [Parameter] - public string Fill { get; set; } + public string? Fill { get; set; } /// /// Gets or sets the pixel width of the line. Set to 2 by default. @@ -59,7 +59,7 @@ namespace Radzen.Blazor { get { - return Stroke; + return Stroke ?? string.Empty; } } @@ -86,13 +86,19 @@ namespace Radzen.Blazor /// public override bool Contains(double x, double y, double tolerance) { - var category = ComposeCategory(Chart.CategoryScale); - var value = ComposeValue(Chart.ValueScale); + var chart = Chart; + if (chart == null) + { + return false; + } + + var category = ComposeCategory(chart.CategoryScale); + var value = ComposeValue(chart.ValueScale); var points = Items.Select(item => new Point { X = category(item), Y = value(item) }).ToArray(); - var valueTicks = Chart.ValueScale.Ticks(Chart.ValueAxis.TickDistance); - var axisY = Chart.ValueScale.Scale(Math.Max(0, valueTicks.Start)); + var valueTicks = chart.ValueScale.Ticks(chart.ValueAxis.TickDistance); + var axisY = chart.ValueScale.Scale(Math.Max(0, valueTicks.Start)); if (points.Length > 0) { diff --git a/Radzen.Blazor/RadzenAutoComplete.razor.cs b/Radzen.Blazor/RadzenAutoComplete.razor.cs index cfdd8814..c70e58d0 100644 --- a/Radzen.Blazor/RadzenAutoComplete.razor.cs +++ b/Radzen.Blazor/RadzenAutoComplete.razor.cs @@ -33,14 +33,14 @@ namespace Radzen.Blazor /// public partial class RadzenAutoComplete : DataBoundFormComponent { - object selectedItem = null; + object? selectedItem; /// /// Gets or sets the selected item. /// /// The selected item. [Parameter] - public object SelectedItem + public object? SelectedItem { get { @@ -67,7 +67,7 @@ namespace Radzen.Blazor /// /// The attributes. [Parameter] - public IReadOnlyDictionary InputAttributes { get; set; } + public IReadOnlyDictionary? InputAttributes { get; set; } /// /// Gets or sets a value indicating whether this is multiline. @@ -95,7 +95,7 @@ namespace Radzen.Blazor /// /// The template. [Parameter] - public RenderFragment Template { get; set; } + public RenderFragment? Template { get; set; } /// /// Gets or sets the minimum length. @@ -135,15 +135,17 @@ namespace Radzen.Blazor /// protected ElementReference list; - string customSearchText; + string? customSearchText; int selectedIndex = -1; /// - /// Handles the event. + /// Handles the FilterKeyPress event. /// /// The instance containing the event data. protected async Task OnFilterKeyPress(KeyboardEventArgs args) { + ArgumentNullException.ThrowIfNull(args); + var items = (LoadData.HasDelegate ? Data != null ? Data : Enumerable.Empty() : (View != null ? View : Enumerable.Empty())).OfType(); var key = args.Code != null ? args.Code : args.Key; @@ -152,7 +154,10 @@ namespace Radzen.Blazor { try { - selectedIndex = await JSRuntime.InvokeAsync("Radzen.focusListItem", search, list, key == "ArrowDown", selectedIndex); + if (JSRuntime != null) + { + selectedIndex = await JSRuntime.InvokeAsync("Radzen.focusListItem", search, list, key == "ArrowDown", selectedIndex); + } } catch (Exception) { @@ -167,12 +172,12 @@ namespace Radzen.Blazor selectedIndex = -1; } - if (key == "Tab") + if (key == "Tab" && JSRuntime != null) { await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID); } } - else if (key == "Escape") + else if (key == "Escape" && JSRuntime != null) { await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID); } @@ -186,6 +191,7 @@ namespace Radzen.Blazor async Task DebounceFilter() { + if (JSRuntime == null) return; var value = await JSRuntime.InvokeAsync("Radzen.getInputValue", search); value = $"{value}"; @@ -218,7 +224,10 @@ namespace Radzen.Blazor private async Task OnSelectItem(object item) { - await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID); + if (JSRuntime != null) + { + await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID); + } await SelectItem(item); } @@ -227,7 +236,7 @@ namespace Radzen.Blazor /// Gets the IQueryable. /// /// The IQueryable. - protected override IQueryable Query + protected override IQueryable? Query { get { @@ -239,13 +248,13 @@ namespace Radzen.Blazor /// Gets the view - the Query with filtering applied. /// /// The view. - protected override IEnumerable View + protected override IEnumerable? View { get { if (Query != null) { - return Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity); + return TextProperty != null ? Query.Where(TextProperty, searchText ?? string.Empty, FilterOperator, FilterCaseSensitivity) : Query; } return null; @@ -253,11 +262,13 @@ namespace Radzen.Blazor } /// - /// Handles the event. + /// Handles the Change event. /// /// The instance containing the event data. protected async System.Threading.Tasks.Task OnChange(ChangeEventArgs args) { + ArgumentNullException.ThrowIfNull(args); + Value = args.Value?.ToString(); await ValueChanged.InvokeAsync($"{Value}"); @@ -309,10 +320,12 @@ namespace Radzen.Blazor { base.Dispose(); - if (IsJSRuntimeAvailable) + if (IsJSRuntimeAvailable && JSRuntime != null) { JSRuntime.InvokeVoid("Radzen.destroyPopup", PopupID); } + + GC.SuppressFinalize(this); } private bool firstRender = true; @@ -356,7 +369,7 @@ namespace Radzen.Blazor Value = parameters.GetValueOrDefault(nameof(Value)); } - if (shouldClose && !firstRender) + if (shouldClose && !firstRender && JSRuntime != null) { await JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", PopupID); } diff --git a/Radzen.Blazor/RadzenAxisTitle.cs b/Radzen.Blazor/RadzenAxisTitle.cs index 3b9c7278..8a205063 100644 --- a/Radzen.Blazor/RadzenAxisTitle.cs +++ b/Radzen.Blazor/RadzenAxisTitle.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components; +using System; namespace Radzen.Blazor { @@ -12,7 +13,7 @@ namespace Radzen.Blazor /// /// The text. [Parameter] - public string Text { get; set; } + public string? Text { get; set; } /// /// Sets the axis with this configuration applies to. @@ -23,6 +24,8 @@ namespace Radzen.Blazor { set { + ArgumentNullException.ThrowIfNull(value); + value.Title = this; } } diff --git a/Radzen.Blazor/RadzenBadge.razor.cs b/Radzen.Blazor/RadzenBadge.razor.cs index da486d8a..0e1b25a9 100644 --- a/Radzen.Blazor/RadzenBadge.razor.cs +++ b/Radzen.Blazor/RadzenBadge.razor.cs @@ -46,7 +46,7 @@ namespace Radzen.Blazor /// /// The child content render fragment. [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } /// /// Gets or sets the text content displayed in the badge. @@ -54,7 +54,7 @@ namespace Radzen.Blazor /// /// The badge text. [Parameter] - public string Text { get; set; } + public string? Text { get; set; } /// /// Gets or sets the semantic color style of the badge. diff --git a/Radzen.Blazor/RadzenBarOptions.cs b/Radzen.Blazor/RadzenBarOptions.cs index 0e354bfa..d0fc5882 100644 --- a/Radzen.Blazor/RadzenBarOptions.cs +++ b/Radzen.Blazor/RadzenBarOptions.cs @@ -31,7 +31,10 @@ namespace Radzen.Blazor /// protected override void Initialize() { - Chart.BarOptions = this; + if (Chart != null) + { + Chart.BarOptions = this; + } } /// diff --git a/Radzen.Blazor/RadzenBarSeries.razor b/Radzen.Blazor/RadzenBarSeries.razor index 11b705ca..358e4f9a 100644 --- a/Radzen.Blazor/RadzenBarSeries.razor +++ b/Radzen.Blazor/RadzenBarSeries.razor @@ -12,28 +12,44 @@ @code { public override RenderFragment Render(ScaleBase categoryScale, ScaleBase valueScale) { + var chart = Chart; + if (chart == null) + { + return _ => { }; + } + var value = ComposeValue(categoryScale); var category = ComposeCategory(valueScale); - var ticks = Chart.CategoryScale.Ticks(Chart.ValueAxis.TickDistance); - var x0 = Chart.CategoryScale.Scale(Math.Max(0, ticks.Start)); - var style = $"clip-path: url(#{Chart.ClipPath}); -webkit-clip-path: url(#{Chart.ClipPath});"; + var ticks = chart.CategoryScale.Ticks(chart.ValueAxis.TickDistance); + var x0 = chart.CategoryScale.Scale(Math.Max(0, ticks.Start)); + var style = $"clip-path: url(#{chart.ClipPath}); -webkit-clip-path: url(#{chart.ClipPath});"; var barSeries = VisibleBarSeries; + if (barSeries.Count == 0) + { + return _ => { }; + } + var index = barSeries.IndexOf(this); - var padding = Chart.BarOptions.Margin; + if (index < 0) + { + return _ => { }; + } + var barOptions = chart.BarOptions; + var padding = barOptions?.Margin ?? 0; var barHeight = BandHeight; - var height = barHeight / barSeries.Count() - padding + padding / barSeries.Count();; - var className = $"rz-bar-series rz-series-{Chart.Series.IndexOf(this)}"; + var height = barHeight / barSeries.Count - padding + padding / barSeries.Count; + var className = $"rz-bar-series rz-series-{chart.Series.IndexOf(this)}"; return @ - @foreach(var data in Items) + @foreach (var data in Items) { var y = category(data) - barHeight / 2 + index * height + index * padding; var x = value(data); var itemValue = Value(data); - var radius = Chart.BarOptions.Radius; + var radius = barOptions?.Radius ?? 0; var width = Math.Abs(x0 - x); if (radius > height / 2 || radius > width) @@ -43,11 +59,11 @@ var r = radius.ToInvariantString(); - var path = $"M {x0.ToInvariantString()} {y.ToInvariantString()} L {(x-radius).ToInvariantString()} {y.ToInvariantString()} A {r} {r} 0 0 1 {x.ToInvariantString()} {(y+radius).ToInvariantString()} L {x.ToInvariantString()} {(y+height-radius).ToInvariantString()} A {r} {r} 0 0 1 {(x-radius).ToInvariantString()} {(y + height).ToInvariantString()} L {x0.ToInvariantString()} {(y+height).ToInvariantString()} Z"; + var path = $"M {x0.ToInvariantString()} {y.ToInvariantString()} L {(x - radius).ToInvariantString()} {y.ToInvariantString()} A {r} {r} 0 0 1 {x.ToInvariantString()} {(y + radius).ToInvariantString()} L {x.ToInvariantString()} {(y + height - radius).ToInvariantString()} A {r} {r} 0 0 1 {(x - radius).ToInvariantString()} {(y + height).ToInvariantString()} L {x0.ToInvariantString()} {(y + height).ToInvariantString()} Z"; if (x < x0) { - path = $"M {x0.ToInvariantString()} {y.ToInvariantString()} L {(x+radius).ToInvariantString()} {y.ToInvariantString()} A {r} {r} 0 0 0 {x.ToInvariantString()} {(y+radius).ToInvariantString()} L {x.ToInvariantString()} {(y+height-radius).ToInvariantString()} A {r} {r} 0 0 0 {(x+radius).ToInvariantString()} {(y + height).ToInvariantString()} L {x0.ToInvariantString()} {(y+height).ToInvariantString()} Z"; + path = $"M {x0.ToInvariantString()} {y.ToInvariantString()} L {(x + radius).ToInvariantString()} {y.ToInvariantString()} A {r} {r} 0 0 0 {x.ToInvariantString()} {(y + radius).ToInvariantString()} L {x.ToInvariantString()} {(y + height - radius).ToInvariantString()} A {r} {r} 0 0 0 {(x + radius).ToInvariantString()} {(y + height).ToInvariantString()} L {x0.ToInvariantString()} {(y + height).ToInvariantString()} Z"; } var fill = PickColor(Items.IndexOf(data), Fills, Fill, FillRange, itemValue); var stroke = PickColor(Items.IndexOf(data), Strokes, Stroke, StrokeRange, itemValue); diff --git a/Radzen.Blazor/RadzenBarSeries.razor.cs b/Radzen.Blazor/RadzenBarSeries.razor.cs index ac6ccea9..85edad55 100644 --- a/Radzen.Blazor/RadzenBarSeries.razor.cs +++ b/Radzen.Blazor/RadzenBarSeries.razor.cs @@ -17,28 +17,28 @@ namespace Radzen.Blazor /// /// The fill. [Parameter] - public string Fill { get; set; } + public string? Fill { get; set; } /// /// Specifies a list of colors that will be used to set the individual bar backgrounds. /// /// The fills. [Parameter] - public IEnumerable Fills { get; set; } + public IEnumerable? Fills { get; set; } /// /// Specifies the stroke (border color) of the bar series. /// /// The stroke. [Parameter] - public string Stroke { get; set; } + public string? Stroke { get; set; } /// /// Specifies a list of colors that will be used to set the individual bar borders. /// /// The strokes. [Parameter] - public IEnumerable Strokes { get; set; } + public IEnumerable? Strokes { get; set; } /// /// Gets or sets the width of the stroke (border). @@ -59,21 +59,21 @@ namespace Radzen.Blazor /// /// The color range of the fill. [Parameter] - public IList FillRange { get; set; } + public IList? FillRange { get; set; } /// /// Gets or sets the color range of the stroke. /// /// The color range of the stroke. [Parameter] - public IList StrokeRange { get; set; } + public IList? StrokeRange { get; set; } /// public override string Color { get { - return Fill; + return Fill ?? string.Empty; } } @@ -99,7 +99,13 @@ namespace Radzen.Blazor { get { - return Chart.Series.Where(series => series is IChartBarSeries).Cast().ToList(); + var chart = Chart; + if (chart == null) + { + return new List(); + } + + return chart.Series.Where(series => series is IChartBarSeries).Cast().ToList(); } } @@ -136,14 +142,26 @@ namespace Radzen.Blazor get { var barSeries = VisibleBarSeries; - - if (Chart.BarOptions.Height.HasValue) + if (barSeries.Count == 0) { - return Chart.BarOptions.Height.Value * barSeries.Count; + return 0; + } + + var chart = Chart; + if (chart == null) + { + return 0; + } + + var barOptions = chart.BarOptions; + + if (barOptions?.Height.HasValue == true) + { + return barOptions.Height.Value * barSeries.Count; } else { - var availableHeight = Chart.ValueScale.OutputSize; // - (Chart.ValueAxis.Padding * 2); + var availableHeight = chart.ValueScale.OutputSize; // - (Chart.ValueAxis.Padding * 2); var bands = barSeries.Cast().Max(series => series.Count) + 2; return availableHeight / bands; } @@ -167,7 +185,13 @@ namespace Radzen.Blazor /// internal override double TooltipX(TItem item) { - var value = Chart.CategoryScale.Compose(Value); + var chart = Chart; + if (chart == null) + { + return 0; + } + + var value = chart.CategoryScale.Compose(Value); var x = value(item); return x; @@ -176,29 +200,52 @@ namespace Radzen.Blazor /// protected override string TooltipValue(TItem item) { - return Chart.ValueAxis.Format(Chart.CategoryScale, Chart.CategoryScale.Value(Value(item))); + var chart = Chart; + if (chart == null) + { + return string.Empty; + } + + return chart.ValueAxis.Format(chart.CategoryScale, chart.CategoryScale.Value(Value(item))); } /// protected override string TooltipTitle(TItem item) { - var category = Category(Chart.ValueScale); - return Chart.CategoryAxis.Format(Chart.ValueScale, Chart.ValueScale.Value(category(item))); + var chart = Chart; + if (chart == null) + { + return string.Empty; + } + + var category = Category(chart.ValueScale); + return chart.CategoryAxis.Format(chart.ValueScale, chart.ValueScale.Value(category(item))); } /// public override (object, Point) DataAt(double x, double y) { - var value = ComposeValue(Chart.CategoryScale); - var category = ComposeCategory(Chart.ValueScale); - var ticks = Chart.CategoryScale.Ticks(Chart.ValueAxis.TickDistance); - var x0 = Chart.CategoryScale.Scale(Math.Max(0, ticks.Start)); + var chart = Chart; + if (chart == null) + { + return (default!, new Point()); + } + + var value = ComposeValue(chart.CategoryScale); + var category = ComposeCategory(chart.ValueScale); + var ticks = chart.CategoryScale.Ticks(chart.ValueAxis.TickDistance); + var x0 = chart.CategoryScale.Scale(Math.Max(0, ticks.Start)); var barSeries = VisibleBarSeries; var index = barSeries.IndexOf(this); - var padding = Chart.BarOptions.Margin; + if (barSeries.Count == 0 || index < 0) + { + return (default!, new Point()); + } + + var padding = chart.BarOptions?.Margin ?? 0; var bandHeight = BandHeight; - var height = bandHeight / barSeries.Count() - padding + padding / barSeries.Count(); + var height = barSeries.Count > 0 ? bandHeight / barSeries.Count - padding + padding / barSeries.Count : 0; foreach (var data in Items) { @@ -210,22 +257,33 @@ namespace Radzen.Blazor if (startX <= x && x <= endX && startY <= y && y <= endY) { - return (data, new Point() { X = x, Y = y }); + return (data!, new Point() { X = x, Y = y }); } } - return (null, null); + return (default!, new Point()); } /// internal override double TooltipY(TItem item) { - var category = ComposeCategory(Chart.ValueScale); + var chart = Chart; + if (chart == null) + { + return 0; + } + + var category = ComposeCategory(chart.ValueScale); var barSeries = VisibleBarSeries; var index = barSeries.IndexOf(this); - var padding = Chart.BarOptions.Margin; + if (barSeries.Count == 0 || index < 0) + { + return 0; + } + + var padding = chart.BarOptions?.Margin ?? 0; var bandHeight = BandHeight; - var height = bandHeight / barSeries.Count() - padding + padding / barSeries.Count(); + var height = barSeries.Count > 0 ? bandHeight / barSeries.Count - padding + padding / barSeries.Count : 0; var y = category(item) - bandHeight / 2 + index * height + index * padding; return y + height / 2; @@ -235,19 +293,27 @@ namespace Radzen.Blazor public override IEnumerable GetDataLabels(double offsetX, double offsetY) { var list = new List(); + var chart = Chart; + if (chart == null) + { + return list; + } (string Anchor, int Sign) position; - foreach (var d in Data) + if (Data != null) { - position = Value(d) < 0 ? ("end", -1) : Value(d) == 0 ? ("middle", 0) : ("start", 1); - - list.Add(new ChartDataLabel + foreach (var d in Data) { - Position = new Point() { X = TooltipX(d) + offsetX + (8 * position.Sign), Y = TooltipY(d) + offsetY }, - TextAnchor = position.Anchor, - Text = Chart.ValueAxis.Format(Chart.CategoryScale, Value(d)) - }); + position = Value(d) < 0 ? ("end", -1) : Value(d) == 0 ? ("middle", 0) : ("start", 1); + + list.Add(new ChartDataLabel + { + Position = new Point() { X = TooltipX(d) + offsetX + (8 * position.Sign), Y = TooltipY(d) + offsetY }, + TextAnchor = position.Anchor, + Text = chart.ValueAxis.Format(chart.CategoryScale, Value(d)) + }); + } } return list; diff --git a/Radzen.Blazor/RadzenBody.razor.cs b/Radzen.Blazor/RadzenBody.razor.cs index 57653316..aafd3e72 100644 --- a/Radzen.Blazor/RadzenBody.razor.cs +++ b/Radzen.Blazor/RadzenBody.razor.cs @@ -6,6 +6,8 @@ using Microsoft.AspNetCore.Components.Routing; using Microsoft.JSInterop; using System.Threading; using System.Runtime.ExceptionServices; +using System; +using System.Globalization; namespace Radzen.Blazor { @@ -21,7 +23,7 @@ namespace Radzen.Blazor /// /// The style. [Parameter] - public override string Style { get; set; } = DefaultStyle; + public override string? Style { get; set; } = DefaultStyle; /// protected override string GetComponentCssClass() => ClassList.Create("rz-body") @@ -42,7 +44,7 @@ namespace Radzen.Blazor /// The this component is nested in. /// [CascadingParameter] - public RadzenLayout Layout { get; set; } + public RadzenLayout? Layout { get; set; } /// /// Gets the style. @@ -57,10 +59,10 @@ namespace Radzen.Blazor if (!string.IsNullOrEmpty(Style)) { - var marginLeftStyle = Style.Split(';').Where(i => i.Split(':')[0].Contains("margin-left")).FirstOrDefault(); - if (!string.IsNullOrEmpty(marginLeftStyle) && marginLeftStyle.Contains("px")) + var marginLeftStyle = Style.Split(';').Where(i => i.Split(':')[0].Contains("margin-left", StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + if (!string.IsNullOrEmpty(marginLeftStyle) && marginLeftStyle.Contains("px", StringComparison.OrdinalIgnoreCase)) { - marginLeft = int.Parse(marginLeftStyle.Split(':')[1].Trim().Replace("px", "").Split('.')[0].Trim()); + marginLeft = int.Parse(marginLeftStyle.Split(':')[1].Trim().Replace("px", "", StringComparison.OrdinalIgnoreCase).Split('.')[0].Trim(), CultureInfo.InvariantCulture); } } @@ -72,7 +74,7 @@ namespace Radzen.Blazor if (!string.IsNullOrEmpty(style)) { - style = style.Replace(DefaultStyle, ""); + style = style.Replace(DefaultStyle, "", StringComparison.Ordinal); } return $"{style}"; @@ -94,30 +96,38 @@ namespace Radzen.Blazor public EventCallback ExpandedChanged { get; set; } [Inject] - NavigationManager NavigationManager { get; set; } + NavigationManager? NavigationManager { get; set; } /// protected override Task OnInitializedAsync() { - NavigationManager.LocationChanged += OnLocationChanged; + if (NavigationManager != null) + { + NavigationManager.LocationChanged += OnLocationChanged; + } return base.OnInitializedAsync(); } - private void OnLocationChanged(object sender, LocationChangedEventArgs e) + private void OnLocationChanged(object? sender, LocationChangedEventArgs e) { - if (IsJSRuntimeAvailable && Layout != null) + if (IsJSRuntimeAvailable && Layout != null && JSRuntime != null) { - JSRuntime.InvokeVoidAsync("eval", $"try{{document.getElementById('{GetId()}').scrollTop = 0}}catch(e){{}}"); + _ = JSRuntime.InvokeVoidAsync("eval", $"try{{document.getElementById('{GetId()}').scrollTop = 0}}catch(e){{}}"); } } /// public override void Dispose() { - NavigationManager.LocationChanged -= OnLocationChanged; + if (NavigationManager != null) + { + NavigationManager.LocationChanged -= OnLocationChanged; + } base.Dispose(); + + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/Radzen.Blazor/RadzenBreadCrumb.razor.cs b/Radzen.Blazor/RadzenBreadCrumb.razor.cs index 393b45b1..07f4bffd 100644 --- a/Radzen.Blazor/RadzenBreadCrumb.razor.cs +++ b/Radzen.Blazor/RadzenBreadCrumb.razor.cs @@ -37,7 +37,7 @@ namespace Radzen.Blazor /// /// The custom item template render fragment. [Parameter] - public RenderFragment Template { get; set; } + public RenderFragment? Template { get; set; } /// protected override string GetComponentCssClass() diff --git a/Radzen.Blazor/RadzenBreadCrumbItem.razor.cs b/Radzen.Blazor/RadzenBreadCrumbItem.razor.cs index e3e8b830..7191ed09 100644 --- a/Radzen.Blazor/RadzenBreadCrumbItem.razor.cs +++ b/Radzen.Blazor/RadzenBreadCrumbItem.razor.cs @@ -11,39 +11,39 @@ namespace Radzen.Blazor /// Cascaded Template Parameter from Component /// [CascadingParameter] - public RenderFragment Template { get; set; } + public RenderFragment? Template { get; set; } /// /// The Displayed Text /// [Parameter] - public string Text { get; set; } + public string? Text { get; set; } /// /// An optional Link to be rendendered /// [Parameter] - public string Path { get; set; } + public string? Path { get; set; } /// /// An optional Icon to be rendered /// [Parameter] - public string Icon { get; set; } + public string? Icon { get; set; } /// /// Gets or sets the icon color. /// /// The icon color. [Parameter] - public string IconColor { get; set; } + public string? IconColor { get; set; } /// /// Template Parameter used only for this Item /// Note: this overrides the Cascading Parameter /// [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } /// protected override string GetComponentCssClass() diff --git a/Radzen.Blazor/RadzenButton.razor b/Radzen.Blazor/RadzenButton.razor index 4c2efd66..127c1709 100644 --- a/Radzen.Blazor/RadzenButton.razor +++ b/Radzen.Blazor/RadzenButton.razor @@ -3,7 +3,7 @@ @if (Visible) {