diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ff5538ba..e9a11361 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -19,4 +19,7 @@ jobs:
dotnet-version: 3.1.201
- name: Dotnet Build
- run: dotnet build --configuration Release
\ No newline at end of file
+ run: dotnet build -c Release
+
+ - name: Dotnet Test
+ run: dotnet test -c Release
\ No newline at end of file
diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml
index a3e7ff53..0bdb5610 100644
--- a/.github/workflows/cicd.yml
+++ b/.github/workflows/cicd.yml
@@ -30,6 +30,9 @@ jobs:
- name: Dotnet Build
run: dotnet build -c Release
+ - name: Dotnet Test
+ run: dotnet test -c Release
+
- name: Dotnet Publish
working-directory: src/BlazorTable.Sample.Wasm
run: dotnet publish -c Release
@@ -42,7 +45,7 @@ jobs:
- name: Deploy to Test
id: netlify
- uses: ivanjosipovic/actions/cli@master
+ uses: netlify/actions/cli@master
with:
args: deploy --json -d src/BlazorTable.Sample.Wasm/bin/Release/netstandard2.1/publish/wwwroot
env:
@@ -51,6 +54,6 @@ jobs:
- name: Get Test Address
run: |
- $url = $(ConvertFrom-Json '${{ steps.netlify.outputs.output }}').deploy_url;
+ $url = '${{ steps.netlify.outputs.NETLIFY_URL }}';
Write-Output $url;
shell: pwsh
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e5d72cf9..1346537f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -29,6 +29,9 @@ jobs:
working-directory: src/BlazorTable
run: dotnet pack -c Release -p:Version=${{ steps.version.outputs.new_tag }}
+ - name: Dotnet Test
+ run: dotnet test --configuration Release
+
- name: Dotnet Nuget Push
if: "!contains(github.event.head_commit.message, '#skip')"
working-directory: src/BlazorTable/bin/Release
diff --git a/BlazorTable.sln b/BlazorTable.sln
index e8780a0b..321315a3 100644
--- a/BlazorTable.sln
+++ b/BlazorTable.sln
@@ -20,6 +20,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorTable.Sample.Server",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorTable.Sample.Shared", "src\BlazorTable.Sample.Shared\BlazorTable.Sample.Shared.csproj", "{25E8AB75-2F08-463E-8499-0C5BBB20BC50}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorTable.Tests", "src\BlazorTable.Tests\BlazorTable.Tests.csproj", "{64B71D26-A307-4541-9B17-BD6709211F21}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -42,6 +44,10 @@ Global
{25E8AB75-2F08-463E-8499-0C5BBB20BC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25E8AB75-2F08-463E-8499-0C5BBB20BC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25E8AB75-2F08-463E-8499-0C5BBB20BC50}.Release|Any CPU.Build.0 = Release|Any CPU
+ {64B71D26-A307-4541-9B17-BD6709211F21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {64B71D26-A307-4541-9B17-BD6709211F21}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {64B71D26-A307-4541-9B17-BD6709211F21}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {64B71D26-A307-4541-9B17-BD6709211F21}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/BlazorTable.Sample.Shared/BlazorTable.Sample.Shared.csproj b/src/BlazorTable.Sample.Shared/BlazorTable.Sample.Shared.csproj
index b32adbb6..0bf52e24 100644
--- a/src/BlazorTable.Sample.Shared/BlazorTable.Sample.Shared.csproj
+++ b/src/BlazorTable.Sample.Shared/BlazorTable.Sample.Shared.csproj
@@ -9,11 +9,15 @@
-
+
+
+
+
+
diff --git a/src/BlazorTable.Sample.Shared/Bugs/104.razor b/src/BlazorTable.Sample.Shared/Bugs/104.razor
new file mode 100644
index 00000000..bad2f4e0
--- /dev/null
+++ b/src/BlazorTable.Sample.Shared/Bugs/104.razor
@@ -0,0 +1,53 @@
+@page "/104"
+
+@using BlazorTable
+
+
Issue #104
+
+
+
+
+ @if (@context?.Child?.Name == null)
+ {
+ N/A
+ }
+ else
+ {
+ @context.Child.Name
+ }
+
+
+
+
+@code
+{
+ private List items = new List();
+
+ protected override void OnInitialized()
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ items.Add(new Parent() { Name = i.ToString(), Child = new Child() { Name = i.ToString() } });
+ items.Add(new Parent() { Name = i.ToString() });
+ }
+ }
+
+ private class Parent
+ {
+ public string Name { get; set; }
+
+ public Child Child { get; set; }
+ }
+
+ private class Child
+ {
+ public string Name { get; set; }
+
+ public GrandChild GrandChild { get; set; }
+ }
+
+ private class GrandChild
+ {
+ public string Name { get; set; }
+ }
+}
diff --git a/src/BlazorTable.Sample.Wasm/BlazorTable.Sample.Wasm.csproj b/src/BlazorTable.Sample.Wasm/BlazorTable.Sample.Wasm.csproj
index c70394e2..e47270e1 100644
--- a/src/BlazorTable.Sample.Wasm/BlazorTable.Sample.Wasm.csproj
+++ b/src/BlazorTable.Sample.Wasm/BlazorTable.Sample.Wasm.csproj
@@ -6,13 +6,17 @@
-
-
-
+
+
+
+
+
+
+
diff --git a/src/BlazorTable.Sample.Wasm/Program.cs b/src/BlazorTable.Sample.Wasm/Program.cs
index b0e4f033..2d10099a 100644
--- a/src/BlazorTable.Sample.Wasm/Program.cs
+++ b/src/BlazorTable.Sample.Wasm/Program.cs
@@ -2,6 +2,8 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System;
namespace BlazorTable.Sample.Wasm
{
@@ -10,9 +12,10 @@ public class Program
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
+
builder.RootComponents.Add("app");
- builder.Services.AddBaseAddressHttpClient();
+ builder.Services.AddSingleton(new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
diff --git a/src/BlazorTable.Tests/BlazorTable.Tests.csproj b/src/BlazorTable.Tests/BlazorTable.Tests.csproj
new file mode 100644
index 00000000..d890664d
--- /dev/null
+++ b/src/BlazorTable.Tests/BlazorTable.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/src/BlazorTable.Tests/Bug104.cs b/src/BlazorTable.Tests/Bug104.cs
new file mode 100644
index 00000000..9a674706
--- /dev/null
+++ b/src/BlazorTable.Tests/Bug104.cs
@@ -0,0 +1,72 @@
+using Shouldly;
+using System;
+using System.Linq.Expressions;
+using Xunit;
+
+namespace BlazorTable.Tests
+{
+ public class Bug104
+ {
+ [Fact]
+ public void AddToExisting()
+ {
+ Expression> query = x => ((x.Child.Name != null) && (x.Child.Name == "bob"));
+
+ Expression> queryfixed = x => ((x.Child != null) &&((x.Child.Name != null) && (x.Child.Name == "bob")));
+
+ query.AddNullChecks().ToString().ShouldBe(queryfixed.ToString());
+ }
+
+
+ [Fact]
+ public void NoParent()
+ {
+ Expression> query = x => x.Name != null;
+
+ Expression> queryfixed = x => x.Name != null;
+
+ query.AddNullChecks().ToString().ShouldBe(queryfixed.ToString());
+ }
+
+ [Fact]
+ public void SingleParent()
+ {
+ Expression> query = x => x.Child.Name != null;
+
+ Expression> queryfixed = x => x.Child != null
+ && x.Child.Name != null;
+
+ query.AddNullChecks().ToString().ShouldBe(queryfixed.ToString());
+ }
+
+ [Fact]
+ public void MultiParent()
+ {
+ Expression> query1 = x => x.Child.GrandChild.Name != null;
+
+ Expression> query1fixed = x => ((x.Child != null)
+ && ((x.Child.GrandChild != null) && (x.Child.GrandChild.Name != null)));
+
+ query1.AddNullChecks().ToString().ShouldBe("x => ((x.Child != null) AndAlso ((x.Child.GrandChild != null) AndAlso (x.Child.GrandChild.Name != null)))");
+ }
+
+ private class Parent
+ {
+ public string Name { get; set; }
+
+ public Child Child { get; set; }
+ }
+
+ private class Child
+ {
+ public string Name { get; set; }
+
+ public GrandChild GrandChild { get; set; }
+ }
+
+ private class GrandChild
+ {
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/src/BlazorTable/BlazorTable.csproj b/src/BlazorTable/BlazorTable.csproj
index 161d0fc9..219de317 100644
--- a/src/BlazorTable/BlazorTable.csproj
+++ b/src/BlazorTable/BlazorTable.csproj
@@ -30,5 +30,9 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
\ No newline at end of file
diff --git a/src/BlazorTable/Components/Table.razor.cs b/src/BlazorTable/Components/Table.razor.cs
index 18131af1..c6701deb 100644
--- a/src/BlazorTable/Components/Table.razor.cs
+++ b/src/BlazorTable/Components/Table.razor.cs
@@ -123,7 +123,7 @@ private IEnumerable GetData()
{
if (item.Filter != null)
{
- ItemsQueryable = ItemsQueryable.Where(item.Filter);
+ ItemsQueryable = ItemsQueryable.Where(item.Filter.AddNullChecks());
}
}
diff --git a/src/BlazorTable/Utillities.cs b/src/BlazorTable/Utillities.cs
index 113a20f1..2b990d54 100644
--- a/src/BlazorTable/Utillities.cs
+++ b/src/BlazorTable/Utillities.cs
@@ -1,4 +1,5 @@
-using System;
+using LinqKit;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
@@ -8,7 +9,7 @@
namespace BlazorTable
{
- internal static class Utillities
+ public static class Utillities
{
public static IEnumerable OrEmptyIfNull(this IEnumerable source)
{
@@ -114,5 +115,53 @@ public static string ToDescriptionString(this Enum val)
var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
+
+ ///
+ /// Recursively walks up the tree and adds null checks
+ ///
+ ///
+ ///
+ public static Expression> AddNullChecks(this Expression> expression)
+ {
+ var parents = new Queue();
+ Expression> tempExpression = expression;
+
+ if (expression?.Body is BinaryExpression binary)
+ {
+ if (binary.Left is MemberExpression member)
+ {
+ // From here we're looking at parents
+ Recurse(member.Expression);
+ }
+ else if (expression?.Body is BinaryExpression binary2)
+ {
+ if (binary2.Left is BinaryExpression binary3 && binary3.Left is MemberExpression member2)
+ {
+ // From here we're looking at parents
+ Recurse(member2.Expression);
+ }
+ }
+ }
+
+ while (parents.Count > 0)
+ {
+ var nullCheck = Expression.NotEqual(parents.Dequeue(), Expression.Constant(null));
+
+ var newQuery = Expression.Lambda>(nullCheck, expression.Parameters);
+
+ tempExpression = newQuery.And(tempExpression);
+ }
+
+ return tempExpression;
+
+ void Recurse(object expression)
+ {
+ if (expression is MemberExpression member)
+ {
+ parents.Enqueue(member);
+ Recurse(member.Expression);
+ }
+ }
+ }
}
}