Skip to content

Commit

Permalink
service collection extension added.
Browse files Browse the repository at this point in the history
  • Loading branch information
bugrakosen committed Apr 12, 2024
1 parent 4b40cc9 commit 0b94533
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 80 deletions.
2 changes: 2 additions & 0 deletions ExpressionBuilder.Test.NetCore/Integration/BuilderTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ExpressionBuilder.Common;
using ExpressionBuilder.Configuration;
using ExpressionBuilder.Exceptions;
using ExpressionBuilder.Generics;
using ExpressionBuilder.Operations;
Expand Down Expand Up @@ -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<Person>();
filter.By("Birth.DateOffset", Operation.GreaterThan, dateOffset);
Expand Down
23 changes: 9 additions & 14 deletions ExpressionBuilder/Configuration/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,24 @@

namespace ExpressionBuilder.Configuration;

public class Settings
public static class Settings
{
public List<SupportedType> SupportedTypes { get; private set; }
public static List<SupportedType> 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>("typeGroup");

var type = Type.GetType(supportedType.GetValue<string>("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<SupportedType> supportedTypes) => SupportedTypes.AddRange(supportedTypes);
}
11 changes: 7 additions & 4 deletions ExpressionBuilder/ExpressionBuilder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</Description>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>1.0.2</Version>
<Version>1.0.3</Version>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageId>Milvasoft.ExpressionBuilder</PackageId>
</PropertyGroup>
Expand All @@ -38,8 +38,11 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

</Project>
1 change: 0 additions & 1 deletion ExpressionBuilder/Generics/FilterStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
65 changes: 24 additions & 41 deletions ExpressionBuilder/Helpers/OperationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ public class OperationHelper : IOperationHelper
{
private static HashSet<IOperation> _operations;

private readonly Settings _settings;

public static Dictionary<TypeGroup, HashSet<Type>> TypeGroups { get; } = new Dictionary<TypeGroup, HashSet<Type>>
{
{ TypeGroup.Text, new HashSet<Type> { typeof(string), typeof(char) } },
Expand All @@ -31,16 +29,7 @@ public class OperationHelper : IOperationHelper
static OperationHelper()
{
LoadDefaultOperations();
}

/// <summary>
/// Instantiates a new OperationHelper.
/// </summary>
public OperationHelper()
{
_settings = new Settings();

Settings.LoadSettings(_settings);
GetCustomSupportedTypes();
}

/// <summary>
Expand All @@ -66,8 +55,27 @@ public static void LoadDefaultOperations()
/// <returns></returns>
public HashSet<IOperation> SupportedOperations(Type type)
{
GetCustomSupportedTypes();
return GetSupportedOperations(type);
var underlyingNullableType = Nullable.GetUnderlyingType(type);
var typeName = (underlyingNullableType ?? type).Name;

var supportedOperations = new List<IOperation>();

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<IOperation>(supportedOperations);
}

/// <summary>
Expand Down Expand Up @@ -102,40 +110,15 @@ public void LoadOperations(List<IOperation> 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<IOperation> GetSupportedOperations(Type type)
{
var underlyingNullableType = Nullable.GetUnderlyingType(type);
var typeName = (underlyingNullableType ?? type).Name;

var supportedOperations = new List<IOperation>();

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<IOperation>(supportedOperations);
}

private static void DeactivateOperation(string operationName, bool overloadExisting)
{
if (!overloadExisting)
Expand Down
16 changes: 16 additions & 0 deletions ExpressionBuilder/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
28 changes: 13 additions & 15 deletions ExpressionBuilder/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<Person>();

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<Person>();

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:
Expand Down Expand Up @@ -138,36 +144,26 @@ While compiling the filter into a lambda expression, the expression builder will
</configuration>
```

## 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<Products>();

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<Products>();

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);
```

Expand All @@ -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
{
Expand Down Expand Up @@ -246,14 +243,15 @@ 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<IOperation> { new ThisDay(), new EqualTo() }, true);
```

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.
Expand Down
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# <img src="ExpressionBuilder\ExpressionBuilder.png" width="36" style="position: relative; top: 5px">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)
Expand Down

0 comments on commit 0b94533

Please sign in to comment.