From 9a00bf5bbb00452faa342d43051cce2e3668aa33 Mon Sep 17 00:00:00 2001 From: Jochen Maucher Date: Tue, 14 Feb 2023 14:50:43 +0100 Subject: [PATCH 1/2] Added support to change the current CulutureInfo --- Sieve/Models/SieveOptions.cs | 6 +- Sieve/Services/SieveProcessor.cs | 8 +++ SieveUnitTests/Abstractions/Entity/IPost.cs | 2 + SieveUnitTests/Entities/Post.cs | 3 + SieveUnitTests/General.cs | 78 +++++++++++++++++++++ 5 files changed, 96 insertions(+), 1 deletion(-) diff --git a/Sieve/Models/SieveOptions.cs b/Sieve/Models/SieveOptions.cs index af0ee0b..559b478 100644 --- a/Sieve/Models/SieveOptions.cs +++ b/Sieve/Models/SieveOptions.cs @@ -1,4 +1,6 @@ -namespace Sieve.Models +using System.Globalization; + +namespace Sieve.Models { public class SieveOptions { @@ -13,5 +15,7 @@ public class SieveOptions public bool IgnoreNullsOnNotEqual { get; set; } = true; public bool DisableNullableTypeExpressionForSorting { get; set; } = false; + + public CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture; } } diff --git a/Sieve/Services/SieveProcessor.cs b/Sieve/Services/SieveProcessor.cs index 5b36ea1..2dd7647 100644 --- a/Sieve/Services/SieveProcessor.cs +++ b/Sieve/Services/SieveProcessor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -122,6 +123,9 @@ public IQueryable Apply(TSieveModel model, IQueryable object[] dataForCustomMethods = null, bool applyFiltering = true, bool applySorting = true, bool applyPagination = true) { + var currentCultureInfo = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = Options.Value.CultureInfo; + var result = source; if (model == null) @@ -162,6 +166,10 @@ public IQueryable Apply(TSieveModel model, IQueryable throw new SieveException(ex.Message, ex); } + finally + { + CultureInfo.CurrentCulture = currentCultureInfo; + } } protected virtual IQueryable ApplyFiltering(TSieveModel model, IQueryable result, diff --git a/SieveUnitTests/Abstractions/Entity/IPost.cs b/SieveUnitTests/Abstractions/Entity/IPost.cs index b05d365..716f91c 100644 --- a/SieveUnitTests/Abstractions/Entity/IPost.cs +++ b/SieveUnitTests/Abstractions/Entity/IPost.cs @@ -9,6 +9,8 @@ public interface IPost: IBaseEntity string Title { get; set; } [Sieve(CanFilter = true, CanSort = true)] int LikeCount { get; set; } + [Sieve(CanFilter = true)] + float MeanRating { get; set; } [Sieve(CanFilter = true, CanSort = true)] int CommentCount { get; set; } [Sieve(CanFilter = true, CanSort = true)] diff --git a/SieveUnitTests/Entities/Post.cs b/SieveUnitTests/Entities/Post.cs index aa931e9..e17c31b 100644 --- a/SieveUnitTests/Entities/Post.cs +++ b/SieveUnitTests/Entities/Post.cs @@ -12,6 +12,9 @@ public class Post : BaseEntity, IPost [Sieve(CanFilter = true, CanSort = true)] public int LikeCount { get; set; } = new Random().Next(0, 1000); + + [Sieve(CanFilter = true)] + public float MeanRating { get; set; } = 0.0f; [Sieve(CanFilter = true, CanSort = true)] public int CommentCount { get; set; } = new Random().Next(0, 1000); diff --git a/SieveUnitTests/General.cs b/SieveUnitTests/General.cs index 886ce50..4fc02dc 100644 --- a/SieveUnitTests/General.cs +++ b/SieveUnitTests/General.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Sieve.Exceptions; using Sieve.Models; @@ -40,6 +41,7 @@ public General(ITestOutputHelper testOutputHelper) Id = 0, Title = "A", LikeCount = 100, + MeanRating = 3.5f, IsDraft = true, CategoryId = null, TopComment = new Comment { Id = 0, Text = "A1" }, @@ -50,6 +52,7 @@ public General(ITestOutputHelper testOutputHelper) Id = 1, Title = "B", LikeCount = 50, + MeanRating = 3.5f, IsDraft = false, CategoryId = 1, TopComment = new Comment { Id = 3, Text = "B1" }, @@ -60,6 +63,7 @@ public General(ITestOutputHelper testOutputHelper) Id = 2, Title = "C", LikeCount = 0, + MeanRating = 3.5f, CategoryId = 1, TopComment = new Comment { Id = 2, Text = "C1" }, FeaturedComment = new Comment { Id = 6, Text = "C2" } @@ -69,6 +73,7 @@ public General(ITestOutputHelper testOutputHelper) Id = 3, Title = "D", LikeCount = 3, + MeanRating = 3.5f, IsDraft = true, CategoryId = 2, TopComment = new Comment { Id = 1, Text = "D1" }, @@ -79,6 +84,7 @@ public General(ITestOutputHelper testOutputHelper) Id = 4, Title = "Yen", LikeCount = 5, + MeanRating = 3.5f, IsDraft = true, CategoryId = 5, TopComment = new Comment { Id = 4, Text = "Yen3" }, @@ -848,5 +854,77 @@ public void CanFilterWithEscapedOperators(string filter) Assert.Equal(1, resultCount); } + + [Theory] + [InlineData("en-US", "1.2")] + [InlineData("de-DE", @"1\,2")] + public void ParseFloatsWithChangedCultureInfo_Works(string culture, string value) + { + // ARRANGE + var posts = new List + { + new Post + { + Id = 1, + Title = "E\\", + LikeCount = 4, + MeanRating = 1.2f, + IsDraft = true, + CategoryId = 1, + TopComment = new Comment { Id = 1, Text = "E1" }, + FeaturedComment = new Comment { Id = 7, Text = "E2" } + } + }.AsQueryable(); + + var optionsAccessor = new SieveOptionsAccessor(); + optionsAccessor.Value.CultureInfo = new CultureInfo(culture, false); + + var processor = new ApplicationSieveProcessor(optionsAccessor, + new SieveCustomSortMethods(), + new SieveCustomFilterMethods()); + + var model = new SieveModel() { Filters = $"MeanRating=={value}" }; + + // ACT + var result = processor.Apply(model, posts); + + // ASSERT + Assert.NotNull(result); + } + + [Theory] + [InlineData("en-US", @"1\,2")] + [InlineData("de-DE", "1.2")] + public void ParseFloatsWithChangedCultureInfo_Fails(string culture, string value) + { + // ARRANGE + var posts = new List + { + new Post + { + Id = 1, + Title = "E\\", + LikeCount = 4, + MeanRating = 1.9f, + IsDraft = true, + CategoryId = 1, + TopComment = new Comment { Id = 1, Text = "E1" }, + FeaturedComment = new Comment { Id = 7, Text = "E2" } + } + }.AsQueryable(); + + var optionsAccessor = new SieveOptionsAccessor(); + optionsAccessor.Value.CultureInfo = new CultureInfo(culture, false); + + var processor = new ApplicationSieveProcessor(optionsAccessor, + new SieveCustomSortMethods(), + new SieveCustomFilterMethods()); + + var model = new SieveModel() { Filters = $"MeanRating=={value}" }; + + // ACT, ASSERT + Assert.Throws(() => processor.Apply(model, posts)); + } + } } From 7344720c6f2e199721b8e001ffad0fcb5cd71da4 Mon Sep 17 00:00:00 2001 From: Jochen Maucher Date: Wed, 26 Jul 2023 09:01:15 +0200 Subject: [PATCH 2/2] Took solution from https://github.com/Biarity/Sieve/pull/125 and added tests --- Sieve/Models/SieveOptions.cs | 6 ++---- Sieve/Services/SieveProcessor.cs | 16 +++++----------- SieveUnitTests/Entities/Post.cs | 2 +- SieveUnitTests/General.cs | 12 +++++------- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/Sieve/Models/SieveOptions.cs b/Sieve/Models/SieveOptions.cs index 559b478..4c19575 100644 --- a/Sieve/Models/SieveOptions.cs +++ b/Sieve/Models/SieveOptions.cs @@ -1,5 +1,3 @@ -using System.Globalization; - namespace Sieve.Models { public class SieveOptions @@ -15,7 +13,7 @@ public class SieveOptions public bool IgnoreNullsOnNotEqual { get; set; } = true; public bool DisableNullableTypeExpressionForSorting { get; set; } = false; - - public CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture; + + public string CultureNameOfTypeConversion { get; set; } = "en"; } } diff --git a/Sieve/Services/SieveProcessor.cs b/Sieve/Services/SieveProcessor.cs index 2dd7647..b586b51 100644 --- a/Sieve/Services/SieveProcessor.cs +++ b/Sieve/Services/SieveProcessor.cs @@ -123,9 +123,6 @@ public IQueryable Apply(TSieveModel model, IQueryable object[] dataForCustomMethods = null, bool applyFiltering = true, bool applySorting = true, bool applyPagination = true) { - var currentCultureInfo = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = Options.Value.CultureInfo; - var result = source; if (model == null) @@ -166,10 +163,6 @@ public IQueryable Apply(TSieveModel model, IQueryable throw new SieveException(ex.Message, ex); } - finally - { - CultureInfo.CurrentCulture = currentCultureInfo; - } } protected virtual IQueryable ApplyFiltering(TSieveModel model, IQueryable result, @@ -180,6 +173,8 @@ protected virtual IQueryable ApplyFiltering(TSieveModel model, return result; } + var cultureInfoToUseForTypeConversion = new CultureInfo(Options.Value.CultureNameOfTypeConversion ?? "en"); + Expression outerExpression = null; var parameter = Expression.Parameter(typeof(TEntity), "e"); foreach (var filterTerm in model.GetFiltersParsed()) @@ -206,7 +201,7 @@ protected virtual IQueryable ApplyFiltering(TSieveModel model, var filterValue = isFilterTermValueNull ? Expression.Constant(null, property.PropertyType) - : ConvertStringValueToConstantExpression(filterTermValue, property, converter); + : ConvertStringValueToConstantExpression(filterTermValue, property, converter, cultureInfoToUseForTypeConversion); if (filterTerm.OperatorIsCaseInsensitive && !isFilterTermValueNull) { @@ -317,15 +312,14 @@ private static Expression GenerateFilterNullCheckExpression(Expression propertyV Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type))); } - private static Expression ConvertStringValueToConstantExpression(string value, PropertyInfo property, - TypeConverter converter) + private static Expression ConvertStringValueToConstantExpression(string value, PropertyInfo property, TypeConverter converter, CultureInfo cultureInfo) { // to allow user to distinguish between prop==null (as null) and prop==\null (as "null"-string) value = value.Equals(EscapeChar + NullFilterValue, StringComparison.InvariantCultureIgnoreCase) ? value.TrimStart(EscapeChar) : value; dynamic constantVal = converter.CanConvertFrom(typeof(string)) - ? converter.ConvertFrom(value) + ? converter.ConvertFrom(null, cultureInfo, value) : Convert.ChangeType(value, property.PropertyType); return GetClosureOverConstant(constantVal, property.PropertyType); diff --git a/SieveUnitTests/Entities/Post.cs b/SieveUnitTests/Entities/Post.cs index e17c31b..d0ba59f 100644 --- a/SieveUnitTests/Entities/Post.cs +++ b/SieveUnitTests/Entities/Post.cs @@ -14,7 +14,7 @@ public class Post : BaseEntity, IPost public int LikeCount { get; set; } = new Random().Next(0, 1000); [Sieve(CanFilter = true)] - public float MeanRating { get; set; } = 0.0f; + public float MeanRating { get; set; } [Sieve(CanFilter = true, CanSort = true)] public int CommentCount { get; set; } = new Random().Next(0, 1000); diff --git a/SieveUnitTests/General.cs b/SieveUnitTests/General.cs index 4fc02dc..9b8f780 100644 --- a/SieveUnitTests/General.cs +++ b/SieveUnitTests/General.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Sieve.Exceptions; using Sieve.Models; @@ -854,8 +853,7 @@ public void CanFilterWithEscapedOperators(string filter) Assert.Equal(1, resultCount); } - - [Theory] + [Theory] [InlineData("en-US", "1.2")] [InlineData("de-DE", @"1\,2")] public void ParseFloatsWithChangedCultureInfo_Works(string culture, string value) @@ -877,7 +875,7 @@ public void ParseFloatsWithChangedCultureInfo_Works(string culture, string value }.AsQueryable(); var optionsAccessor = new SieveOptionsAccessor(); - optionsAccessor.Value.CultureInfo = new CultureInfo(culture, false); + optionsAccessor.Value.CultureNameOfTypeConversion = culture; var processor = new ApplicationSieveProcessor(optionsAccessor, new SieveCustomSortMethods(), @@ -893,8 +891,8 @@ public void ParseFloatsWithChangedCultureInfo_Works(string culture, string value } [Theory] - [InlineData("en-US", @"1\,2")] - [InlineData("de-DE", "1.2")] + [InlineData("en", @"1\,2")] + [InlineData("de", "1.2")] public void ParseFloatsWithChangedCultureInfo_Fails(string culture, string value) { // ARRANGE @@ -914,7 +912,7 @@ public void ParseFloatsWithChangedCultureInfo_Fails(string culture, string value }.AsQueryable(); var optionsAccessor = new SieveOptionsAccessor(); - optionsAccessor.Value.CultureInfo = new CultureInfo(culture, false); + optionsAccessor.Value.CultureNameOfTypeConversion = culture; var processor = new ApplicationSieveProcessor(optionsAccessor, new SieveCustomSortMethods(),