Skip to content

Commit 5cc75d4

Browse files
committed
made multiplatform .net 8,9,10
1 parent 54e1805 commit 5cc75d4

File tree

5 files changed

+674
-503
lines changed

5 files changed

+674
-503
lines changed

.github/workflows/main.yml

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,67 @@
1-
name: Deploy NuGet Package
1+
name: Publish GridifyExtensions To NuGet
22

33
env:
4-
PROJECT_PATH: './src/GridifyExtensions/GridifyExtensions.csproj'
5-
OUTPUT_DIR: 'nupkgs'
6-
NUGET_SOURCE: 'https://api.nuget.org/v3/index.json'
7-
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
4+
NUGET_SOURCE: https://api.nuget.org/v3/index.json
5+
OUTPUT_DIR: nupkgs
86

97
on:
108
push:
11-
branches:
12-
- main
9+
branches: [ main ]
10+
11+
permissions:
12+
contents: read
13+
1314
jobs:
14-
deploy:
15+
publish:
1516
runs-on: ubuntu-latest
17+
environment: Environment Settings
1618

1719
steps:
1820
- name: Checkout
1921
uses: actions/checkout@v6
2022

21-
- name: Setup .NET Core
23+
- name: Setup .NET
2224
uses: actions/setup-dotnet@v5
2325
with:
2426
global-json-file: global.json
2527

28+
- name: Restore
29+
run: dotnet restore
30+
2631
- name: Build
27-
run: dotnet build ${{ env.PROJECT_PATH }}
32+
run: dotnet build --no-restore --configuration Release
33+
34+
- name: Test
35+
run: dotnet test --no-build --configuration Release --verbosity normal
2836

2937
- name: Pack
30-
run: dotnet pack ${{ env.PROJECT_PATH }} --output ${{ env.OUTPUT_DIR }}
38+
run: dotnet pack src/GridifyExtensions/GridifyExtensions.csproj --no-build --configuration Release --output ${{ env.OUTPUT_DIR }}
39+
40+
- name: Publish packages
41+
env:
42+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
43+
shell: bash
44+
run: |
45+
set -euo pipefail
46+
47+
if [ -z "${NUGET_API_KEY:-}" ]; then
48+
echo "NUGET_API_KEY is missing. If you stored it as an Environment secret, set jobs.publish.environment to that Environment name."
49+
exit 1
50+
fi
51+
52+
shopt -s nullglob
53+
54+
nupkgs=( "${{ env.OUTPUT_DIR }}"/*.nupkg )
55+
snupkgs=( "${{ env.OUTPUT_DIR }}"/*.snupkg )
56+
57+
if [ ${#nupkgs[@]} -eq 0 ]; then
58+
echo "No .nupkg files found in ${{ env.OUTPUT_DIR }}"
59+
ls -la "${{ env.OUTPUT_DIR }}" || true
60+
exit 1
61+
fi
62+
63+
dotnet nuget push "${nupkgs[@]}" --api-key "$NUGET_API_KEY" --source "${{ env.NUGET_SOURCE }}" --skip-duplicate
3164
32-
- name: Publish
33-
run: dotnet nuget push ${{ env.OUTPUT_DIR }}/*.nupkg -k ${{ env.NUGET_API_KEY }} -s ${{ env.NUGET_SOURCE }}
65+
if [ ${#snupkgs[@]} -gt 0 ]; then
66+
dotnet nuget push "${snupkgs[@]}" --api-key "$NUGET_API_KEY" --source "${{ env.NUGET_SOURCE }}" --skip-duplicate
67+
fi

README.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# Pandatech.GridifyExtensions
2+
3+
Extensions for [Gridify](https://github.com/alirezanet/Gridify) providing streamlined filtering, ordering, pagination,
4+
and aggregation for .NET 8+ with Entity Framework Core.
5+
6+
## Installation
7+
8+
```bash
9+
dotnet add package Pandatech.GridifyExtensions
10+
```
11+
12+
## Setup
13+
14+
Register Gridify with custom mapper discovery:
15+
16+
```csharp
17+
builder.AddGridify(); // Scans calling assembly
18+
// or
19+
builder.AddGridify(typeof(Program).Assembly, typeof(Domain).Assembly);
20+
```
21+
22+
## Quick Start
23+
24+
### 1. Define Entity Mapper
25+
26+
```csharp
27+
public class Book
28+
{
29+
public Guid Id { get; set; }
30+
public string Title { get; set; }
31+
public DateTime PublishedDate { get; set; }
32+
public decimal Price { get; set; }
33+
}
34+
35+
public class BookMapper : FilterMapper<Book>
36+
{
37+
public BookMapper()
38+
{
39+
GenerateMappings(); // Auto-map all properties
40+
41+
// Custom mappings
42+
AddMap("book-id", x => x.Id);
43+
AddMap("title", x => x.Title.ToLower(), x => x.ToLower());
44+
AddMap("published", x => x.PublishedDate, x => x.ToUtcDateTime());
45+
46+
// Default sort order
47+
AddDefaultOrderByDescending("published");
48+
}
49+
}
50+
```
51+
52+
### 2. Query with Filtering & Pagination
53+
54+
```csharp
55+
// Paged response
56+
var result = await dbContext.Books
57+
.FilterOrderAndGetPagedAsync(
58+
new GridifyQueryModel {
59+
Page = 1,
60+
PageSize = 20,
61+
Filter = "title=*potter*",
62+
OrderBy = "published desc"
63+
},
64+
cancellationToken
65+
);
66+
67+
// With projection
68+
var dtos = await dbContext.Books
69+
.FilterOrderAndGetPagedAsync(
70+
new GridifyQueryModel { Page = 1, PageSize = 20 },
71+
x => new BookDto { Title = x.Title, Price = x.Price },
72+
cancellationToken
73+
);
74+
```
75+
76+
### 3. Cursor-Based Pagination
77+
78+
```csharp
79+
var result = await dbContext.Books
80+
.FilterOrderAndGetCursoredAsync(
81+
new GridifyCursoredQueryModel {
82+
PageSize = 50,
83+
Filter = "price>10"
84+
},
85+
cancellationToken
86+
);
87+
```
88+
89+
## Core Features
90+
91+
### Filtering & Ordering
92+
93+
```csharp
94+
// Apply filter only
95+
var filtered = query.ApplyFilter("title=*science*");
96+
97+
// Apply filter from model
98+
var filtered = query.ApplyFilter(new GridifyQueryModel { Filter = "price<20" });
99+
100+
// Apply ordering
101+
var ordered = query.ApplyOrder(new GridifyQueryModel { OrderBy = "title" });
102+
```
103+
104+
### Pagination
105+
106+
```csharp
107+
// Traditional paging
108+
var paged = await query.GetPagedAsync(
109+
new GridifyQueryModel { Page = 2, PageSize = 10 },
110+
cancellationToken
111+
);
112+
113+
// Cursor-based (more efficient for large datasets)
114+
var cursored = await query.FilterOrderAndGetCursoredAsync(
115+
new GridifyCursoredQueryModel { PageSize = 50 },
116+
cancellationToken
117+
);
118+
```
119+
120+
### Column Distinct Values
121+
122+
```csharp
123+
// Get distinct values for a column (with cursor pagination)
124+
var distinctTitles = await dbContext.Books
125+
.ColumnDistinctValuesAsync(
126+
new ColumnDistinctValueCursoredQueryModel {
127+
PropertyName = "title",
128+
PageSize = 50,
129+
Filter = "title>A"
130+
},
131+
cancellationToken
132+
);
133+
134+
// For encrypted columns, provide decryptor
135+
var decrypted = await query.ColumnDistinctValuesAsync(
136+
model,
137+
decryptor: bytes => Decrypt(bytes),
138+
cancellationToken
139+
);
140+
```
141+
142+
### Aggregations
143+
144+
```csharp
145+
var total = await dbContext.Books
146+
.AggregateAsync(
147+
new AggregateQueryModel {
148+
AggregateType = AggregateType.Sum,
149+
PropertyName = "price"
150+
},
151+
cancellationToken
152+
);
153+
```
154+
155+
**Supported aggregations:**
156+
157+
- `UniqueCount` - Count of distinct values
158+
- `Sum` - Sum of values
159+
- `Average` - Average value
160+
- `Min` - Minimum value
161+
- `Max` - Maximum value
162+
163+
## Advanced Features
164+
165+
### Custom Converters
166+
167+
```csharp
168+
public class DeviceMapper : FilterMapper<Device>
169+
{
170+
public DeviceMapper()
171+
{
172+
GenerateMappings();
173+
174+
// Case-insensitive string matching
175+
AddMap("name", x => x.Name.ToLower(), x => x.ToLower());
176+
177+
// DateTime with UTC conversion
178+
AddMap("created", x => x.CreatedAt, x => x.ToUtcDateTime());
179+
180+
AddDefaultOrderByDescending("created");
181+
}
182+
}
183+
```
184+
185+
### Encrypted Columns
186+
187+
```csharp
188+
public class UserMapper : FilterMapper<User>
189+
{
190+
public UserMapper()
191+
{
192+
// Mark column as encrypted
193+
AddMap("email", x => x.EncryptedEmail, isEncrypted: true);
194+
}
195+
}
196+
197+
// Query with decryption
198+
var users = await dbContext.Users
199+
.ColumnDistinctValuesAsync(
200+
new ColumnDistinctValueCursoredQueryModel { PropertyName = "email" },
201+
decryptor: encryptedBytes => AesDecrypt(encryptedBytes),
202+
cancellationToken
203+
);
204+
```
205+
206+
### Complex Mappings
207+
208+
```csharp
209+
// Navigation properties
210+
AddMap("author-name", x => x.Author.Name);
211+
212+
// Collections
213+
AddMap("tags", x => x.Tags.Select(t => t.Name));
214+
215+
// Nested properties
216+
AddMap("publisher-country", x => x.Publisher.Address.Country);
217+
```
218+
219+
### Page Size Limits
220+
221+
By default, `GridifyQueryModel` limits `PageSize` to 500:
222+
223+
```csharp
224+
// Remove limit via constructor
225+
var model = new GridifyQueryModel(validatePageSize: false) {
226+
PageSize = 1000,
227+
Page = 1
228+
};
229+
230+
// Or set to maximum
231+
model.SetMaxPageSize(); // Sets PageSize to int.MaxValue
232+
```
233+
234+
### Custom Flag Operator
235+
236+
Built-in support for bitwise flag checking:
237+
238+
```csharp
239+
// Filter by enum flags
240+
var query = "permissions#hasFlag16"; // checks if bit 16 is set
241+
```
242+
243+
## Gridify Query Syntax
244+
245+
Standard Gridify filtering syntax is supported:
246+
247+
```csharp
248+
// Operators: =, !=, <, >, <=, >=, =*, *=, =*=
249+
Filter = "title=*potter*, price<20, published>2020-01-01"
250+
251+
// Logical: , (AND), | (OR), () (grouping)
252+
Filter = "(title=*fantasy*|title=*scifi*), price<30"
253+
254+
// Custom operators
255+
Filter = "flags#hasFlag4" // bitwise flag check
256+
```
257+
258+
## API Reference
259+
260+
### Extension Methods
261+
262+
| Method | Description |
263+
|-----------------------------------------|----------------------------|
264+
| `ApplyFilter(model/filter)` | Apply filtering |
265+
| `ApplyOrder(model)` | Apply ordering |
266+
| `GetPagedAsync(model)` | Get paged results |
267+
| `FilterOrderAndGetPagedAsync(model)` | Filter + order + page |
268+
| `FilterOrderAndGetCursoredAsync(model)` | Cursor-based pagination |
269+
| `ColumnDistinctValuesAsync(model)` | Get distinct column values |
270+
| `AggregateAsync(model)` | Perform aggregation |
271+
272+
### Model Types
273+
274+
- `GridifyQueryModel` - Traditional pagination with page number
275+
- `GridifyCursoredQueryModel` - Cursor-based pagination
276+
- `ColumnDistinctValueCursoredQueryModel` - Distinct values with cursor
277+
- `AggregateQueryModel` - Aggregation configuration
278+
279+
### Response Types
280+
281+
- `PagedResponse<T>` - `(Data, Page, PageSize, TotalCount)`
282+
- `CursoredResponse<T>` - `(Data, PageSize)`
283+
284+
## License
285+
286+
MIT

0 commit comments

Comments
 (0)