1
1
# KSFramework
2
2
3
- ** KSFramework** is a lightweight, extensible .NET framework designed to accelerate clean architecture and domain-driven development. It offers generic repository support, pagination utilities , and helpful abstractions to simplify the implementation of enterprise-grade backend systems using modern .NET patterns .
3
+ ** KSFramework** is a modular and extensible .NET framework that simplifies the implementation of enterprise-grade applications using ** Clean Architecture ** , ** DDD ** , and ** CQRS ** patterns. It includes built-in support for Generic Repository, Unit of Work, and a custom MediatR-style messaging system .
4
4
5
5
---
6
6
7
7
## ✨ Features
8
8
9
- - 🧱 ** Domain-Driven Design (DDD) Friendly**
10
- - 🧼 ** Clean Architecture Support**
11
- - 🧰 ** Generic Repository Pattern**
12
- - 📄 ** Built-in Pagination Support**
13
- - 🧪 ** Unit Testable Core Interfaces**
14
- - 🧩 ** Customizable and Extensible by Design**
9
+ - ✅ Clean Architecture and DDD-friendly structure
10
+ - ✅ Generic Repository with constraint on AggregateRoots
11
+ - ✅ Fully extensible Unit of Work pattern
12
+ - ✅ Built-in Pagination utilities
13
+ - ✅ Internal MediatR-like message dispatch system (KSMessaging)
14
+ - ✅ Scrutor-based handler and behavior registration
15
+ - ✅ Support for pipeline behaviors (logging, validation, etc.)
16
+ - ✅ Strongly testable, loosely coupled abstractions
15
17
16
18
---
17
19
@@ -23,267 +25,170 @@ Install via NuGet:
23
25
dotnet add package KSFramework
24
26
```
25
27
26
- Or via the Package Manager Console:
27
-
28
- ``` powershell
29
- Install-Package KSFramework
30
- ```
31
-
32
28
---
33
29
34
- ## 📂 Project Structure
30
+ ## 🧰 Modules Overview
35
31
36
- The KSFramework package is modular and consists of:
37
-
38
- - ` KSFramework.GenericRepository ` — Contains generic repository interfaces and base implementations.
39
- - ` KSFramework.Pagination ` — Provides interfaces and classes for paginated queries.
40
- - ` KSFramework.KSDomain ` — Base types for entities, aggregate roots, and value objects.
32
+ - ` KSFramework.KSDomain ` — Domain primitives: ` Entity ` , ` AggregateRoot ` , ` ValueObject `
33
+ - ` KSFramework.GenericRepository ` — ` Repository ` , ` UnitOfWork ` , pagination support
34
+ - ` KSFramework.KSMessaging ` — CQRS with internal MediatR-style handler resolver, behaviors, stream handling
41
35
42
36
---
43
37
44
- ## 🚀 Getting Started
38
+ ## ⚙️ Usage Overview
45
39
46
- ### 1. Define Your Entities
40
+ ### 🧱 Register Services (Program.cs)
47
41
48
42
``` csharp
49
- public class BlogPost
50
- {
51
- public int Id { get ; set ; }
52
- public string Title { get ; set ; }
53
- public string Content { get ; set ; }
54
- public DateTime PublishedAt { get ; set ; }
55
- }
43
+ builder .Services .AddKSMediator (typeof (Program ).Assembly );
44
+ builder .Services .AddScoped <IUnitOfWork , UnitOfWork >();
56
45
```
57
46
58
- ### 2. Setup Your DbContext
47
+ ### 🧪 Sample Command
59
48
60
49
``` csharp
61
- public class AppDbContext : DbContext
62
- {
63
- public DbSet <BlogPost > BlogPosts { get ; set ; }
50
+ public record CreatePostCommand (string Title , string Content ) : ICommand <Guid >;
64
51
65
- public AppDbContext (DbContextOptions <AppDbContext > options ) : base (options ) { }
66
- }
67
- ```
68
-
69
- ### 3. Create Your Repository
70
-
71
- You can use the generic repository directly:
72
-
73
- ``` csharp
74
- public class BlogPostRepository : Repository <BlogPost >
52
+ public class CreatePostHandler : ICommandHandler <CreatePostCommand , Guid >
75
53
{
76
- public BlogPostRepository (AppDbContext context ) : base (context ) { }
77
- }
78
- ```
79
-
80
- Or inject ` IRepository<BlogPost> ` if you're using dependency injection with Scrutor or your own DI container.
54
+ private readonly IUnitOfWork _uow ;
81
55
82
- ---
83
-
84
- ## 🧪 Common Repository Operations
85
-
86
- ``` csharp
87
- await _repository .AddAsync (new BlogPost { Title = " Welcome" , Content = " This is the first post." });
88
-
89
- var post = await _repository .GetByIdAsync (1 );
90
-
91
- await _repository .UpdateAsync (post );
92
-
93
- await _repository .DeleteAsync (post );
94
-
95
- var allPosts = await _repository .GetAllAsync ();
96
-
97
- var filtered = _repository .Find (p => p .PublishedAt > DateTime .UtcNow .AddDays (- 30 ));
98
- ```
99
-
100
- ---
101
-
102
- ## 📘 Pagination Example
56
+ public CreatePostHandler (IUnitOfWork uow )
57
+ {
58
+ _uow = uow ;
59
+ }
103
60
104
- ``` csharp
105
- var query = _repository .Query ();
106
- var paginated = await query .PaginateAsync (pageNumber : 1 , pageSize : 10 );
61
+ public async Task <Guid > Handle (CreatePostCommand request , CancellationToken cancellationToken )
62
+ {
63
+ var post = new BlogPost { Id = Guid .NewGuid (), Title = request .Title , Content = request .Content };
64
+ await _uow .GetRepository <BlogPost >().AddAsync (post );
65
+ await _uow .SaveChangesAsync ();
66
+ return post .Id ;
67
+ }
68
+ }
107
69
```
108
70
109
- ` PaginateAsync ` is an extension method provided by the ` KSFramework.Pagination ` namespace.
110
-
111
71
---
112
72
113
- ## 📚 Full Example: Building a Blog with Weekly Newsletter
114
-
115
- ### Project Overview
73
+ ## 📚 Example: Blog with Newsletter
116
74
117
- This sample demonstrates how to build a simple blog platform using ` KSFramework ` with:
75
+ ### ✍️ Features
118
76
119
- - Post publishing
120
- - Subscriber registration
121
- - Weekly newsletter delivery to subscribers
77
+ - CRUD for Blog Posts using Commands/Queries
78
+ - Subscriber registration using Notification pattern
79
+ - Weekly newsletter dispatch via background service
122
80
123
81
---
124
82
125
- ### Step 1: Define Entities
83
+ ### 🧩 Domain Layer
126
84
127
85
``` csharp
128
- public class BlogPost
86
+ public class BlogPost : AggregateRoot < Guid >
129
87
{
130
- public int Id { get ; set ; }
131
88
public string Title { get ; set ; }
132
89
public string Content { get ; set ; }
133
90
public DateTime PublishedAt { get ; set ; }
134
91
}
135
92
136
- public class Subscriber
93
+ public class Subscriber : Entity < Guid >
137
94
{
138
- public int Id { get ; set ; }
139
95
public string Email { get ; set ; }
140
96
}
141
97
```
142
98
143
99
---
144
100
145
- ### Step 2: Repositories
101
+ ### 🧠 Application Commands
146
102
147
103
``` csharp
148
- public class BlogPostRepository : Repository <BlogPost >
149
- {
150
- public BlogPostRepository (AppDbContext context ) : base (context ) { }
151
- }
104
+ public record SubscribeCommand (string Email ) : ICommand ;
152
105
153
- public class SubscriberRepository : Repository < Subscriber >
106
+ public class SubscribeHandler : ICommandHandler < SubscribeCommand >
154
107
{
155
- public SubscriberRepository (AppDbContext context ) : base (context ) { }
156
- }
157
- ```
108
+ private readonly IUnitOfWork _uow ;
158
109
159
- ---
110
+ public SubscribeHandler ( IUnitOfWork uow ) => _uow = uow ;
160
111
161
- ### Step 3: API Controllers
162
-
163
- ``` csharp
164
- [ApiController ]
165
- [Route (" api/[controller]" )]
166
- public class BlogPostsController : ControllerBase
167
- {
168
- private readonly IRepository <BlogPost > _repository ;
169
-
170
- public BlogPostsController (IRepository <BlogPost > repository )
112
+ public async Task Handle (SubscribeCommand request , CancellationToken cancellationToken )
171
113
{
172
- _repository = repository ;
173
- }
174
-
175
- [HttpGet ]
176
- public async Task <IEnumerable <BlogPost >> GetAll () => await _repository .GetAllAsync ();
177
-
178
- [HttpPost ]
179
- public async Task <IActionResult > Create (BlogPost post )
180
- {
181
- post .PublishedAt = DateTime .UtcNow ;
182
- await _repository .AddAsync (post );
183
- return Ok (post );
114
+ var repo = _uow .GetRepository <Subscriber >();
115
+ await repo .AddAsync (new Subscriber { Email = request .Email });
116
+ await _uow .SaveChangesAsync ();
184
117
}
185
118
}
186
119
```
187
120
188
121
---
189
122
190
- ### Step 4: Subscription Controller
123
+ ### 🌐 API Controller
191
124
192
125
``` csharp
193
126
[ApiController ]
194
127
[Route (" api/[controller]" )]
195
128
public class NewsletterController : ControllerBase
196
129
{
197
- private readonly IRepository < Subscriber > _subRepo ;
130
+ private readonly ISender _sender ;
198
131
199
- public NewsletterController (IRepository <Subscriber > subRepo )
200
- {
201
- _subRepo = subRepo ;
202
- }
132
+ public NewsletterController (ISender sender ) => _sender = sender ;
203
133
204
134
[HttpPost (" subscribe" )]
205
- public async Task <IActionResult > Subscribe (string email )
135
+ public async Task <IActionResult > Subscribe ([ FromForm ] string email )
206
136
{
207
- await _subRepo . AddAsync (new Subscriber { Email = email } );
137
+ await _sender . Send (new SubscribeCommand ( email ) );
208
138
return Ok (" Subscribed" );
209
139
}
210
140
}
211
141
```
212
142
213
143
---
214
144
215
- ### Step 5: Weekly Newsletter Job (Example)
216
-
217
- You can use [ Hangfire] ( https://www.hangfire.io/ ) or any scheduler to run this task weekly:
145
+ ### 📨 Weekly Newsletter Job
218
146
219
147
``` csharp
220
148
public class WeeklyNewsletterJob
221
149
{
222
- private readonly IRepository <Subscriber > _subRepo ;
223
- private readonly IRepository <BlogPost > _postRepo ;
150
+ private readonly IUnitOfWork _uow ;
224
151
225
- public WeeklyNewsletterJob (IRepository <Subscriber > subRepo , IRepository <BlogPost > postRepo )
226
- {
227
- _subRepo = subRepo ;
228
- _postRepo = postRepo ;
229
- }
152
+ public WeeklyNewsletterJob (IUnitOfWork uow ) => _uow = uow ;
230
153
231
- public async Task Send ()
154
+ public async Task SendAsync ()
232
155
{
233
- var lastWeek = DateTime .UtcNow .AddDays (- 7 );
234
- var posts = (await _postRepo .GetAllAsync ())
235
- .Where (p => p .PublishedAt > lastWeek )
236
- .ToList ();
156
+ var posts = (await _uow .GetRepository <BlogPost >().GetAllAsync ())
157
+ .Where (p => p .PublishedAt >= DateTime .UtcNow .AddDays (- 7 ));
237
158
238
- var subscribers = await _subRepo .GetAllAsync ();
159
+ var subscribers = await _uow . GetRepository < Subscriber >() .GetAllAsync ();
239
160
240
- foreach (var sub in subscribers )
161
+ foreach (var s in subscribers )
241
162
{
242
- // send email (via SMTP or 3rd party)
243
- await EmailSender .Send (sub .Email , " Weekly Newsletter" , RenderPosts (posts ));
244
- }
245
- }
246
-
247
- private string RenderPosts (IEnumerable <BlogPost > posts )
248
- {
249
- return string .Join ("
163
+ await EmailSender .Send (s .Email , " Your Weekly Digest" , string .Join ("
250
164
251
- " , posts .Select (p => $" { p .Title }
252
- { p . Content } " ));
165
+ " , posts .Select (p => p .Title )));
166
+ }
253
167
}
254
168
}
255
169
```
256
170
257
171
---
258
172
259
- ## 📌 Roadmap
173
+ ## 🧪 Testing
260
174
261
- - [x] Generic Repository
262
- - [x] Pagination
263
- - [ ] Specification Pattern
264
- - [ ] Auditing Support
265
- - [ ] Integration with MediatR & CQRS
175
+ - Handlers and pipelines are fully testable using unit tests
176
+ - Use ` KSFramework.UnitTests ` project to validate handler behavior
177
+ - Custom behaviors can be tested independently
266
178
267
179
---
268
180
269
- ## 🤝 Contributing
270
-
271
- Contributions are welcome!
181
+ ## 📌 Roadmap
272
182
273
- 1 . Fork the repository
274
- 2 . Create a feature branch
275
- 3 . Submit a pull request
183
+ - [x] Internal MediatR/CQRS support
184
+ - [x] Pagination
185
+ - [x] Unit of Work abstraction
186
+ - [ ] ValidationBehavior
187
+ - [ ] ExceptionHandlingBehavior
188
+ - [ ] Domain Events integration
276
189
277
190
---
278
191
279
192
## 📄 License
280
193
281
- This project is licensed under the MIT License. See the [ LICENSE] ( LICENSE ) file for details.
282
-
283
- ---
284
-
285
- ## 🔗 Related Projects
286
-
287
- - [ MediatR] ( https://github.com/jbogard/MediatR )
288
- - [ Scrutor] ( https://github.com/khellang/Scrutor )
289
- - [ Hangfire] ( https://www.hangfire.io/ )
194
+ Licensed under MIT.
0 commit comments