From 0b945339deb234a1d5b5d079531e1a314e9411a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20Bu=C4=9Fra=20K=C3=B6sen?= Date: Fri, 12 Apr 2024 18:10:00 +0300 Subject: [PATCH] service collection extension added. --- .../Integration/BuilderTest.cs | 2 + ExpressionBuilder/Configuration/Settings.cs | 23 +++---- ExpressionBuilder/ExpressionBuilder.csproj | 11 ++-- ExpressionBuilder/Generics/FilterStatement.cs | 1 - ExpressionBuilder/Helpers/OperationHelper.cs | 65 +++++++------------ .../ServiceCollectionExtensions.cs | 16 +++++ ExpressionBuilder/readme.md | 28 ++++---- README.md | 6 +- 8 files changed, 72 insertions(+), 80 deletions(-) create mode 100644 ExpressionBuilder/ServiceCollectionExtensions.cs diff --git a/ExpressionBuilder.Test.NetCore/Integration/BuilderTest.cs b/ExpressionBuilder.Test.NetCore/Integration/BuilderTest.cs index 40c395e..e840c14 100644 --- a/ExpressionBuilder.Test.NetCore/Integration/BuilderTest.cs +++ b/ExpressionBuilder.Test.NetCore/Integration/BuilderTest.cs @@ -1,4 +1,5 @@ using ExpressionBuilder.Common; +using ExpressionBuilder.Configuration; using ExpressionBuilder.Exceptions; using ExpressionBuilder.Generics; using ExpressionBuilder.Operations; @@ -262,6 +263,7 @@ public void BuilderWithNullableValues() [TestCase(TestName = "Builder working with custom supported type")] public void BuilderUsingCustomSupportedType() { + Settings.LoadSettings([new SupportedType { Type = typeof(DateTimeOffset), TypeGroup = TypeGroup.Date }]); var dateOffset = new DateTimeOffset(new DateTime(1980, 1, 1)); var filter = new Filter(); filter.By("Birth.DateOffset", Operation.GreaterThan, dateOffset); diff --git a/ExpressionBuilder/Configuration/Settings.cs b/ExpressionBuilder/Configuration/Settings.cs index 8d23c25..130eeb3 100644 --- a/ExpressionBuilder/Configuration/Settings.cs +++ b/ExpressionBuilder/Configuration/Settings.cs @@ -3,29 +3,24 @@ namespace ExpressionBuilder.Configuration; -public class Settings +public static class Settings { - public List SupportedTypes { get; private set; } + public static List SupportedTypes { get; internal set; } = []; - public static void LoadSettings(Settings settings) + public static void LoadSettingsFromConfigurationFile(IConfigurationManager configurationManager) { - var builder = new ConfigurationBuilder().SetBasePath(AppContext.BaseDirectory) - .AddJsonFile("appsettings.json", - optional: true, - reloadOnChange: true); - - var config = builder.Build(); - - settings.SupportedTypes = []; - - foreach (var supportedType in config.GetSection("supportedTypes").GetChildren()) + foreach (var supportedType in configurationManager.GetSection("supportedTypes").GetChildren()) { var typeGroup = supportedType.GetValue("typeGroup"); + var type = Type.GetType(supportedType.GetValue("Type"), false, true); + if (type != null) { - settings.SupportedTypes.Add(new SupportedType { TypeGroup = typeGroup, Type = type }); + SupportedTypes.Add(new SupportedType { TypeGroup = typeGroup, Type = type }); } } } + + public static void LoadSettings(List supportedTypes) => SupportedTypes.AddRange(supportedTypes); } \ No newline at end of file diff --git a/ExpressionBuilder/ExpressionBuilder.csproj b/ExpressionBuilder/ExpressionBuilder.csproj index 5947d50..8eed9b0 100644 --- a/ExpressionBuilder/ExpressionBuilder.csproj +++ b/ExpressionBuilder/ExpressionBuilder.csproj @@ -18,7 +18,7 @@ 1.0.0.0 true - 1.0.2 + 1.0.3 false Milvasoft.ExpressionBuilder @@ -38,8 +38,11 @@ - - - + + + + + + \ No newline at end of file diff --git a/ExpressionBuilder/Generics/FilterStatement.cs b/ExpressionBuilder/Generics/FilterStatement.cs index 02fc4e7..3023f70 100644 --- a/ExpressionBuilder/Generics/FilterStatement.cs +++ b/ExpressionBuilder/Generics/FilterStatement.cs @@ -107,7 +107,6 @@ private void ValidateSupportedOperations(OperationHelper helper) { if (typeof(TPropertyType) == typeof(object)) { - //TODO: Issue regarding the TPropertyType that comes from the UI always as 'Object' System.Diagnostics.Debug.WriteLine("WARN: Not able to check if the operation is supported or not."); return; } diff --git a/ExpressionBuilder/Helpers/OperationHelper.cs b/ExpressionBuilder/Helpers/OperationHelper.cs index fbea21a..0e280c9 100644 --- a/ExpressionBuilder/Helpers/OperationHelper.cs +++ b/ExpressionBuilder/Helpers/OperationHelper.cs @@ -12,8 +12,6 @@ public class OperationHelper : IOperationHelper { private static HashSet _operations; - private readonly Settings _settings; - public static Dictionary> TypeGroups { get; } = new Dictionary> { { TypeGroup.Text, new HashSet { typeof(string), typeof(char) } }, @@ -31,16 +29,7 @@ public class OperationHelper : IOperationHelper static OperationHelper() { LoadDefaultOperations(); - } - - /// - /// Instantiates a new OperationHelper. - /// - public OperationHelper() - { - _settings = new Settings(); - - Settings.LoadSettings(_settings); + GetCustomSupportedTypes(); } /// @@ -66,8 +55,27 @@ public static void LoadDefaultOperations() /// public HashSet SupportedOperations(Type type) { - GetCustomSupportedTypes(); - return GetSupportedOperations(type); + var underlyingNullableType = Nullable.GetUnderlyingType(type); + var typeName = (underlyingNullableType ?? type).Name; + + var supportedOperations = new List(); + + if (type.IsArray) + { + typeName = type.GetElementType()?.Name; + supportedOperations.AddRange(Operations.Where(o => o.SupportsLists && o.Active)); + } + + var typeGroup = TypeGroup.Default; + if (TypeGroups.Any(i => i.Value.Any(v => v.Name == typeName))) + typeGroup = TypeGroups.FirstOrDefault(i => i.Value.Any(v => v.Name == typeName)).Key; + + supportedOperations.AddRange(Operations.Where(o => o.TypeGroup.HasFlag(typeGroup) && !o.SupportsLists && o.Active)); + + if (underlyingNullableType != null) + supportedOperations.AddRange(Operations.Where(o => o.TypeGroup.HasFlag(TypeGroup.Nullable) && !o.SupportsLists && o.Active)); + + return new HashSet(supportedOperations); } /// @@ -102,40 +110,15 @@ public void LoadOperations(List operations, bool overloadExisting) } } - private void GetCustomSupportedTypes() + private static void GetCustomSupportedTypes() { - foreach (var supportedType in _settings.SupportedTypes) + foreach (var supportedType in Settings.SupportedTypes) { if (supportedType.Type != null) TypeGroups[supportedType.TypeGroup].Add(supportedType.Type); } } - private HashSet GetSupportedOperations(Type type) - { - var underlyingNullableType = Nullable.GetUnderlyingType(type); - var typeName = (underlyingNullableType ?? type).Name; - - var supportedOperations = new List(); - - if (type.IsArray) - { - typeName = type.GetElementType()?.Name; - supportedOperations.AddRange(Operations.Where(o => o.SupportsLists && o.Active)); - } - - var typeGroup = TypeGroup.Default; - if (TypeGroups.Any(i => i.Value.Any(v => v.Name == typeName))) - typeGroup = TypeGroups.FirstOrDefault(i => i.Value.Any(v => v.Name == typeName)).Key; - - supportedOperations.AddRange(Operations.Where(o => o.TypeGroup.HasFlag(typeGroup) && !o.SupportsLists && o.Active)); - - if (underlyingNullableType != null) - supportedOperations.AddRange(Operations.Where(o => o.TypeGroup.HasFlag(TypeGroup.Nullable) && !o.SupportsLists && o.Active)); - - return new HashSet(supportedOperations); - } - private static void DeactivateOperation(string operationName, bool overloadExisting) { if (!overloadExisting) diff --git a/ExpressionBuilder/ServiceCollectionExtensions.cs b/ExpressionBuilder/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..89156dd --- /dev/null +++ b/ExpressionBuilder/ServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using ExpressionBuilder.Configuration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace ExpressionBuilder; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddExpressionBuilder(this IServiceCollection services, IConfigurationManager configurationManager = null) + { + if (configurationManager != null) + Settings.LoadSettingsFromConfigurationFile(configurationManager); + + return services; + } +} diff --git a/ExpressionBuilder/readme.md b/ExpressionBuilder/readme.md index 423d608..8f1fbee 100644 --- a/ExpressionBuilder/readme.md +++ b/ExpressionBuilder/readme.md @@ -3,6 +3,7 @@ In short words, this library basically provides you with a simple way to create # How to use it Let us imagine we have classes like this... + ```CSharp public enum PersonGender { @@ -46,21 +47,26 @@ public class Contact ``` Now, what about being able query a list of `Person` in a way like this: + ```CSharp var filter = new Filter(); + filter.By("Id", Operation.Between, 2, 4, Connector.And); filter.By("Contacts[Value]", Operation.EndsWith, "@email.com", default(string), Connector.And); filter.By("Birth.Country", Operation.IsNotNull, default(string), default(string), Connector.Or); filter.By("Name", Operation.Contains, " John"); + var people = People.Where(filter); //or like this... var filter = new Filter(); + filter.By("Id", Operation.Between, 2, 4) .And.By("Birth.Country", Operation.IsNotNull) .And.By("Contacts[Value]", Operation.EndsWith, "@email.com") .Or.By("Name", Operation.Contains, " John "); + var people = People.Where(filter); ``` So that would generate an expression like this: @@ -138,36 +144,26 @@ While compiling the filter into a lambda expression, the expression builder will ``` -## Globalization support -You just need to perform some easy steps to add globalization support to the UI: -1. Add a resource file to the project, naming it after the type you'll create your filter to (e.g. `Person.resx`); -2. Add one entry for each property you'd like to globalize following the conventions (previously mentioned), but replacing the dots (`.`) and the brackets (`[`, `]`) by underscores (`_`); -3. You can globalize the operations on a similar way as well by adding a resources file named `Operations.resx`; -4. For the properties, you'll instantiate a `PropertyCollection` : `new PropertyCollection(typeof(Person), Resources.Person.ResourceManager)`. That will give you a collection of objects with three members: - * `Id`: The conventionalised property identifier (previously mentioned) - * `Name`: The resources file matching value for the property id - * `Info`: The `PropertyInfo` object for the property -5. And for the operations, you have an extension method: `Operation.GreaterThanOrEqualTo.GetDescription(Resources.Operations.ResourceManager)`. - -#### Note on globalization -Any property or operation not mentioned at the resources files will be replaced by its conventionalised property identifier. - ## Complex expressions Complex expressions are handled basically by grouping up filter statements, like in the example below: ```CSharp var filter = new Filter(); + filter.By("SupplierID", Operation.EqualTo, 1); filter.StartGroup(); filter.By("CategoryID", Operation.EqualTo, 1, Connector.Or); filter.By("CategoryID", Operation.EqualTo, 2); + var people = db.Products.Where(filter); //or using the fluent interface... var filter = new Filter(); + filter.By("SupplierID", Operation.EqualTo, 1) .And .Group.By("CategoryID", Operation.EqualTo, 1).Or.By("CategoryID", Operation.EqualTo, 2); + var people = db.Products.Where(filter); ``` @@ -183,6 +179,7 @@ Every time you start a group that means all further statements will by at the sa This is a breakthrough feature that enables you to create your own operations, or even overwrite the behaviour of the existing default operations. For example, let us say that you would like to have an operation to be applied on dates that would filter based on today's day and month (to know whose birthday is today, or to see which bills are due today). To do that, you would need to go through just two simple steps: 1. Create your custom operation. An operation to do what was proposed on the previous example would look like this: + ```CSharp public class ThisDay : IOperation { @@ -246,6 +243,7 @@ public class ThisDay : IOperation ``` 2. Load your custom operation into the operations list for the Expression Builder. This should be done ONLY ONCE, and before you first ever try to use your custom operation. + ```CSharp ExpressionBuilder.Operations.Operation.LoadOperations(new List { new ThisDay(), new EqualTo() }, true); ``` @@ -253,7 +251,7 @@ ExpressionBuilder.Operations.Operation.LoadOperations(new List { new You can see this custom operation in action by running the WinForms example project. And if you have any hard time creating your custom operations, please refer to my article [Build Lambda Expression Dynamically](https://www.codeproject.com/Articles/1079028/Build-Lambda-Expressions-Dynamically) for some insights on the subject. # License -Copyright 2018 David Belmont +Copyright Milvasoft Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 2ddeb69..71abc92 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,7 @@ # Expression Builder In short words, this library basically provides you with a simple way to create lambda expressions to filter lists and database queries by delivering an easy-to-use fluent interface that enables the creation, storage and transmission of those filters. That can be used to help to turn WebApi requests parameters into expressions, create advanced search screens with the capability to save and re-run those filters, among other things. If you would like more details on how it works, please, check out the article [Build Lambda Expression Dynamically](https://www.codeproject.com/Articles/1079028/Build-Lambda-Expressions-Dynamically). -| | Badges | -| -- | -- | -Build | ![buildStatus](https://dbelmont.visualstudio.com/_apis/public/build/definitions/514190c1-40ad-46f3-b8d7-428acd1a108c/2/badge) -Quality | ![Codacy Badge](https://api.codacy.com/project/badge/Grade/dc3b91e17b554d0183f4b504bb3c50d1) ![SonarQube Quality Gate Statys](https://sonarcloud.io/api/project_badges/measure?project=expressionbuilder&metric=alert_status) ![Code Coverage](https://sonarcloud.io/api/project_badges/measure?project=expressionbuilder&metric=coverage) -Nuget | [![NuGet](https://img.shields.io/nuget/v/LambdaExpressionBuilder.svg)](https://www.nuget.org/packages/LambdaExpressionBuilder) [![NuGet](https://img.shields.io/nuget/dt/LambdaExpressionBuilder.svg)](https://www.nuget.org/packages/LambdaExpressionBuilder/) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Milvasoft/ExpressionBuilder/blob/master/LICENSE) [![NuGet](https://img.shields.io/nuget/v/Milvasoft.ExpressionBuilder)](https://www.nuget.org/packages/Milvasoft.ExpressionBuilder/) [![NuGet](https://img.shields.io/nuget/dt/Milvasoft.ExpressionBuilder)](https://www.nuget.org/packages/Milvasoft.ExpressionBuilder/) * [Features](#features)