Skip to content

Commit e17f640

Browse files
committed
add grid model interface for working with front-end
1 parent 7a3dbfc commit e17f640

File tree

25 files changed

+4818
-45
lines changed

25 files changed

+4818
-45
lines changed

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,32 @@ The practical repository uses coolstore domain which is mainly borrowed from `ht
1616

1717
# Testing Application
1818

19+
Starting the web application
20+
1921
```
20-
> http://localhost:5002/api/products/40082527-807c-4c39-92f9-b5d83bc1f9b7
22+
$ cd src\Web
23+
$ npm i
24+
$ npm run dev
2125
```
2226

27+
Starting the Api
28+
2329
```
24-
> http://localhost:5002/api/products?quantity=1500&page=1&pageSize=15
30+
$ tye run
31+
```
32+
2533
```
34+
$ cd src\Product\ProductService.Api\
35+
$ dotnet run
36+
```
37+
38+
> Frontend: [http://localhost:3000/products](http://localhost:3000/products)
39+
>
40+
> Backend: [http://localhost:5002](http://localhost:5002)
41+
>
42+
> Tye Dashboard: [http://localhost:8000](http://localhost:8000)
43+
44+
![](assets/products_screen.png)
2645

2746
# Refs
2847
- [C4 PlaintUML Model](https://github.com/plantuml-stdlib/C4-PlantUML/blob/master/samples/C4CoreDiagrams.md)

assets/products_screen.png

207 KB
Loading

restclient.http

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
###
2+
@host = http://localhost:5002
3+
GET {{host}}/api/products HTTP/1.1
4+
content-type: {{contentType}}
5+
6+
{
7+
"filters": [
8+
{
9+
"fieldName": "Quantity",
10+
"comparision": ">",
11+
"fieldValue": "1000"
12+
},
13+
{
14+
"fieldName": "Name",
15+
"comparision": "Contains",
16+
"fieldValue": "adipiscing"
17+
}
18+
],
19+
"sorts": ["nameDesc"],
20+
"page": 1,
21+
"pageSize": 20
22+
}

src/BuildingBlocks/N8T.Core/Repository/IRepository.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ public interface IRepository<TEntity> where TEntity : EntityBase, IAggregateRoot
1414
Task<TEntity> AddAsync(TEntity entity);
1515
Task RemoveAsync(TEntity entity);
1616
}
17+
18+
public interface IGridRepository<TEntity> where TEntity : EntityBase, IAggregateRoot
19+
{
20+
ValueTask<long> CountAsync(IGridSpecification<TEntity> spec);
21+
Task<List<TEntity>> FindAsync(IGridSpecification<TEntity> spec);
22+
}
1723
}

src/BuildingBlocks/N8T.Core/Specification/Extensions.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
4+
15
namespace N8T.Core.Specification
26
{
37
public static class Extensions
@@ -20,5 +24,47 @@ public static ISpecification<T> Negate<T>(this ISpecification<T> inner)
2024
{
2125
return new Negated<T>(inner);
2226
}
27+
28+
public static void ApplySorting(this IRootSpecification gridSpec,
29+
string sort,
30+
string orderByDescendingMethodName,
31+
string groupByMethodName)
32+
{
33+
if (string.IsNullOrEmpty(sort)) return;
34+
35+
const string descendingSuffix = "Desc";
36+
37+
var descending = sort.EndsWith(descendingSuffix, StringComparison.Ordinal);
38+
var propertyName = sort.Substring(0, 1).ToUpperInvariant() +
39+
sort.Substring(1, sort.Length - 1 - (descending ? descendingSuffix.Length : 0));
40+
41+
var specificationType = gridSpec.GetType().BaseType;
42+
var targetType = specificationType?.GenericTypeArguments[0];
43+
var property = targetType!.GetRuntimeProperty(propertyName) ??
44+
throw new InvalidOperationException($"Because the property {propertyName} does not exist it cannot be sorted.");
45+
46+
var lambdaParamX = Expression.Parameter(targetType, "x");
47+
48+
var propertyReturningExpression = Expression.Lambda(
49+
Expression.Convert(
50+
Expression.Property(lambdaParamX, property),
51+
typeof(object)),
52+
lambdaParamX);
53+
54+
if (descending)
55+
{
56+
specificationType?.GetMethod(
57+
orderByDescendingMethodName,
58+
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
59+
?.Invoke(gridSpec, new object[]{propertyReturningExpression});
60+
}
61+
else
62+
{
63+
specificationType?.GetMethod(
64+
groupByMethodName,
65+
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
66+
?.Invoke(gridSpec, new object[]{propertyReturningExpression});
67+
}
68+
}
2369
}
2470
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
5+
namespace N8T.Core.Specification
6+
{
7+
public abstract class GridSpecificationBase<T> : IGridSpecification<T>
8+
{
9+
public virtual List<Expression<Func<T, bool>>> Criterias { get; } = new();
10+
public List<Expression<Func<T, object>>> Includes { get; } = new();
11+
public List<string> IncludeStrings { get; } = new();
12+
public Expression<Func<T, object>> OrderBy { get; private set; }
13+
public Expression<Func<T, object>> OrderByDescending { get; private set; }
14+
public Expression<Func<T, object>> GroupBy { get; private set; }
15+
16+
public int Take { get; private set; }
17+
public int Skip { get; private set; }
18+
public bool IsPagingEnabled { get; set; }
19+
20+
protected void AddInclude(Expression<Func<T, object>> includeExpression)
21+
{
22+
Includes.Add(includeExpression);
23+
}
24+
25+
protected void AddInclude(string includeString)
26+
{
27+
IncludeStrings.Add(includeString);
28+
}
29+
30+
protected void ApplyPaging(int skip, int take)
31+
{
32+
Skip = skip;
33+
Take = take;
34+
IsPagingEnabled = true;
35+
}
36+
37+
protected void ApplyOrderBy(Expression<Func<T, object>> orderByExpression) =>
38+
OrderBy = orderByExpression;
39+
40+
protected void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression) =>
41+
OrderByDescending = orderByDescendingExpression;
42+
43+
protected void ApplyGroupBy(Expression<Func<T, object>> groupByExpression) =>
44+
GroupBy = groupByExpression;
45+
46+
protected void ApplySorting(string sort)
47+
{
48+
this.ApplySorting(sort, nameof(ApplyOrderBy), nameof(ApplyOrderByDescending));
49+
50+
/*if (string.IsNullOrEmpty(sort)) return;
51+
52+
const string descendingSuffix = "Desc";
53+
54+
var descending = sort.EndsWith(descendingSuffix, StringComparison.Ordinal);
55+
var propertyName = sort.Substring(0, 1).ToUpperInvariant() +
56+
sort.Substring(1, sort.Length - 1 - (descending ? descendingSuffix.Length : 0));
57+
58+
var specificationType = GetType().BaseType;
59+
var targetType = specificationType?.GenericTypeArguments[0];
60+
var property = targetType!.GetRuntimeProperty(propertyName) ??
61+
throw new InvalidOperationException($"Because the property {propertyName} does not exist it cannot be sorted.");
62+
63+
var lambdaParamX = Expression.Parameter(targetType, "x");
64+
65+
var propertyReturningExpression = Expression.Lambda(
66+
Expression.Convert(
67+
Expression.Property(lambdaParamX, property),
68+
typeof(object)),
69+
lambdaParamX);
70+
71+
if (descending)
72+
{
73+
specificationType?.GetMethod(
74+
nameof(ApplyOrderByDescending),
75+
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
76+
?.Invoke(this, new object[]{propertyReturningExpression});
77+
}
78+
else
79+
{
80+
specificationType?.GetMethod(
81+
nameof(ApplyOrderBy),
82+
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
83+
?.Invoke(this, new object[]{propertyReturningExpression});
84+
}*/
85+
}
86+
}
87+
}

src/BuildingBlocks/N8T.Core/Specification/ISpecification.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44

55
namespace N8T.Core.Specification
66
{
7+
public interface IRootSpecification
8+
{
9+
}
10+
711
/// <summary>
812
/// https://stackoverflow.com/questions/63082758/ef-core-specification-pattern-add-all-column-for-sorting-data-with-custom-specif
913
/// </summary>
1014
/// <typeparam name="T"></typeparam>
11-
public interface ISpecification<T>
15+
public interface ISpecification<T> : IRootSpecification
1216
{
1317
Expression<Func<T, bool>> Criteria { get; }
14-
List<Expression<Func<T, bool>>> Criterias { get; }
1518
List<Expression<Func<T, object>>> Includes { get; }
1619
List<string> IncludeStrings { get; }
1720
Expression<Func<T, object>> OrderBy { get; }
@@ -22,4 +25,18 @@ public interface ISpecification<T>
2225
int Skip { get; }
2326
bool IsPagingEnabled { get; }
2427
}
28+
29+
public interface IGridSpecification<T> : IRootSpecification
30+
{
31+
List<Expression<Func<T, bool>>> Criterias { get; }
32+
List<Expression<Func<T, object>>> Includes { get; }
33+
List<string> IncludeStrings { get; }
34+
Expression<Func<T, object>> OrderBy { get; }
35+
Expression<Func<T, object>> OrderByDescending { get; }
36+
Expression<Func<T, object>> GroupBy { get; }
37+
38+
int Take { get; }
39+
int Skip { get; }
40+
bool IsPagingEnabled { get; set; }
41+
}
2542
}

src/BuildingBlocks/N8T.Core/Specification/SpecificationBase.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq.Expressions;
4-
using System.Reflection;
54

65
namespace N8T.Core.Specification
76
{
@@ -47,7 +46,9 @@ protected void ApplyGroupBy(Expression<Func<T, object>> groupByExpression) =>
4746

4847
protected void ApplySorting(string sort)
4948
{
50-
if (string.IsNullOrEmpty(sort)) return;
49+
this.ApplySorting(sort, nameof(ApplyOrderBy), nameof(ApplyOrderByDescending));
50+
51+
/*if (string.IsNullOrEmpty(sort)) return;
5152
5253
const string descendingSuffix = "Desc";
5354
@@ -81,8 +82,7 @@ protected void ApplySorting(string sort)
8182
nameof(ApplyOrderBy),
8283
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
8384
?.Invoke(this, new object[]{propertyReturningExpression});
84-
}
85+
}*/
8586
}
86-
8787
}
8888
}

src/BuildingBlocks/N8T.Infrastructure.EfCore/Repository.cs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace N8T.Infrastructure.EfCore
1212
{
13-
public class RepositoryBase<TDbContext, TEntity> : IRepository<TEntity>
13+
public class RepositoryBase<TDbContext, TEntity> : IRepository<TEntity>, IGridRepository<TEntity>
1414
where TEntity : EntityBase, IAggregateRoot
1515
where TDbContext : DbContext
1616
{
@@ -32,7 +32,7 @@ public async Task<TEntity> FindOneAsync(ISpecification<TEntity> spec)
3232
{
3333
await using var dbContext = _dbContextFactory.CreateDbContext();
3434

35-
return await dbContext.Set<TEntity>().Where(spec.Criterias.FirstOrDefault()!).SingleOrDefaultAsync();
35+
return await dbContext.Set<TEntity>().Where(spec.Criteria).SingleOrDefaultAsync();
3636
}
3737

3838
public async Task<List<TEntity>> FindAsync(ISpecification<TEntity> spec)
@@ -44,6 +44,25 @@ public async Task<List<TEntity>> FindAsync(ISpecification<TEntity> spec)
4444
return await specificationResult.ToListAsync();
4545
}
4646

47+
public async ValueTask<long> CountAsync(IGridSpecification<TEntity> spec)
48+
{
49+
await using var dbContext = _dbContextFactory.CreateDbContext();
50+
51+
spec.IsPagingEnabled = false;
52+
var specificationResult = GetQuery(dbContext.Set<TEntity>(), spec);
53+
54+
return specificationResult.LongCount();
55+
}
56+
57+
public async Task<List<TEntity>> FindAsync(IGridSpecification<TEntity> spec)
58+
{
59+
await using var dbContext = _dbContextFactory.CreateDbContext();
60+
61+
var specificationResult = GetQuery(dbContext.Set<TEntity>(), spec);
62+
63+
return await specificationResult.ToListAsync();
64+
}
65+
4766
public async Task<TEntity> AddAsync(TEntity entity)
4867
{
4968
await using var dbContext = _dbContextFactory.CreateDbContext();
@@ -68,7 +87,44 @@ private static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery,
6887
{
6988
var query = inputQuery;
7089

71-
if (specification.Criterias != null && specification.Criterias.Count > 0)
90+
if (specification.Criteria is not null)
91+
{
92+
query = query.Where(specification.Criteria);
93+
}
94+
95+
query = specification.Includes.Aggregate(query, (current, include) => current.Include(include));
96+
97+
query = specification.IncludeStrings.Aggregate(query, (current, include) => current.Include(include));
98+
99+
if (specification.OrderBy is not null)
100+
{
101+
query = query.OrderBy(specification.OrderBy);
102+
}
103+
else if (specification.OrderByDescending is not null)
104+
{
105+
query = query.OrderByDescending(specification.OrderByDescending);
106+
}
107+
108+
if (specification.GroupBy is not null)
109+
{
110+
query = query.GroupBy(specification.GroupBy).SelectMany(x => x);
111+
}
112+
113+
if (specification.IsPagingEnabled)
114+
{
115+
query = query.Skip(specification.Skip)
116+
.Take(specification.Take);
117+
}
118+
119+
return query;
120+
}
121+
122+
private static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery,
123+
IGridSpecification<TEntity> specification)
124+
{
125+
var query = inputQuery;
126+
127+
if (specification.Criterias is not null && specification.Criterias.Count > 0)
72128
{
73129
var expr = specification.Criterias.First();
74130
for (var i = 1; i < specification.Criterias.Count; i++)
@@ -83,16 +139,16 @@ private static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery,
83139

84140
query = specification.IncludeStrings.Aggregate(query, (current, include) => current.Include(include));
85141

86-
if (specification.OrderBy != null)
142+
if (specification.OrderBy is not null)
87143
{
88144
query = query.OrderBy(specification.OrderBy);
89145
}
90-
else if (specification.OrderByDescending != null)
146+
else if (specification.OrderByDescending is not null)
91147
{
92148
query = query.OrderByDescending(specification.OrderByDescending);
93149
}
94150

95-
if (specification.GroupBy != null)
151+
if (specification.GroupBy is not null)
96152
{
97153
query = query.GroupBy(specification.GroupBy).SelectMany(x => x);
98154
}

0 commit comments

Comments
 (0)