42 |
43 |
44 | ## Give a Star! :star:
45 | If you like or are using this project to learn or start your solution, please give it a star. Thanks!
46 |
47 | ## Support This Project
48 |
49 | If you have found this project helpful, either as a library that you use or as a learning tool, please consider buying me a coffee:
50 |
51 |
52 |
53 |
54 |
55 | ## Table of Contents
56 |
57 | * [Onion Architecture](#Onion-Architecture)
58 | * [reference](#reference)
59 | * [About the Project](#about-the-project)
60 |
61 | * [Getting Started](#getting-started)
62 | * [Features available in this project](#Features-available-in-this-project)
63 | * [Project description](#project-description)
64 | * [Design patterns Used](#roadmap)
65 | * [Contributing](#contributing)
66 | * [Licence Used](#Licence-Used)
67 | * [Contact](#contact)
68 | * [Support This Project](#Support-This-Project)
69 |
70 |
71 | ## Onion Architecture
72 |
73 | Onion Architecture was introduced by Jeffrey Palermo to provide a better way to build applications in perspective of better testability, maintainability, and dependability on the infrastructures like databases and services
74 |
75 | Onion, Clean or Hexagonal architecture: it's all the same. Which is built on Domain-Driven Desgin approach.
76 |
77 | Domain in center and building layer top of it. You can call it as Domain-centric Architecture too.
78 |
79 | ### Reference
80 |
81 | * [It's all the same (Domain centeric architecture) - Mark Seemann](https://blog.ploeh.dk/2013/12/03/layers-onions-ports-adapters-its-all-the-same/)
82 | * [Onion Architecture by Jeffrey Palermo](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/)
83 | * [Clean Architecture by Robert C. Martin (Uncle Bob)
84 | ](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
85 | * [Hexagonal Architecture by Dr. Alistair Cockburn](https://alistair.cockburn.us/hexagonal+architecture)
86 |
87 | ## About The Project
88 |
89 |
90 |
91 | WhiteApp or QuickApp API solution template which is built on Onion Architecture with all essential feature using .NET Core.
92 |
93 | 
94 |
95 | ## Getting Started
96 |
97 | ### Step 1: Download extension from project template
98 |
99 |
100 |
101 | 
102 |
103 | ### Step 2: Create Project
104 |
105 | Select project type as API, and select Onion Architecture
106 |
107 | 
108 |
109 | ### Step 3: Select Onion Architecture project template
110 |
111 | Select project type as API, and select Onion Architecture
112 |
113 | 
114 |
115 | ### Step 4: Project is ready
116 |
117 | 
118 |
119 | ### Step 5: Configure connection string in appsettings.json
120 |
121 | Make sure to connect proper database
122 |
123 | ```json
124 | "ConnectionStrings": {
125 | "OnionArchConn": "Data Source=(local)\\sqlexpress01;Initial Catalog=OnionDb;Integrated Security=True",
126 | "IdentityConnection": "Data Source=(local)\\sqlexpress01;Initial Catalog=OnionDb;Integrated Security=True"
127 | },
128 | ```
129 |
130 | and connect to logging in DB or proer path
131 |
132 | ```diff
133 | "Serilog": {
134 | "MinimumLevel": "Information",
135 | "WriteTo": [
136 | {
137 | "Name": "RollingFile",
138 | "Args": {
139 | ++ "pathFormat": "D:\\Logs\\log-{Date}.log",
140 | "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
141 | }
142 | },
143 | {
144 | "Name": "MSSqlServer",
145 | "Args": {
146 | ++ "connectionString": "Data Source=(local)\\sqlexpress01;Initial Catalog=OnionDb3;Integrated Security=True",
147 | "sinkOptionsSection": {
148 | "tableName": "Logs",
149 | "schemaName": "EventLogging",
150 | "autoCreateSqlTable": true
151 | },
152 | "restrictedToMinimumLevel": "Warning"
153 | }
154 | }
155 | ],
156 | "Properties": {
157 | "Application": "Onion Architecture application"
158 | }
159 | },
160 | ```
161 |
162 | ### Step 6: Create Database (Sample is for Microsoft SQL Server)
163 |
164 | For Code First approach (To run this application, use Code First apporach)
165 |
166 | - For running migration:
167 |
168 | + Option 1: Using Package Manager Console:
169 | + Open Package Manager Console, select *<< ProjectName >>.Persistence* as Default Project
170 | + Run these commands:
171 | ```sh
172 | PM> add-migration Initial-commit-Application -Context ApplicationDbContext -o Migrations/Application
173 | PM> add-migration Identity-commit -Context IdentityContext -o Migrations/Identity
174 |
175 | PM> update-database -Context ApplicationDbContext
176 | PM> update-database -Context IdentityContext
177 | ```
178 |
179 | 
180 |
181 | + Option 2: Using dotnet cli:
182 | + Install **dotnet-ef** cli:
183 | ```
184 | dotnet tool install --global dotnet-ef --version="3.1"
185 | ```
186 | + Navigate to [OA](https://github.com/Amitpnk/Onion-architecture-ASP.NET-Core/tree/develop/src/OA/) and run these commands:
187 | ```
188 | $ dotnet ef migrations add Initial-commit-Application --context ApplicationDbContext -o Migrations/Application
189 | $ dotnet ef migrations add Identity-commit-Identity --context IdentityContext -o Migrations/Identity
190 | $ dotnet ef database update --context ApplicationDbContext
191 | $ dotnet ef database update --context IdentityContext
192 | ```
193 |
194 | For Database First approach
195 |
196 | In Package Manager console in *<< ProjectName >>.Persistence*, run below command
197 |
198 | ```sh
199 | scaffold-dbcontext -provider Microsoft.EntityFrameworkCore.SqlServer -connection "Data Source=(local)\SQLexpress;Initial Catalog=OnionArchitectureDB;Integrated Security=True"
200 | ```
201 |
202 | ### Step 7: Build and run application
203 |
204 | #### Health check UI
205 |
206 | Navigate to Health Checks UI https://localhost:44356/healthcheck-ui and make sure everything is green.
207 |
208 | ** Change port number according to your application
209 |
210 | 
211 |
212 | #### Swagger UI
213 |
214 | Swagger UI https://localhost:44356/OpenAPI/index.html
215 |
216 | ** Change port number according to your application
217 |
218 | 
219 |
220 | ## Features available in this project
221 |
222 | This is default white application for ASP.NET Core API development
223 |
224 | This whiteapp contains following features, uncheck feature need to implement yet.
225 |
226 | - [x] Application is implemented on Onion architecture
227 | - [x] RESTful API
228 | - [x] Entityframework Core
229 | - [x] Expection handling
230 | - [x] Automapper
231 | - [x] Unit testing via NUnit
232 | - [x] Integration testing via NUnit
233 | - [x] Versioning
234 | - [x] Swagger UI
235 | - [x] CQRS Pattern
236 |
237 | Below features will be implemented in infrastructure layer. You can plug and play based on your project.
238 |
239 | - [x] Loggings - seriLog
240 | - [x] Email
241 | - [x] Health checks UI
242 | - [x] JWT authentication with Microsoft Identity
243 | - [x] Role based Authorization
244 | - [x] Fluent validations
245 | - [x] Database Seeding
246 | - [x] Enable CORS origin
247 | - [x] Enable feature flag (Make it true when you configure your email configuration)
248 |
249 |
250 | ## Project description
251 |
252 | we can see that all the Layers are dependent only on the Core Layers
253 |
254 |
255 | Domain layer
256 |
257 | Domain Layers (Core layer) is implemented in center and never depends on any other layer. Therefore, what we do is that we create interfaces to Persistence layer and these interfaces get implemented in the external layers. This is also known and DIP or Dependency Inversion Principle
258 |
259 |
260 |
261 | Persistence layer
262 |
263 | In Persistence layer where we implement reposistory design pattern. In our project, we have implement Entityframework which already implements a repository design pattern. DbContext will be UoW (Unit of Work) and each DbSet is the repository. This interacts with our database using dataproviders
264 |
265 |
266 |
267 | Service layer
268 |
269 | Service layer (or also called as Application layer) where we can implement business logic. For OLAP/OLTP process, we can implement CQRS design pattern. In our project, we have implemented CQRS design pattern on top of Mediator design pattern via MediatR libraries
270 |
271 |
In case you want to implement email feature logic, we define an IMailService in the Service Layer.
272 | Using DIP, it is easily possible to switch the implementations. This helps build scalable applications.
273 |
274 |
275 |
276 | Infrastructure Layer
277 |
278 | In this layer, we add our third party libraries like JWT Tokens Authentication or Serilog for logging, etc. so that all the third libraries will be in one place. In our project, we have implemented almost all important libraries, you can plug & play (add/remove) based on your project requirement in StartUp.cs file.
279 |
280 |
281 |
282 | Presentation Layer
283 |
284 | This can be WebApi or UI.
285 |
286 |
287 |
288 | ## Licence Used
289 |
290 | [](https://github.com/Amitpnk/Onion-architecture-ASP.NET-Core/blob/develop/LICENSE)
291 |
292 | See the contents of the LICENSE file for details
293 |
294 | ## Contact
295 |
296 | Having any issues or troubles getting started? Drop a mail to amit.naik8103@gmail.com or [Raise a Bug or Feature Request](https://github.com/Amitpnk/Onion-architecture-ASP.NET-Core/issues/new). Always happy to help.
297 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-tactile
--------------------------------------------------------------------------------
/code_of_conduct.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | In the interest of fostering an open and welcoming environment, we as
7 | contributors and maintainers pledge to make participation in our project and
8 | our community a harassment-free experience for everyone, regardless of age, body
9 | size, disability, ethnicity, sex characteristics, gender identity and expression,
10 | level of experience, education, socio-economic status, nationality, personal
11 | appearance, race, religion, or sexual identity and orientation.
12 |
13 | ## Our Standards
14 |
15 | Examples of behavior that contributes to creating a positive environment
16 | include:
17 |
18 | * Using welcoming and inclusive language
19 | * Being respectful of differing viewpoints and experiences
20 | * Gracefully accepting constructive criticism
21 | * Focusing on what is best for the community
22 | * Showing empathy towards other community members
23 |
24 | Examples of unacceptable behavior by participants include:
25 |
26 | * The use of sexualized language or imagery and unwelcome sexual attention or
27 | advances
28 | * Trolling, insulting/derogatory comments, and personal or political attacks
29 | * Public or private harassment
30 | * Publishing others' private information, such as a physical or electronic
31 | address, without explicit permission
32 | * Other conduct which could reasonably be considered inappropriate in a
33 | professional setting
34 |
35 | ## Our Responsibilities
36 |
37 | Project maintainers are responsible for clarifying the standards of acceptable
38 | behavior and are expected to take appropriate and fair corrective action in
39 | response to any instances of unacceptable behavior.
40 |
41 | Project maintainers have the right and responsibility to remove, edit, or
42 | reject comments, commits, code, wiki edits, issues, and other contributions
43 | that are not aligned to this Code of Conduct, or to ban temporarily or
44 | permanently any contributor for other behaviors that they deem inappropriate,
45 | threatening, offensive, or harmful.
46 |
47 | ## Scope
48 |
49 | This Code of Conduct applies within all project spaces, and it also applies when
50 | an individual is representing the project or its community in public spaces.
51 | Examples of representing a project or community include using an official
52 | project e-mail address, posting via an official social media account, or acting
53 | as an appointed representative at an online or offline event. Representation of
54 | a project may be further defined and clarified by project maintainers.
55 |
56 | ## Enforcement
57 |
58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
59 | reported by contacting the project team at [amit.naik8103@gmail.com](amit.naik8103@gmail.com). All
60 | complaints will be reviewed and investigated and will result in a response that
61 | is deemed necessary and appropriate to the circumstances. The project team is
62 | obligated to maintain confidentiality with regard to the reporter of an incident.
63 | Further details of specific enforcement policies may be posted separately.
64 |
65 | Project maintainers who do not follow or enforce the Code of Conduct in good
66 | faith may face temporary or permanent repercussions as determined by other
67 | members of the project's leadership.
68 |
69 | ## Attribution
70 |
71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
73 |
74 | [homepage]: https://www.contributor-covenant.org
75 |
76 | For answers to common questions about this code of conduct, see
77 | https://www.contributor-covenant.org/faq
78 |
79 |
--------------------------------------------------------------------------------
/docs/PPT/Onion Architecture.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/PPT/Onion Architecture.pptx
--------------------------------------------------------------------------------
/docs/PPT/Unit test.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/PPT/Unit test.pptx
--------------------------------------------------------------------------------
/docs/UML Script/CQRS.drawio:
--------------------------------------------------------------------------------
1 | 7Vrbdps4FP0aP8aLO/gxsdPpQzqTWemsto8yyKApIEaSY7tfPxJI3LFNzNirnTgPQUdHF7S3NucIZuYy2f9GQBZ9wgGMZ4YW7GfmamYYuqtZ/J+wHArLwvMKQ0hQIJ0qwwv6AaVRk9YtCiBtODKMY4ayptHHaQp91rABQvCu6bbBcXPUDISwY3jxQdy1fkEBiwqrZ2uV/SNEYaRG1jVZkwDlLA00AgHe1Uzm48xcEoxZcZXslzAWi6fWpWj3YaC2nBiBKTungZX+/pHs/vr8+YcLP339sqMPa+1OovMK4q28YTlZdlArAAO+ILKICYtwiFMQP1bWB4K3aQDFMBovVT5PGGfcqHPj35Cxg0QXbBnmpoglsawtxhQDDd6bwh6QELIjN2SWK8spCXECGTnwdgTGgKHXZv9AciMs/cqmzxjxkQ1N8thSeEsW66bW7ILiLfGhbFWBcE8IONTcMuFAR4xjtzAd588vihmoUm1NKlPOkxGcMXs448QclIcNzidXkcf5Z4tVxR3N4b/nDqaW7atKfhWK/88EUo43xwmnqkc+waLTwqVDzop6gke7CDH4kgFf1O64IDVptkFxvMQxJnlbc7OBju9zO2UEf4e1msBdrDWtHO8VEgb3x6nZpVwlgU2IPFneVXJiK9GIalLitChWZ2kN4PH4LX6yPV/srSM3ZJ+pDd6F2nDRqtsX7xrD7ts1S5wkIA3or7NjbKO5Y4xFd8coobvOjvEGsYvMYeR2cmoCuxSTBIhx8iXS8voNSFB8KOp5U5BkeaVpWmKFYYghr9iiTlXuvuxrBAgSgwz5U5DSOwoJ2pQTiVEK76LaRPW51eXYaKZavfoOCUWUwZSTTc0xXdOsNkCHvKVZLPQvw+hS72/GaPed0RNo759bPjSk/zM2tyOa2+uz3vdwvX5Iw9ePHL6K9nNbFb/V61Z72XlROsjShKGQbpwZC7kXxkL9+QhPSBrUsNp5UjGvTp40VWqibv9d196f1JNom63fXNucM7QtDe7FWRcv+TGgFPktYdojVtMlXvqmhIhfV6okCkqUamKmW94YOctLnEKILwAk02uce6bEDRwFNfLuLpDKdumJkdYkkuW1CDJwYnTyiOfqktoXKo7l38CDUTvBpPNJc5IM9i3JYC+aj0XOjreRwV5Y81ZX9nXpoG5+ejnSj8rRhPoxcAx0GyrY7lt1wWnqgqtdmQh9odZEujBdwHxSF4qI8VZsMN0TIJ7NBr31lFhcmQ19LwWG2ZDiVGRcAaBRGQ7WeCHsz4Dx6CHNLYZmloGfegtnXxAinmbOzcIGfdHA0TnzRdPJjjoB6n9NiL4z05Hy0ATc+AngMxfNbai/NerrdHTlqM/oe0k0LrXUvb7UcgUYWAMKz31dQSOQiUv/wBPgQKQTp/LGdZFkPq1LA/C/h3nq+ceWiTS6Qa+aSjj5T95JzV78unqzehR/0ySepmu0IjtH7dgadVXwEDU/ORim6UWpp+LcsT2MiyVdlp9faHVdFwWxLMgH8RNYw/gZU5S/3zVXa8wYTmoO9zEKRQUTx20PQJZ8voA57HVhkKRI9qH46mQOdtScb2nu1sbIWJkrgR33DBCscJWPoCmg87y524rKu9A5Jb518FxHWUegx4vVZyPFjq8+vjEf/wU=
--------------------------------------------------------------------------------
/docs/UML Script/DP_AP.drawio:
--------------------------------------------------------------------------------
1 | 7ZlLU9swEMc/TWbogRm/kxxJwuMAUwZaOCvyxlZRLCPLefDpK9lyEluG0iEhdFwOwfqvJMv721W0Ts8dz1eXHKXxDQuB9hwrXPXcSc9xBr4jP5WwLgXfsksh4iQspR3hnryAFi2t5iSErNZRMEYFSesiZkkCWNQ0xDlb1rvNGK3fNUURGMI9RtRUH0ko4uqxrK1+BSSKqzvblrbMUdVZC1mMQrbckdzznjvmjInyar4aA1W+q/xSjrt4xbpZGIdEvGfA6vL6Cn/H4ePP56sXn019NJyc6lkWiOb6gfVixbrywDImAu5ThFV7KSH33FEs5lS2bHmJsrT0+4ysQN5qNCOUjhllvBjuzmYQYCz1THD2BDuWsD+cSm+5I70G4AJWrz6cvXGZDDVgcxB8LbvoAUEFRIeZPfTL9nILza5IxLvAKhHpQIk2c299KS+0O//Ctc47fAuhDDbdZFzELGIJoudbdSSjJlXWGYXVmYpnqXGWJ6Hy9UR5bzvumrFUU/kFQqx1NqFcsDqzGUuENtqeiSz0YRB6bcgGztQNAmmJOAqJZFMDrf42ONWjvQ1TeoLlHMNbPtQZj3gE4k9hbAYHB4oEWdTXsX/Qh8+hAYb2HJoOfM/fUw55gf/lcsj6n0MfzSHvnTnkHDOHvIPnENgSSb8NyDDouyjYTw45Xy+HghbXBlToEK75OHjOWWU4zYrgPpMd7H662hrlVaT+TyAjUVJ4RwjgSTWrXGQ5cdnN4CidK+ro6kQSlkADn5YQVTd0J1jCAKmPFCoiz2pn2jAnYVhkfFus1DN+H2eOYf3M4Xgma68F9cFI9w9E+oxj5U8scg4d5u1b/tfiPfgc3uXXqJ55yregO8bfbfKvao5j8R8a/Mey/JYKhYWqwr8kiOa5aR8bcb9R/PmeAWbQAsY7FBjbPLaOKMNP3UPjDRrnId/cMz8XjVmV33KSYJKqPa4rWJwmlsDcyj4Xi1kAXCAsmJp6bH733EHKMlKarclta5/u0Gzuf65z7CTzDZo3D+OCknXz8HDTHTTN/e/4aMxy8O78/sdFXgzNgC8I7tJGOGzwcY+9EZpF3MljjNRpfs1ytTCUiG//CKCirRfp7INW8+2KbdJqfbtyOFxmDXZyxZYbWmQuzxVz6C4yr/nDTBsy/1ORmWVTDVnG6KJ4zcHZVLLrKrjAfUeuVfn4QXCyuf0dtLDt/Jjsnv8G
--------------------------------------------------------------------------------
/docs/UML Script/OnionArchitecture.drawio:
--------------------------------------------------------------------------------
1 | 7Vldb5swFP01eUwUMJDksU26dVKnRUqlSXsz2IBbwMyYhvTXz4D5NFG7LSxRupcWH9sX+5x7fS/OBKzD7DODsf+VIhxM9DnKJmAz0fWlqYu/OXAoAWNllIDHCCohrQF25BVLcC7RlCCcdAZySgNO4i7o0CjCDu9gkDG67w5zadB9aww9rAA7BwYq+p0g7lfbmjf4PSaeX71Zm8ueEFaDJZD4ENF9CwJ3E7BmlPLyKczWOMi5q3gp53060lsvjOGIv2fCLvtx/2D5z+nT43dr+rp9QmkytUorLzBI5YblYvmhYkBYEWSLxu3eJxzvYujkPXsht8B8HgaipYlHmMSlAi7JsHjprUuCYE0DygpDwHWx5TgCTzijz7jVgxYrW/AGbuVqMOM4O7pNrSZPOB2mIebsIIZUE+aLmVlOki5Xt/eNgIYpMb8lXg1C6TRebb3hVTxIan+DZnB9NC+smd7juUFaTAOt5r/NdQs+Odva1bEtTpyeU2sN0iJb14BKdQ2enGj9+ohezrs0V+0WyVqVm/zuwT+WN8/fwXKEbvJkJ1oRjXCX2XI0Rkqi65EiMitkHuZvpQuVvBY55gA3FcZwADl56S5jiC/5hi0lYoG1NsDoamNYev8ISWjKHCzntXNh39SqZ0pTTJVcKKYKCeut/4Wq78i8uScTUYo8QBsHW5oQTmgkumzKOQ3zUKhKitz7EUz8PHCKBgyIlw91hMSYdf2hMnsjx3Aa12FV1ThaaT3OVxJmXl7azULqPKfxLIQs/xeQws+ORyMiTAR0ueKIstzs0Zg+QeSCXjYCKzBbDJyQA/5pNYnr9NG7uHadR9RUreX0gQpjoQ8VGPpo5/Hyv6J/rKjRO3gvQc+VoueGhpBEiqpi27wrSJdDmXrb9ElIEbWvZEgQCo5VSYymEaodxKURl9/Jmnmis1N7O8qMgYNzNE10tebZYpaQhONIMPNhhdFMtRz9t8Ko31Y7zF7IRxZldW5N1M+wLUvEZmCRdT6sLuDcuqi3Pl8il0FBd+rwlImQsYJcCpt1FLJ+pvmdYEHRNCk4uhEDNDPOCqKqfvHk8Zq6i1OYUemAYDM1zHEkN8xuOQGsc2tuKJo/4oRfvkRjKTRwTwjOnsRMRSRFoKqEdg6iXEYF0W9cYNkltQ92DUDn2SsI/5bysugu8F55jSBeuoMXW5azxLZ7oout/oWHqoE1oMFyNA3U+48N5NCGyaVWEqfIUD0RDDBWIIhm88tSeWfV/DwH7n4B
--------------------------------------------------------------------------------
/docs/UML Script/UML Diagram.nomnoml:
--------------------------------------------------------------------------------
1 | [API] --> [Service]
2 | [API] --> [Persistence]
3 | [Service] --:> [Persistence]
4 | [Persistence] --:> [Data]
5 | [Data] --:> [Domain]
--------------------------------------------------------------------------------
/docs/img/CQRS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/CQRS.png
--------------------------------------------------------------------------------
/docs/img/OnionArchitecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/OnionArchitecture.png
--------------------------------------------------------------------------------
/docs/img/OnionArchitecture_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/OnionArchitecture_icon.png
--------------------------------------------------------------------------------
/docs/img/Step.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/Step.png
--------------------------------------------------------------------------------
/docs/img/Step1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/Step1.png
--------------------------------------------------------------------------------
/docs/img/Step2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/Step2.png
--------------------------------------------------------------------------------
/docs/img/Step3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/Step3.png
--------------------------------------------------------------------------------
/docs/img/Step4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/Step4.png
--------------------------------------------------------------------------------
/docs/img/Step5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/Step5.png
--------------------------------------------------------------------------------
/docs/img/Step6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/Step6.png
--------------------------------------------------------------------------------
/docs/img/classDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/docs/img/classDiagram.png
--------------------------------------------------------------------------------
/src/.template.config/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Amit Naik",
3 | "classifications": [],
4 | "description": "WhiteApp or QuickApp API solution template which is built on Domain-Driven Design (DDD)-based with all essential feature using .NET Core",
5 | "name": "Onion Architecture",
6 | "defaultName": "OA",
7 | "identity": "OnionArchitecture.CSharp",
8 | "groupIdentity": "OnionArchitecture",
9 | "tags": {
10 | "language": "C#",
11 | "type": "project"
12 | },
13 | "shortName": "OnionArchitecture",
14 | "sourceName": "OA",
15 | "guids": [],
16 | "primaryOutputs": [
17 | {
18 | "path": "OA\\OA.csproj"
19 | },
20 | {
21 | "path": "OA.Domain\\OA.Domain.csproj"
22 | },
23 | {
24 | "path": "OA.Infrastructure\\OA.Infrastructure.csproj"
25 | },
26 | {
27 | "path": "OA.Persistence\\OA.Persistence.csproj"
28 | },
29 | {
30 | "path": "OA.Service\\OA.Service.csproj"
31 | },
32 | {
33 | "path": "OA.Test.Integration\\OA.Test.Integration.csproj"
34 | },
35 | {
36 | "path": "OA.Test.Unit\\OA.Test.Unit.csproj"
37 | }
38 | ]
39 | }
--------------------------------------------------------------------------------
/src/.template.config/template.vstemplate:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Onion Architecture
5 | WhiteApp or QuickApp API solution template which is built on Onion Architecture using .NET Core
6 | OnionArchitecture.CSharp
7 | OnionArchitecture
8 |
9 | project-icon.png
10 |
11 | CSharp
12 | 1
13 | 5000
14 | true
15 | true
16 | Enabled
17 | true
18 | C#
19 | windows
20 | API
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Microsoft.VisualStudio.TemplateEngine.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
33 | Microsoft.VisualStudio.TemplateEngine.Wizard.TemplateEngineWizard
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/OA.Domain/Auth/ApplicationUser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 |
3 | namespace OA.Domain.Auth;
4 |
5 | public class ApplicationUser : IdentityUser
6 | {
7 | public string FirstName { get; set; }
8 | public string LastName { get; set; }
9 | public List RefreshTokens { get; set; }
10 | public bool OwnsToken(string token)
11 | {
12 | return this.RefreshTokens?.Find(x => x.Token == token) != null;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Auth/AuthenticationRequest.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Auth;
2 |
3 | public class AuthenticationRequest
4 | {
5 | public string Email { get; set; }
6 | public string Password { get; set; }
7 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Auth/AuthenticationResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace OA.Domain.Auth;
4 |
5 | public class AuthenticationResponse
6 | {
7 | public string Id { get; set; }
8 | public string UserName { get; set; }
9 | public string Email { get; set; }
10 | public List Roles { get; set; }
11 | public bool IsVerified { get; set; }
12 | public string JWToken { get; set; }
13 | [JsonIgnore]
14 | public string RefreshToken { get; set; }
15 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Auth/ForgotPasswordRequest.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace OA.Domain.Auth;
4 |
5 | public class ForgotPasswordRequest
6 | {
7 | [Required]
8 | [EmailAddress]
9 | public string Email { get; set; }
10 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Auth/RefreshToken.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Auth;
2 |
3 | public class RefreshToken
4 | {
5 | public int Id { get; set; }
6 | public string Token { get; set; }
7 | public DateTime Expires { get; set; }
8 | public bool IsExpired => DateTime.UtcNow >= Expires;
9 | public DateTime Created { get; set; }
10 | public string CreatedByIp { get; set; }
11 | public DateTime? Revoked { get; set; }
12 | public string RevokedByIp { get; set; }
13 | public string ReplacedByToken { get; set; }
14 | public bool IsActive => Revoked == null && !IsExpired;
15 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Auth/RegisterRequest.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace OA.Domain.Auth;
4 |
5 | public class RegisterRequest
6 | {
7 | [Required]
8 | public string FirstName { get; set; }
9 |
10 | [Required]
11 | public string LastName { get; set; }
12 |
13 | [Required]
14 | [EmailAddress]
15 | public string Email { get; set; }
16 | [Required]
17 | [MinLength(6)]
18 | public string UserName { get; set; }
19 |
20 | [Required]
21 | [MinLength(6)]
22 | public string Password { get; set; }
23 |
24 | [Required]
25 | [Compare("Password")]
26 | public string ConfirmPassword { get; set; }
27 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Auth/ResetPasswordRequest.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace OA.Domain.Auth;
4 |
5 | public class ResetPasswordRequest
6 | {
7 | [Required]
8 | [EmailAddress]
9 | public string Email { get; set; }
10 | [Required]
11 | public string Token { get; set; }
12 | [Required]
13 | [MinLength(6)]
14 | public string Password { get; set; }
15 |
16 | [Required]
17 | [Compare("Password")]
18 | public string ConfirmPassword { get; set; }
19 | }
--------------------------------------------------------------------------------
/src/OA.Domain/BaseEntity.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace OA.Domain;
4 |
5 | public class BaseEntity
6 | {
7 | [Key]
8 | public int Id { get; set; }
9 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Common/IpHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Sockets;
3 |
4 | namespace OA.Domain.Common;
5 |
6 | public class IpHelper
7 | {
8 | public static string GetIpAddress()
9 | {
10 | var host = Dns.GetHostEntry(Dns.GetHostName());
11 | foreach (var ip in host.AddressList)
12 | {
13 | if (ip.AddressFamily == AddressFamily.InterNetwork)
14 | {
15 | return ip.ToString();
16 | }
17 | }
18 | return string.Empty;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Common/Response.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Common;
2 |
3 | public class Response
4 | {
5 | public Response()
6 | {
7 | }
8 | public Response(T data, string message = null)
9 | {
10 | Succeeded = true;
11 | Message = message;
12 | Data = data;
13 | }
14 | public Response(string message)
15 | {
16 | Succeeded = false;
17 | Message = message;
18 | }
19 | public bool Succeeded { get; set; }
20 | public string Message { get; set; }
21 | public List Errors { get; set; }
22 | public T Data { get; set; }
23 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Entities/Category.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Entities;
2 |
3 | public class Category : BaseEntity
4 | {
5 | public string CategoryName { get; set; }
6 | public string Description { get; set; }
7 | public List Products { get; set; }
8 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Entities/Customer.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Entities;
2 |
3 | public class Customer : BaseEntity
4 | {
5 | public string CustomerName { get; set; }
6 | public string ContactName { get; set; }
7 | public string ContactTitle { get; set; }
8 | public string Address { get; set; }
9 | public string City { get; set; }
10 | public string Region { get; set; }
11 | public string PostalCode { get; set; }
12 | public string Country { get; set; }
13 | public string Phone { get; set; }
14 | public string Fax { get; set; }
15 | public List Orders { get; set; }
16 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Entities/Order.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Entities;
2 |
3 | public class Order : BaseEntity
4 | {
5 | public Customer Customers { get; set; }
6 | public int CustomerId { get; set; }
7 | public int EmployeeId { get; set; }
8 | public DateTime OrderDate { get; set; }
9 | public DateTime RequiredDate { get; set; }
10 | public List OrderDetails { get; set; }
11 |
12 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Entities/OrderDetail.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Entities;
2 |
3 | public class OrderDetail
4 | {
5 | public int OrderId { get; set; }
6 | public int ProductId { get; set; }
7 | public Order Orders { get; set; }
8 | public Product Product { get; set; }
9 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Entities/Product.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations.Schema;
2 |
3 | namespace OA.Domain.Entities;
4 |
5 | public class Product : BaseEntity
6 | {
7 | public string ProductName { get; set; }
8 |
9 | [Column(TypeName = "money")]
10 | public decimal UnitPrice { get; set; }
11 |
12 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Entities/Supplier.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Entities;
2 |
3 | public class Supplier : BaseEntity
4 | {
5 | public string SupplierName { get; set; }
6 | public List Products { get; set; }
7 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Enum/FeatureManagement.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Enum;
2 |
3 | public enum FeatureManagement
4 | {
5 | EnableEmailService
6 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Enum/Roles.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Enum;
2 |
3 | public enum Roles
4 | {
5 | SuperAdmin,
6 | Admin,
7 | Moderator,
8 | Basic
9 | }
10 |
11 | public static class Constants
12 | {
13 | public static readonly string SuperAdmin = Guid.NewGuid().ToString();
14 | public static readonly string Admin = Guid.NewGuid().ToString();
15 | public static readonly string Moderator = Guid.NewGuid().ToString();
16 | public static readonly string Basic = Guid.NewGuid().ToString();
17 |
18 | public static readonly string SuperAdminUser = Guid.NewGuid().ToString();
19 | public static readonly string BasicUser = Guid.NewGuid().ToString();
20 | }
--------------------------------------------------------------------------------
/src/OA.Domain/OA.Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/OA.Domain/Settings/AppSettings.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Settings;
2 |
3 | public class AppSettings
4 | {
5 | public ApplicationDetail ApplicationDetail { get; set; }
6 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Settings/ApplicationDetail.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Settings;
2 |
3 | public class ApplicationDetail
4 | {
5 | public string ApplicationName { get; set; }
6 | public string Description { get; set; }
7 | public string ContactWebsite { get; set; }
8 | public string LicenseDetail { get; set; }
9 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Settings/JWTSettings.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Settings;
2 |
3 | public class JWTSettings
4 | {
5 | public string Key { get; set; }
6 | public string Issuer { get; set; }
7 | public string Audience { get; set; }
8 | public double DurationInMinutes { get; set; }
9 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Settings/MailRequest.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Settings;
2 |
3 | public class MailRequest
4 | {
5 | public string ToEmail { get; set; }
6 | public string Subject { get; set; }
7 | public string Body { get; set; }
8 | public string From { get; set; }
9 |
10 | }
--------------------------------------------------------------------------------
/src/OA.Domain/Settings/MailSettings.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Domain.Settings;
2 |
3 | public class MailSettings
4 | {
5 | public string EmailFrom { get; set; }
6 | public string SmtpHost { get; set; }
7 | public int SmtpPort { get; set; }
8 | public string SmtpUser { get; set; }
9 | public string SmtpPass { get; set; }
10 | public string DisplayName { get; set; }
11 | }
--------------------------------------------------------------------------------
/src/OA.Infrastructure/Extension/ConfigureContainer.cs:
--------------------------------------------------------------------------------
1 | using HealthChecks.UI.Client;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Diagnostics.HealthChecks;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.Extensions.Diagnostics.HealthChecks;
6 | using Microsoft.Extensions.Logging;
7 | using OA.Service.Middleware;
8 | using Serilog;
9 |
10 | namespace OA.Infrastructure.Extension;
11 |
12 | public static class ConfigureContainer
13 | {
14 | public static void ConfigureCustomExceptionMiddleware(this IApplicationBuilder app)
15 | {
16 | app.UseMiddleware();
17 | }
18 |
19 | public static void ConfigureSwagger(this IApplicationBuilder app)
20 | {
21 | app.UseSwagger();
22 |
23 | app.UseSwaggerUI(setupAction =>
24 | {
25 | setupAction.SwaggerEndpoint("/swagger/OpenAPISpecification/swagger.json", "Onion Architecture API");
26 | setupAction.RoutePrefix = "OpenAPI";
27 | });
28 | }
29 |
30 | public static void ConfigureSwagger(this ILoggerFactory loggerFactory)
31 | {
32 | loggerFactory.AddSerilog();
33 | }
34 |
35 | public static void UseHealthCheck(this IApplicationBuilder app)
36 | {
37 | app.UseHealthChecks("/healthz", new HealthCheckOptions
38 | {
39 | Predicate = _ => true,
40 | ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
41 | ResultStatusCodes =
42 | {
43 | [HealthStatus.Healthy] = StatusCodes.Status200OK,
44 | [HealthStatus.Degraded] = StatusCodes.Status500InternalServerError,
45 | [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable,
46 | },
47 | }).UseHealthChecksUI(setup =>
48 | {
49 | setup.ApiPath = "/healthcheck";
50 | setup.UIPath = "/healthcheck-ui";
51 | });
52 | }
53 | }
--------------------------------------------------------------------------------
/src/OA.Infrastructure/Extension/ConfigureServiceContainer.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.Extensions.Configuration;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Diagnostics.HealthChecks;
7 | using Microsoft.OpenApi.Models;
8 | using OA.Domain.Settings;
9 | using OA.Infrastructure.Mapping;
10 | using OA.Persistence;
11 | using OA.Service.Contract;
12 | using OA.Service.Implementation;
13 |
14 | namespace OA.Infrastructure.Extension;
15 |
16 | public static class ConfigureServiceContainer
17 | {
18 | public static void AddDbContext(this IServiceCollection serviceCollection, IConfiguration configuration)
19 | {
20 |
21 | if (configuration.GetValue("UseInMemoryDatabase"))
22 | {
23 | serviceCollection.AddDbContext(options =>
24 | options.UseInMemoryDatabase(nameof(ApplicationDbContext)));
25 | }
26 | else
27 | {
28 | serviceCollection.AddDbContext(options =>
29 | options.UseSqlServer(configuration.GetConnectionString("OnionArchConn")
30 | , b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
31 | }
32 |
33 | }
34 |
35 | public static void AddAutoMapper(this IServiceCollection serviceCollection)
36 | {
37 | var mappingConfig = new MapperConfiguration(mc =>
38 | {
39 | mc.AddProfile(new CustomerProfile());
40 | });
41 | IMapper mapper = mappingConfig.CreateMapper();
42 | serviceCollection.AddSingleton(mapper);
43 | }
44 |
45 | public static void AddScopedServices(this IServiceCollection serviceCollection)
46 | {
47 | serviceCollection.AddScoped(provider => provider.GetService());
48 |
49 |
50 | }
51 | public static void AddTransientServices(this IServiceCollection serviceCollection)
52 | {
53 | serviceCollection.AddTransient();
54 | serviceCollection.AddTransient();
55 | }
56 |
57 |
58 |
59 | public static void AddSwaggerOpenAPI(this IServiceCollection serviceCollection)
60 | {
61 | serviceCollection.AddSwaggerGen(setupAction =>
62 | {
63 |
64 | setupAction.SwaggerDoc(
65 | "OpenAPISpecification",
66 | new OpenApiInfo()
67 | {
68 | Title = "Onion Architecture WebAPI",
69 | Version = "1",
70 | Description = "Through this API you can access customer details",
71 | Contact = new OpenApiContact()
72 | {
73 | Email = "amit.naik8103@gmail.com",
74 | Name = "Amit Naik",
75 | Url = new Uri("https://amitpnk.github.io/")
76 | },
77 | License = new OpenApiLicense()
78 | {
79 | Name = "MIT License",
80 | Url = new Uri("https://opensource.org/licenses/MIT")
81 | }
82 | });
83 |
84 | setupAction.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
85 | {
86 | Type = SecuritySchemeType.Http,
87 | Scheme = "bearer",
88 | BearerFormat = "JWT",
89 | Description = $"Input your Bearer token in this format - Bearer token to access this API",
90 | });
91 | setupAction.AddSecurityRequirement(new OpenApiSecurityRequirement
92 | {
93 | {
94 | new OpenApiSecurityScheme
95 | {
96 | Reference = new OpenApiReference
97 | {
98 | Type = ReferenceType.SecurityScheme,
99 | Id = "Bearer",
100 | },
101 | }, new List()
102 | },
103 | });
104 | });
105 |
106 | }
107 |
108 | public static void AddMailSetting(this IServiceCollection serviceCollection,
109 | IConfiguration configuration)
110 | {
111 | serviceCollection.Configure(configuration.GetSection("MailSettings"));
112 | }
113 |
114 | public static void AddController(this IServiceCollection serviceCollection)
115 | {
116 | serviceCollection.AddControllers().AddNewtonsoftJson();
117 | }
118 |
119 | public static void AddVersion(this IServiceCollection serviceCollection)
120 | {
121 | serviceCollection.AddApiVersioning(config =>
122 | {
123 | config.DefaultApiVersion = new ApiVersion(1, 0);
124 | config.AssumeDefaultVersionWhenUnspecified = true;
125 | config.ReportApiVersions = true;
126 | });
127 | }
128 |
129 | public static void AddHealthCheck(this IServiceCollection serviceCollection, AppSettings appSettings, IConfiguration configuration)
130 | {
131 | serviceCollection.AddHealthChecks()
132 | .AddDbContextCheck(name: "Application DB Context", failureStatus: HealthStatus.Degraded)
133 | .AddUrlGroup(new Uri(appSettings.ApplicationDetail.ContactWebsite), name: "My personal website", failureStatus: HealthStatus.Degraded)
134 | .AddSqlServer(configuration.GetConnectionString("OnionArchConn"));
135 |
136 | serviceCollection.AddHealthChecksUI(setupSettings: setup =>
137 | {
138 | setup.AddHealthCheckEndpoint("Basic Health Check", $"/healthz");
139 | }).AddInMemoryStorage();
140 | }
141 |
142 |
143 | }
--------------------------------------------------------------------------------
/src/OA.Infrastructure/Mapping/CustomerProfile.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using OA.Domain.Entities;
3 | using OA.Infrastructure.ViewModel;
4 |
5 | namespace OA.Infrastructure.Mapping;
6 |
7 | public class CustomerProfile : Profile
8 | {
9 | public CustomerProfile()
10 | {
11 | CreateMap()
12 | .ForMember(dest => dest.Id,
13 | opt => opt.MapFrom(src => src.CustomerId))
14 | .ReverseMap();
15 | }
16 | }
--------------------------------------------------------------------------------
/src/OA.Infrastructure/OA.Infrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/OA.Infrastructure/ViewModel/CustomerModel.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Infrastructure.ViewModel;
2 |
3 | public class CustomerModel
4 | {
5 | public int CustomerId { get; set; }
6 | public string CustomerName { get; set; }
7 | public string ContactName { get; set; }
8 | public string ContactTitle { get; set; }
9 | public string Address { get; set; }
10 | public string City { get; set; }
11 | public string Region { get; set; }
12 | public string PostalCode { get; set; }
13 | public string Country { get; set; }
14 | public string Phone { get; set; }
15 | public string Fax { get; set; }
16 | }
--------------------------------------------------------------------------------
/src/OA.Persistence/ApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using OA.Domain.Entities;
3 |
4 | namespace OA.Persistence;
5 |
6 | public class ApplicationDbContext : DbContext, IApplicationDbContext
7 | {
8 | // This constructor is used of runit testing
9 | public ApplicationDbContext()
10 | {
11 |
12 | }
13 | public ApplicationDbContext(DbContextOptions options) : base(options)
14 | {
15 | ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
16 | }
17 |
18 | public DbSet Customers { get; set; }
19 | public DbSet Orders { get; set; }
20 | public DbSet Products { get; set; }
21 | public DbSet Categories { get; set; }
22 | public DbSet Suppliers { get; set; }
23 |
24 | protected override void OnModelCreating(ModelBuilder modelBuilder)
25 | {
26 | modelBuilder.Entity().HasKey(o => new { o.OrderId, o.ProductId });
27 | }
28 |
29 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
30 | {
31 | if (!optionsBuilder.IsConfigured)
32 | {
33 | optionsBuilder
34 | .UseSqlServer("DataSource=app.db");
35 | }
36 |
37 | }
38 |
39 | public async Task SaveChangesAsync()
40 | {
41 | return await base.SaveChangesAsync();
42 | }
43 | }
--------------------------------------------------------------------------------
/src/OA.Persistence/ApplicationDbContext.dgml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
156 |
160 |
164 |
168 |
172 |
176 |
180 |
184 |
185 |
--------------------------------------------------------------------------------
/src/OA.Persistence/IApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using OA.Domain.Entities;
3 |
4 | namespace OA.Persistence;
5 |
6 | public interface IApplicationDbContext
7 | {
8 | DbSet Categories { get; set; }
9 | DbSet Customers { get; set; }
10 | DbSet Orders { get; set; }
11 | DbSet Products { get; set; }
12 | DbSet Suppliers { get; set; }
13 |
14 | Task SaveChangesAsync();
15 | }
--------------------------------------------------------------------------------
/src/OA.Persistence/IdentityContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore;
4 | using OA.Domain.Auth;
5 | using OA.Persistence.Seeds;
6 |
7 | namespace OA.Persistence;
8 |
9 | public class IdentityContext(DbContextOptions options)
10 | : IdentityDbContext(options)
11 | {
12 |
13 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
14 | {
15 | if (!optionsBuilder.IsConfigured)
16 | {
17 | optionsBuilder
18 | .UseSqlServer("DataSource=app.db");
19 | }
20 |
21 | }
22 | protected override void OnModelCreating(ModelBuilder modelBuilder)
23 | {
24 | base.OnModelCreating(modelBuilder);
25 | modelBuilder.HasDefaultSchema("Identity");
26 | modelBuilder.Entity(entity =>
27 | {
28 | entity.ToTable(name: "User");
29 | });
30 |
31 | modelBuilder.Entity(entity =>
32 | {
33 | entity.ToTable(name: "Role");
34 | });
35 | modelBuilder.Entity>(entity =>
36 | {
37 | entity.ToTable("UserRoles");
38 | });
39 |
40 | modelBuilder.Entity>(entity =>
41 | {
42 | entity.ToTable("UserClaims");
43 | });
44 |
45 | modelBuilder.Entity>(entity =>
46 | {
47 | entity.ToTable("UserLogins");
48 | });
49 |
50 | modelBuilder.Entity>(entity =>
51 | {
52 | entity.ToTable("RoleClaims");
53 | });
54 |
55 | modelBuilder.Entity>(entity =>
56 | {
57 | entity.ToTable("UserTokens");
58 | });
59 |
60 | modelBuilder.Seed();
61 | }
62 | }
--------------------------------------------------------------------------------
/src/OA.Persistence/OA.Persistence.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/OA.Persistence/Seeds/ContextSeed.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using Microsoft.EntityFrameworkCore;
3 | using OA.Domain.Auth;
4 |
5 | namespace OA.Persistence.Seeds;
6 |
7 | public static class ContextSeed
8 | {
9 | public static void Seed(this ModelBuilder modelBuilder)
10 | {
11 | CreateRoles(modelBuilder);
12 |
13 | CreateBasicUsers(modelBuilder);
14 |
15 | MapUserRole(modelBuilder);
16 | }
17 |
18 | private static void CreateRoles(ModelBuilder modelBuilder)
19 | {
20 | List roles = DefaultRoles.IdentityRoleList();
21 | modelBuilder.Entity().HasData(roles);
22 | }
23 |
24 | private static void CreateBasicUsers(ModelBuilder modelBuilder)
25 | {
26 | List users = DefaultUser.IdentityBasicUserList();
27 | modelBuilder.Entity().HasData(users);
28 | }
29 |
30 | private static void MapUserRole(ModelBuilder modelBuilder)
31 | {
32 | var identityUserRoles = MappingUserRole.IdentityUserRoleList();
33 | modelBuilder.Entity>().HasData(identityUserRoles);
34 | }
35 | }
--------------------------------------------------------------------------------
/src/OA.Persistence/Seeds/DefaultRoles.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using OA.Domain.Enum;
3 |
4 | namespace OA.Persistence.Seeds;
5 |
6 |
7 | public static class DefaultRoles
8 | {
9 | public static List IdentityRoleList()
10 | {
11 | return new List()
12 | {
13 | new IdentityRole
14 | {
15 | Id = Constants.SuperAdmin,
16 | Name = Roles.SuperAdmin.ToString(),
17 | NormalizedName = Roles.SuperAdmin.ToString()
18 | },
19 | new IdentityRole
20 | {
21 | Id = Constants.Admin,
22 | Name = Roles.Admin.ToString(),
23 | NormalizedName = Roles.Admin.ToString()
24 | },
25 | new IdentityRole
26 | {
27 | Id = Constants.Moderator,
28 | Name = Roles.Moderator.ToString(),
29 | NormalizedName = Roles.Moderator.ToString()
30 | },
31 | new IdentityRole
32 | {
33 | Id = Constants.Basic,
34 | Name = Roles.Basic.ToString(),
35 | NormalizedName = Roles.Basic.ToString()
36 | }
37 | };
38 | }
39 | }
--------------------------------------------------------------------------------
/src/OA.Persistence/Seeds/DefaultUser.cs:
--------------------------------------------------------------------------------
1 | using OA.Domain.Auth;
2 | using OA.Domain.Enum;
3 |
4 | namespace OA.Persistence.Seeds;
5 |
6 | public static class DefaultUser
7 | {
8 | public static List IdentityBasicUserList()
9 | {
10 | return new List()
11 | {
12 | new ApplicationUser
13 | {
14 | Id = Constants.SuperAdminUser,
15 | UserName = "superadmin",
16 | Email = "superadmin@gmail.com",
17 | FirstName = "Amit",
18 | LastName = "Naik",
19 | EmailConfirmed = true,
20 | PhoneNumberConfirmed = true,
21 | // Password@123
22 | PasswordHash = "AQAAAAEAACcQAAAAEBLjouNqaeiVWbN0TbXUS3+ChW3d7aQIk/BQEkWBxlrdRRngp14b0BIH0Rp65qD6mA==",
23 | NormalizedEmail= "SUPERADMIN@GMAIL.COM",
24 | NormalizedUserName="SUPERADMIN"
25 | },
26 | new ApplicationUser
27 | {
28 | Id = Constants.BasicUser,
29 | UserName = "basicuser",
30 | Email = "basicuser@gmail.com",
31 | FirstName = "Basic",
32 | LastName = "User",
33 | EmailConfirmed = true,
34 | PhoneNumberConfirmed = true,
35 | // Password@123
36 | PasswordHash = "AQAAAAEAACcQAAAAEBLjouNqaeiVWbN0TbXUS3+ChW3d7aQIk/BQEkWBxlrdRRngp14b0BIH0Rp65qD6mA==",
37 | NormalizedEmail= "BASICUSER@GMAIL.COM",
38 | NormalizedUserName="BASICUSER"
39 | },
40 | };
41 | }
42 | }
--------------------------------------------------------------------------------
/src/OA.Persistence/Seeds/MappingUserRole.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using OA.Domain.Enum;
3 |
4 | namespace OA.Persistence.Seeds;
5 |
6 | public static class MappingUserRole
7 | {
8 | public static List> IdentityUserRoleList()
9 | {
10 | return new List>()
11 | {
12 | new IdentityUserRole
13 | {
14 | RoleId = Constants.Basic,
15 | UserId = Constants.BasicUser
16 | },
17 | new IdentityUserRole
18 | {
19 | RoleId = Constants.SuperAdmin,
20 | UserId = Constants.SuperAdminUser
21 | },
22 | new IdentityUserRole
23 | {
24 | RoleId = Constants.Admin,
25 | UserId = Constants.SuperAdminUser
26 | },
27 | new IdentityUserRole
28 | {
29 | RoleId = Constants.Moderator,
30 | UserId = Constants.SuperAdminUser
31 | },
32 | new IdentityUserRole
33 | {
34 | RoleId = Constants.Basic,
35 | UserId = Constants.SuperAdminUser
36 | }
37 | };
38 | }
39 | }
--------------------------------------------------------------------------------
/src/OA.Service/Contract/IAccountService.cs:
--------------------------------------------------------------------------------
1 | using OA.Domain.Auth;
2 | using OA.Domain.Common;
3 |
4 | namespace OA.Service.Contract;
5 |
6 | public interface IAccountService
7 | {
8 | Task> AuthenticateAsync(AuthenticationRequest request, string ipAddress);
9 | Task> RegisterAsync(RegisterRequest request, string origin);
10 | Task> ConfirmEmailAsync(string userId, string code);
11 | Task ForgotPassword(ForgotPasswordRequest model, string origin);
12 | Task> ResetPassword(ResetPasswordRequest model);
13 | }
--------------------------------------------------------------------------------
/src/OA.Service/Contract/IAuthenticatedUserService.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Service.Contract;
2 |
3 | public interface IAuthenticatedUserService
4 | {
5 | string UserId { get; }
6 | }
--------------------------------------------------------------------------------
/src/OA.Service/Contract/IDateTimeService.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Service.Contract;
2 |
3 | public interface IDateTimeService
4 | {
5 | DateTime NowUtc { get; }
6 | }
--------------------------------------------------------------------------------
/src/OA.Service/Contract/IEmailService.cs:
--------------------------------------------------------------------------------
1 | using OA.Domain.Settings;
2 |
3 | namespace OA.Service.Contract;
4 |
5 | public interface IEmailService
6 | {
7 | Task SendEmailAsync(MailRequest mailRequest);
8 |
9 | }
--------------------------------------------------------------------------------
/src/OA.Service/DependencyInjection.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.AspNetCore.Authentication.JwtBearer;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.AspNetCore.Identity;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.IdentityModel.Tokens;
9 | using Newtonsoft.Json;
10 | using OA.Domain.Auth;
11 | using OA.Domain.Common;
12 | using OA.Domain.Settings;
13 | using OA.Persistence;
14 | using OA.Service.Contract;
15 | using OA.Service.Implementation;
16 | using System.Reflection;
17 | using System.Text;
18 |
19 | namespace OA.Service;
20 |
21 | public static class DependencyInjection
22 | {
23 | public static void AddServiceLayer(this IServiceCollection services)
24 | {
25 | // or you can use assembly in Extension method in Infra layer with below command
26 | services.AddMediatR(Assembly.GetExecutingAssembly());
27 | services.AddTransient();
28 | }
29 |
30 | public static void AddIdentityService(this IServiceCollection services, IConfiguration configuration)
31 | {
32 | if (configuration.GetValue("UseInMemoryDatabase"))
33 | {
34 | services.AddDbContext(options =>
35 | options.UseInMemoryDatabase(nameof(IdentityContext)));
36 | }
37 | else
38 | {
39 | services.AddDbContext(options =>
40 | options.UseSqlServer(
41 | configuration.GetConnectionString("IdentityConnection"),
42 | b => b.MigrationsAssembly(typeof(IdentityContext).Assembly.FullName)));
43 | }
44 |
45 | services.AddIdentity().AddEntityFrameworkStores()
46 | .AddDefaultTokenProviders();
47 | #region Services
48 | services.AddTransient();
49 | #endregion
50 | services.Configure(configuration.GetSection("JWTSettings"));
51 | services.AddAuthentication(options =>
52 | {
53 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
54 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
55 | })
56 | .AddJwtBearer(o =>
57 | {
58 | o.RequireHttpsMetadata = false;
59 | o.SaveToken = false;
60 | o.TokenValidationParameters = new TokenValidationParameters
61 | {
62 | ValidateIssuerSigningKey = true,
63 | ValidateIssuer = true,
64 | ValidateAudience = true,
65 | ValidateLifetime = true,
66 | ClockSkew = TimeSpan.Zero,
67 | ValidIssuer = configuration["JWTSettings:Issuer"],
68 | ValidAudience = configuration["JWTSettings:Audience"],
69 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWTSettings:Key"]))
70 | };
71 | o.Events = new JwtBearerEvents()
72 | {
73 | OnAuthenticationFailed = c =>
74 | {
75 | c.NoResult();
76 | c.Response.StatusCode = 500;
77 | c.Response.ContentType = "text/plain";
78 | return c.Response.WriteAsync(c.Exception.ToString());
79 | },
80 | OnChallenge = context =>
81 | {
82 | context.HandleResponse();
83 | context.Response.StatusCode = 401;
84 | context.Response.ContentType = "application/json";
85 | var result = JsonConvert.SerializeObject(new Response("You are not Authorized"));
86 | return context.Response.WriteAsync(result);
87 | },
88 | OnForbidden = context =>
89 | {
90 | context.Response.StatusCode = 403;
91 | context.Response.ContentType = "application/json";
92 | var result = JsonConvert.SerializeObject(new Response("You are not authorized to access this resource"));
93 | return context.Response.WriteAsync(result);
94 | },
95 | };
96 | });
97 | }
98 | }
--------------------------------------------------------------------------------
/src/OA.Service/Exceptions/ApiException.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace OA.Service.Exceptions;
4 |
5 | public class ApiException : Exception
6 | {
7 | public ApiException() : base() { }
8 |
9 | public ApiException(string message) : base(message) { }
10 |
11 | public ApiException(string message, params object[] args)
12 | : base(string.Format(CultureInfo.CurrentCulture, message, args))
13 | {
14 | }
15 | }
--------------------------------------------------------------------------------
/src/OA.Service/Exceptions/BadRequestException.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace OA.Service.Exceptions;
4 |
5 | [Serializable]
6 | public class BadRequestException : Exception
7 | {
8 | public BadRequestException(string message)
9 | : base(message)
10 | {
11 | }
12 |
13 | protected BadRequestException(SerializationInfo info, StreamingContext context)
14 | : base(info, context)
15 | {
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/src/OA.Service/Exceptions/DeleteFailureException.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Service.Exceptions;
2 |
3 | public class DeleteFailureException(string name, object key, string message)
4 | : Exception($"Deletion of entity \"{name}\" ({key}) failed. {message}");
--------------------------------------------------------------------------------
/src/OA.Service/Exceptions/NotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Service.Exceptions;
2 |
3 | public class NotFoundException(string name, object key) : Exception($"Entity \"{name}\" ({key}) was not found.");
--------------------------------------------------------------------------------
/src/OA.Service/Exceptions/ValidationException.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation.Results;
2 |
3 | namespace OA.Service.Exceptions;
4 |
5 | public class ValidationException() : Exception("One or more validation failures have occurred.")
6 | {
7 | public ValidationException(List failures)
8 | : this()
9 | {
10 | var propertyNames = failures
11 | .Select(e => e.PropertyName)
12 | .Distinct();
13 |
14 | foreach (var propertyName in propertyNames)
15 | {
16 | var propertyFailures = failures
17 | .Where(e => e.PropertyName == propertyName)
18 | .Select(e => e.ErrorMessage)
19 | .ToArray();
20 |
21 | Failures.Add(propertyName, propertyFailures);
22 | }
23 | }
24 |
25 | public IDictionary Failures { get; } = new Dictionary();
26 | }
--------------------------------------------------------------------------------
/src/OA.Service/Features/CustomerFeatures/Commands/CreateCustomerCommand.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using OA.Domain.Entities;
3 | using OA.Persistence;
4 |
5 | namespace OA.Service.Features.CustomerFeatures.Commands;
6 |
7 | public class CreateCustomerCommand : IRequest
8 | {
9 | public string CustomerName { get; set; }
10 | public string ContactName { get; set; }
11 | public string ContactTitle { get; set; }
12 | public string Address { get; set; }
13 | public string City { get; set; }
14 | public string Region { get; set; }
15 | public string PostalCode { get; set; }
16 | public string Country { get; set; }
17 | public string Phone { get; set; }
18 | public string Fax { get; set; }
19 |
20 | }
21 |
22 | public class CreateCustomerCommandHandler(IApplicationDbContext context)
23 | : IRequestHandler
24 | {
25 | public async Task Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
26 | {
27 | var customer = new Customer
28 | {
29 | CustomerName = request.CustomerName,
30 | ContactName = request.ContactName,
31 | Address = request.Address,
32 | City = request.City,
33 | Region = request.Region,
34 | PostalCode = request.PostalCode,
35 | Country = request.Country,
36 | Phone = request.Phone,
37 | Fax = request.Fax,
38 | ContactTitle = request.ContactTitle
39 | };
40 |
41 | context.Customers.Add(customer);
42 | await context.SaveChangesAsync();
43 | return customer.Id;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/OA.Service/Features/CustomerFeatures/Commands/DeleteCustomerByIdCommand.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.EntityFrameworkCore;
3 | using OA.Persistence;
4 |
5 | namespace OA.Service.Features.CustomerFeatures.Commands;
6 |
7 | public class DeleteCustomerByIdCommand : IRequest
8 | {
9 | public int Id { get; set; }
10 | }
11 |
12 | public class DeleteCustomerByIdCommandHandler(IApplicationDbContext context)
13 | : IRequestHandler
14 | {
15 | public async Task Handle(DeleteCustomerByIdCommand request, CancellationToken cancellationToken)
16 | {
17 | var customer = await context.Customers.FirstOrDefaultAsync(a => a.Id == request.Id, cancellationToken: cancellationToken);
18 | if (customer == null) return default;
19 | context.Customers.Remove(customer);
20 | await context.SaveChangesAsync();
21 | return customer.Id;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/OA.Service/Features/CustomerFeatures/Commands/UpdateCustomerCommand.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.EntityFrameworkCore;
3 | using OA.Persistence;
4 |
5 | namespace OA.Service.Features.CustomerFeatures.Commands;
6 |
7 | public class UpdateCustomerCommand : IRequest
8 | {
9 | public int Id { get; set; }
10 | public string CustomerName { get; set; }
11 | public string ContactName { get; set; }
12 | public string ContactTitle { get; set; }
13 | public string Address { get; set; }
14 | public string City { get; set; }
15 | public string Region { get; set; }
16 | public string PostalCode { get; set; }
17 | public string Country { get; set; }
18 | public string Phone { get; set; }
19 | public string Fax { get; set; }
20 |
21 | }
22 |
23 | public class UpdateCustomerCommandHandler(IApplicationDbContext context)
24 | : IRequestHandler
25 | {
26 | public async Task Handle(UpdateCustomerCommand request, CancellationToken cancellationToken)
27 | {
28 | var customer = await context.Customers.FirstOrDefaultAsync(a => a.Id == request.Id, cancellationToken: cancellationToken);
29 |
30 | if (customer == null)
31 | return default;
32 |
33 | customer.CustomerName = request.CustomerName;
34 | customer.ContactName = request.ContactName;
35 | customer.ContactTitle = request.ContactTitle;
36 | customer.Address = request.Address;
37 | customer.City = request.City;
38 | customer.Region = request.Region;
39 | customer.PostalCode = request.PostalCode;
40 | customer.Country = request.Country;
41 | customer.Fax = request.Fax;
42 | customer.Phone = request.Phone;
43 |
44 | context.Customers.Update(customer);
45 | await context.SaveChangesAsync();
46 |
47 | return customer.Id;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/OA.Service/Features/CustomerFeatures/Queries/GetAllCustomerQuery.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.EntityFrameworkCore;
3 | using OA.Domain.Entities;
4 | using OA.Persistence;
5 |
6 | namespace OA.Service.Features.CustomerFeatures.Queries;
7 |
8 | public class GetAllCustomerQuery : IRequest>
9 | {
10 | }
11 |
12 | public class GetAllCustomerQueryHandler(IApplicationDbContext context)
13 | : IRequestHandler>
14 | {
15 | public async Task> Handle(GetAllCustomerQuery request, CancellationToken cancellationToken)
16 | {
17 | var customerList = await context.Customers.ToListAsync(cancellationToken: cancellationToken);
18 | return customerList.AsReadOnly();
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/src/OA.Service/Features/CustomerFeatures/Queries/GetCustomerByIdQuery.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.EntityFrameworkCore;
3 | using OA.Domain.Entities;
4 | using OA.Persistence;
5 |
6 | namespace OA.Service.Features.CustomerFeatures.Queries;
7 |
8 | public class GetCustomerByIdQuery : IRequest
9 | {
10 | public int Id { get; set; }
11 | }
12 |
13 | public class GetCustomerByIdQueryHandler(IApplicationDbContext context)
14 | : IRequestHandler
15 | {
16 | public async Task Handle(GetCustomerByIdQuery request, CancellationToken cancellationToken)
17 | {
18 | return await context.Customers.FirstOrDefaultAsync(a => a.Id == request.Id, cancellationToken: cancellationToken);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/OA.Service/Implementation/AccountService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using Microsoft.AspNetCore.WebUtilities;
3 | using Microsoft.Extensions.Options;
4 | using Microsoft.FeatureManagement;
5 | using Microsoft.IdentityModel.Tokens;
6 | using OA.Domain.Auth;
7 | using OA.Domain.Common;
8 | using OA.Domain.Enum;
9 | using OA.Domain.Settings;
10 | using OA.Service.Contract;
11 | using OA.Service.Exceptions;
12 | using System.IdentityModel.Tokens.Jwt;
13 | using System.Security.Claims;
14 | using System.Security.Cryptography;
15 | using System.Text;
16 |
17 | namespace OA.Service.Implementation;
18 |
19 | public class AccountService(
20 | UserManager userManager,
21 | IOptions jwtSettings,
22 | SignInManager signInManager,
23 | IEmailService emailService,
24 | IFeatureManager featureManager)
25 | : IAccountService
26 | {
27 | private readonly JWTSettings _jwtSettings = jwtSettings.Value;
28 |
29 | public async Task> AuthenticateAsync(AuthenticationRequest request, string ipAddress)
30 | {
31 | var user = await userManager.FindByEmailAsync(request.Email);
32 | if (user == null)
33 | {
34 | throw new ApiException($"No Accounts Registered with {request.Email}.");
35 | }
36 | var result = await signInManager.PasswordSignInAsync(user.UserName, request.Password, false, lockoutOnFailure: false);
37 | if (!result.Succeeded)
38 | {
39 | throw new ApiException($"Invalid Credentials for '{request.Email}'.");
40 | }
41 | if (!user.EmailConfirmed)
42 | {
43 | throw new ApiException($"Account Not Confirmed for '{request.Email}'.");
44 | }
45 | JwtSecurityToken jwtSecurityToken = await GenerateJWToken(user);
46 | AuthenticationResponse response = new AuthenticationResponse();
47 | response.Id = user.Id;
48 | response.JWToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
49 | response.Email = user.Email;
50 | response.UserName = user.UserName;
51 | var rolesList = await userManager.GetRolesAsync(user).ConfigureAwait(false);
52 | response.Roles = rolesList.ToList();
53 | response.IsVerified = user.EmailConfirmed;
54 | var refreshToken = GenerateRefreshToken(ipAddress);
55 | response.RefreshToken = refreshToken.Token;
56 | return new Response(response, $"Authenticated {user.UserName}");
57 | }
58 |
59 | public async Task> RegisterAsync(RegisterRequest request, string origin)
60 | {
61 | var userWithSameUserName = await userManager.FindByNameAsync(request.UserName);
62 | if (userWithSameUserName != null)
63 | {
64 | throw new ApiException($"Username '{request.UserName}' is already taken.");
65 | }
66 | var user = new ApplicationUser
67 | {
68 | Email = request.Email,
69 | FirstName = request.FirstName,
70 | LastName = request.LastName,
71 | UserName = request.UserName
72 | };
73 | var userWithSameEmail = await userManager.FindByEmailAsync(request.Email);
74 | if (userWithSameEmail == null)
75 | {
76 | var result = await userManager.CreateAsync(user, request.Password);
77 | if (result.Succeeded)
78 | {
79 | await userManager.AddToRoleAsync(user, Roles.Basic.ToString());
80 | var verificationUri = await SendVerificationEmail(user, origin);
81 |
82 | if (await featureManager.IsEnabledAsync(nameof(FeatureManagement.EnableEmailService)))
83 | {
84 | await emailService.SendEmailAsync(new MailRequest() { From = "amit.naik8103@gmail.com", ToEmail = user.Email, Body = $"Please confirm your account by visiting this URL {verificationUri}", Subject = "Confirm Registration" });
85 | }
86 | return new Response(user.Id, message: $"User Registered. Please confirm your account by visiting this URL {verificationUri}");
87 | }
88 | else
89 | {
90 | throw new ApiException($"{result.Errors.ToList()[0].Description}");
91 | }
92 | }
93 | else
94 | {
95 | throw new ApiException($"Email {request.Email} is already registered.");
96 | }
97 | }
98 |
99 | private async Task GenerateJWToken(ApplicationUser user)
100 | {
101 | var userClaims = await userManager.GetClaimsAsync(user);
102 | var roles = await userManager.GetRolesAsync(user);
103 |
104 | var roleClaims = roles.Select(t => new Claim("roles", t)).ToList();
105 |
106 | string ipAddress = IpHelper.GetIpAddress();
107 |
108 | var claims = new[]
109 | {
110 | new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
111 | new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
112 | new Claim(JwtRegisteredClaimNames.Email, user.Email),
113 | new Claim("uid", user.Id),
114 | new Claim("ip", ipAddress)
115 | }
116 | .Union(userClaims)
117 | .Union(roleClaims);
118 |
119 | var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key));
120 | var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
121 |
122 | var jwtSecurityToken = new JwtSecurityToken(
123 | issuer: _jwtSettings.Issuer,
124 | audience: _jwtSettings.Audience,
125 | claims: claims,
126 | expires: DateTime.UtcNow.AddMinutes(_jwtSettings.DurationInMinutes),
127 | signingCredentials: signingCredentials);
128 | return jwtSecurityToken;
129 | }
130 |
131 | private string RandomTokenString()
132 | {
133 | using var rngCryptoServiceProvider = new RNGCryptoServiceProvider();
134 | var randomBytes = new byte[40];
135 | rngCryptoServiceProvider.GetBytes(randomBytes);
136 | // convert random bytes to hex string
137 | return BitConverter.ToString(randomBytes).Replace("-", "");
138 | }
139 |
140 | private async Task SendVerificationEmail(ApplicationUser user, string origin)
141 | {
142 | var code = await userManager.GenerateEmailConfirmationTokenAsync(user);
143 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
144 | var route = "api/account/confirm-email/";
145 | var endpointUri = new Uri(string.Concat($"{origin}/", route));
146 | var verificationUri = QueryHelpers.AddQueryString(endpointUri.ToString(), "userId", user.Id);
147 | verificationUri = QueryHelpers.AddQueryString(verificationUri, "code", code);
148 | //Email Service Call Here
149 | return verificationUri;
150 | }
151 |
152 | public async Task> ConfirmEmailAsync(string userId, string code)
153 | {
154 | var user = await userManager.FindByIdAsync(userId);
155 | code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
156 | var result = await userManager.ConfirmEmailAsync(user, code);
157 | if (result.Succeeded)
158 | {
159 | return new Response(user.Id, message: $"Account Confirmed for {user.Email}. You can now use the /api/Account/authenticate endpoint.");
160 | }
161 | else
162 | {
163 | throw new ApiException($"An error occured while confirming {user.Email}.");
164 | }
165 | }
166 |
167 | private RefreshToken GenerateRefreshToken(string ipAddress)
168 | {
169 | return new RefreshToken
170 | {
171 | Token = RandomTokenString(),
172 | Expires = DateTime.UtcNow.AddDays(7),
173 | Created = DateTime.UtcNow,
174 | CreatedByIp = ipAddress
175 | };
176 | }
177 |
178 | public async Task ForgotPassword(ForgotPasswordRequest model, string origin)
179 | {
180 | var account = await userManager.FindByEmailAsync(model.Email);
181 |
182 | // always return ok response to prevent email enumeration
183 | if (account == null) return;
184 |
185 | var code = await userManager.GeneratePasswordResetTokenAsync(account);
186 | var emailRequest = new MailRequest()
187 | {
188 | Body = $"You reset token is - {code}",
189 | ToEmail = model.Email,
190 | Subject = "Reset Password",
191 | };
192 | await emailService.SendEmailAsync(emailRequest);
193 | }
194 |
195 | public async Task> ResetPassword(ResetPasswordRequest model)
196 | {
197 | var account = await userManager.FindByEmailAsync(model.Email);
198 | if (account == null) throw new ApiException($"No Accounts Registered with {model.Email}.");
199 | var result = await userManager.ResetPasswordAsync(account, model.Token, model.Password);
200 | if (result.Succeeded)
201 | {
202 | return new Response(model.Email, message: $"Password Resetted.");
203 | }
204 | else
205 | {
206 | throw new ApiException($"Error occured while reseting the password.");
207 | }
208 | }
209 | }
--------------------------------------------------------------------------------
/src/OA.Service/Implementation/DateTimeService.cs:
--------------------------------------------------------------------------------
1 | using OA.Service.Contract;
2 |
3 | namespace OA.Service.Implementation;
4 |
5 | public class DateTimeService : IDateTimeService
6 | {
7 | public DateTime NowUtc => DateTime.UtcNow;
8 | }
--------------------------------------------------------------------------------
/src/OA.Service/Implementation/MailService.cs:
--------------------------------------------------------------------------------
1 | using MailKit.Net.Smtp;
2 | using MailKit.Security;
3 | using Microsoft.Extensions.Logging;
4 | using Microsoft.Extensions.Options;
5 | using MimeKit;
6 | using OA.Domain.Settings;
7 | using OA.Service.Contract;
8 | using OA.Service.Exceptions;
9 |
10 | namespace OA.Service.Implementation;
11 |
12 | public class MailService(IOptions mailSettings, ILogger logger)
13 | : IEmailService
14 | {
15 | public async Task SendEmailAsync(MailRequest mailRequest)
16 | {
17 | try
18 | {
19 | // create message
20 | var email = new MimeMessage();
21 | email.Sender = MailboxAddress.Parse(mailRequest.From ?? mailSettings.Value.EmailFrom);
22 | email.To.Add(MailboxAddress.Parse(mailRequest.ToEmail));
23 | email.Subject = mailRequest.Subject;
24 | var builder = new BodyBuilder();
25 | builder.HtmlBody = mailRequest.Body;
26 | email.Body = builder.ToMessageBody();
27 | using var smtp = new SmtpClient();
28 | await smtp.ConnectAsync(mailSettings.Value.SmtpHost, mailSettings.Value.SmtpPort, SecureSocketOptions.StartTls);
29 | await smtp.AuthenticateAsync(mailSettings.Value.SmtpUser, mailSettings.Value.SmtpPass);
30 | await smtp.SendAsync(email);
31 | await smtp.DisconnectAsync(true);
32 |
33 | }
34 | catch (System.Exception ex)
35 | {
36 | logger.LogError(ex.Message, ex);
37 | throw new ApiException(ex.Message);
38 | }
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/src/OA.Service/Middleware/CustomExceptionMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using Microsoft.Extensions.Logging;
3 | using Newtonsoft.Json;
4 | using OA.Service.Exceptions;
5 | using System.Net;
6 |
7 | namespace OA.Service.Middleware;
8 |
9 | public class CustomExceptionMiddleware(RequestDelegate next, ILogger logger)
10 | {
11 | public async Task Invoke(HttpContext context)
12 | {
13 | try
14 | {
15 | await next(context);
16 | }
17 | catch (Exception exceptionObj)
18 | {
19 | await HandleExceptionAsync(context, exceptionObj, logger);
20 | }
21 | }
22 |
23 | private static Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
24 | {
25 | int code;
26 | var result = exception.Message;
27 |
28 | switch (exception)
29 | {
30 | case ValidationException validationException:
31 | code = (int)HttpStatusCode.BadRequest;
32 | result = JsonConvert.SerializeObject(validationException.Failures);
33 | break;
34 | case BadRequestException badRequestException:
35 | code = (int)HttpStatusCode.BadRequest;
36 | result = badRequestException.Message;
37 | break;
38 | case DeleteFailureException deleteFailureException:
39 | code = (int)HttpStatusCode.BadRequest;
40 | result = deleteFailureException.Message;
41 | break;
42 | case NotFoundException _:
43 | code = (int)HttpStatusCode.NotFound;
44 | break;
45 | default:
46 | code = (int)HttpStatusCode.InternalServerError;
47 | break;
48 | }
49 |
50 | logger.LogError(result);
51 |
52 | context.Response.ContentType = "application/json";
53 | context.Response.StatusCode = code;
54 | return context.Response.WriteAsync(JsonConvert.SerializeObject(new { StatusCode = code, ErrorMessage = exception.Message }));
55 | }
56 | }
--------------------------------------------------------------------------------
/src/OA.Service/OA.Service.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/OA.Test.Integration/ApiEndpoints/ApiCustomerTest.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using OA.Service.Features.CustomerFeatures.Commands;
3 | using System.Net;
4 |
5 | namespace OA.Test.Integration.ApiEndpoints;
6 |
7 | public class ApiCustomerTest : TestClientProvider
8 | {
9 | [TestCase("api/v1/Customer", HttpStatusCode.OK)]
10 | [TestCase("api/v1/Customer/100", HttpStatusCode.NoContent)]
11 | public async Task GetAllCustomerTestAsync(string url, HttpStatusCode result)
12 | {
13 | var request = new HttpRequestMessage(HttpMethod.Get, url);
14 |
15 | var response = await Client
16 | .AddBearerTokenAuthorization()
17 | .SendAsync(request);
18 |
19 | Assert.That(response.StatusCode, Is.EqualTo(result));
20 | }
21 |
22 | [Test]
23 | public async Task CreateCustomerTestAsync()
24 | {
25 | // Create a customer model
26 | var model = new CreateCustomerCommand
27 | {
28 | CustomerName = "John Wick",
29 | ContactName = "John Wick",
30 | ContactTitle = "Manager",
31 | Address = "123 Main St",
32 | City = "New York",
33 | Region = "NY",
34 | PostalCode = "10001",
35 | Country = "USA",
36 | Phone = "123-456-7890",
37 | Fax = "987-654-3210"
38 | };
39 |
40 | // Create POST request
41 | var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/Customer")
42 | {
43 | Content = HttpClientExtensions.SerializeAndCreateContent(model)
44 | };
45 |
46 | // Send request and check response
47 | var response = await Client
48 | .AddBearerTokenAuthorization()
49 | .SendAsync(request);
50 |
51 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
52 | }
53 |
54 | [Test]
55 | public async Task UpdateCustomerTestAsync()
56 | {
57 | // First, create a new customer
58 | var createModel = new CreateCustomerCommand
59 | {
60 | CustomerName = "John Wick",
61 | ContactName = "John Wick",
62 | ContactTitle = "Manager",
63 | Address = "123 Main St",
64 | City = "New York",
65 | Region = "NY",
66 | PostalCode = "10001",
67 | Country = "USA",
68 | Phone = "123-456-7890",
69 | Fax = "987-654-3210"
70 | };
71 |
72 | var createRequest = new HttpRequestMessage(HttpMethod.Post, "api/v1/Customer")
73 | {
74 | Content = HttpClientExtensions.SerializeAndCreateContent(createModel)
75 | };
76 |
77 | var createResponse = await Client
78 | .AddBearerTokenAuthorization()
79 | .SendAsync(createRequest);
80 |
81 | Assert.That(createResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK));
82 |
83 | // Now update the new customer
84 | var updateModel = new UpdateCustomerCommand
85 | {
86 | Id = int.Parse(await createResponse.Content.ReadAsStringAsync()),
87 | CustomerName = "Updated John Wick",
88 | ContactName = "Jane Wick",
89 | ContactTitle = "Director",
90 | Address = "456 Another St",
91 | City = "Los Angeles",
92 | Region = "CA",
93 | PostalCode = "90001",
94 | Country = "USA",
95 | Phone = "987-654-3210",
96 | Fax = "123-456-7890"
97 | };
98 |
99 | var updateRequest = new HttpRequestMessage(HttpMethod.Put, $"api/v1/Customer/{updateModel.Id}")
100 | {
101 | Content = HttpClientExtensions.SerializeAndCreateContent(updateModel)
102 | };
103 |
104 | // Send update request and check response status
105 | var updateResponse = await Client
106 | .AddBearerTokenAuthorization()
107 | .SendAsync(updateRequest);
108 |
109 | Assert.That(updateResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK));
110 | }
111 |
112 | [Test]
113 | public async Task DeleteCustomerTestAsync()
114 | {
115 | // First, create a new customer
116 | var createModel = new CreateCustomerCommand
117 | {
118 | CustomerName = "John Wick",
119 | ContactName = "John Wick",
120 | ContactTitle = "Manager",
121 | Address = "123 Main St",
122 | City = "New York",
123 | Region = "NY",
124 | PostalCode = "10001",
125 | Country = "USA",
126 | Phone = "123-456-7890",
127 | Fax = "987-654-3210"
128 | };
129 |
130 | var createRequest = new HttpRequestMessage(HttpMethod.Post, "api/v1/Customer")
131 | {
132 | Content = HttpClientExtensions.SerializeAndCreateContent(createModel)
133 | };
134 |
135 | var createResponse = await Client
136 | .AddBearerTokenAuthorization()
137 | .SendAsync(createRequest);
138 |
139 | Assert.That(createResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK));
140 |
141 | // Get the created customer's ID
142 | int customerId = int.Parse(await createResponse.Content.ReadAsStringAsync());
143 |
144 | // Now delete the customer
145 | var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, $"api/v1/Customer/{customerId}");
146 |
147 | // Send delete request and check response status
148 | var deleteResponse = await Client
149 | .AddBearerTokenAuthorization()
150 | .SendAsync(deleteRequest);
151 |
152 | Assert.That(deleteResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK));
153 |
154 | // Optionally, you can check if the customer has been actually deleted by trying to retrieve it
155 | var getRequest = new HttpRequestMessage(HttpMethod.Get, $"api/v1/Customer/{customerId}");
156 | var getResponse = await Client.AddBearerTokenAuthorization().SendAsync(getRequest);
157 | Assert.That(getResponse.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/src/OA.Test.Integration/HttpClientAuthExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace OA.Test.Integration;
2 |
3 | internal static class HttpClientAuthExtensions
4 | {
5 | const string AuthorizationKey = "Authorization";
6 |
7 | // JWT generated for 'superadmin@gmail.com' with max expiry date
8 | const string Jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdXBlcmFkbWluIiwianRpIjoiNDIzNzhlNjktMmI0YS00ZTVhLWEyZjUtM2RjMTI4YTFhNGFiIiwiZW1haWwiOiJzdXBlcmFkbWluQGdtYWlsLmNvbSIsInVpZCI6Ijk3Y2Y0ZDkwLWYyY2EtNGEwZi04MjdhLWU2ZmVkZTE2ODQyYSIsImlwIjoiMTkyLjE2OC40NS4xMzUiLCJyb2xlcyI6WyJTdXBlckFkbWluIiwiQWRtaW4iLCJCYXNpYyIsIk1vZGVyYXRvciJdLCJleHAiOjI1MzQwMjI4ODIwMCwiaXNzIjoiSWRlbnRpdHkiLCJhdWQiOiJJZGVudGl0eVVzZXIifQ.sYDCw6R-HtNfC8xJYENmq39iYJtXiVrAh5dboTrGlX8";
9 |
10 | public static HttpClient AddBearerTokenAuthorization(this HttpClient client)
11 | {
12 | // Check if the Authorization header is already present
13 | if (client.DefaultRequestHeaders.Any(p => p.Key == AuthorizationKey))
14 | return client;
15 |
16 | client.DefaultRequestHeaders.Add(AuthorizationKey, $"Bearer {Jwt}");
17 |
18 | return client;
19 |
20 | }
21 | }
--------------------------------------------------------------------------------
/src/OA.Test.Integration/HttpClientExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text;
3 |
4 | namespace OA.Test.Integration;
5 |
6 | public static class HttpClientExtensions
7 | {
8 | static readonly JsonSerializerOptions DefaultJsonOptions = new JsonSerializerOptions
9 | {
10 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase
11 | };
12 |
13 | public static StringContent SerializeAndCreateContent(T model)
14 | {
15 | string jsonContent = JsonSerializer.Serialize(model, DefaultJsonOptions);
16 | var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
17 | return content;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/OA.Test.Integration/OA.Test.Integration.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/OA.Test.Integration/TestClientProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.AspNetCore.Mvc.Testing;
3 |
4 | namespace OA.Test.Integration;
5 |
6 | public class TestClientProvider : IDisposable
7 | {
8 | private readonly WebApplicationFactory _factory;
9 | public HttpClient Client { get; }
10 |
11 | public TestClientProvider()
12 | {
13 | // Initialize WebApplicationFactory with the Program class
14 | _factory = new WebApplicationFactory()
15 | .WithWebHostBuilder(builder =>
16 | {
17 | // Set the environment to Test
18 | builder.UseEnvironment("Test");
19 | });
20 |
21 | Client = _factory.CreateClient();
22 | }
23 |
24 | public void Dispose()
25 | {
26 | Client.Dispose();
27 | _factory.Dispose();
28 | }
29 | }
--------------------------------------------------------------------------------
/src/OA.Test.Unit/OA.Test.Unit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/OA.Test.Unit/Persistence/ApplicationDbContextTest.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using NUnit.Framework;
3 | using OA.Domain.Entities;
4 | using OA.Persistence;
5 |
6 | namespace OA.Test.Unit.Persistence;
7 |
8 | public class ApplicationDbContextTest
9 | {
10 | [Test]
11 | public void CanInsertCustomerIntoDatabase()
12 | {
13 |
14 | using var context = new ApplicationDbContext();
15 | var customer = new Customer();
16 | context.Customers.Add(customer);
17 | Assert.That(context.Entry(customer).State, Is.EqualTo(EntityState.Added));
18 | }
19 | }
--------------------------------------------------------------------------------
/src/OA.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OA", "OA\OA.csproj", "{5CE8523B-CD37-4F3E-9F41-9A3D2DAB3D39}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OA.Domain", "OA.Domain\OA.Domain.csproj", "{74D8BF98-D40C-447E-BB40-29B1BAA363AB}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OA.Infrastructure", "OA.Infrastructure\OA.Infrastructure.csproj", "{97A14F11-44A9-443C-ADC4-CF5696BC64F7}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OA.Persistence", "OA.Persistence\OA.Persistence.csproj", "{5F6B0320-95CE-4D4C-82D6-7A0972243716}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OA.Service", "OA.Service\OA.Service.csproj", "{B20723E2-C6FC-41B2-8807-FC1E52B012F0}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OA.Test.Unit", "OA.Test.Unit\OA.Test.Unit.csproj", "{FDDC1E0E-296B-448C-90C2-9364B118F5E2}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OA.Test.Integration", "OA.Test.Integration\OA.Test.Integration.csproj", "{4150259A-1CC5-4BB2-A0D4-891F56338028}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OATemplate", "OATemplate\OATemplate.csproj", "{A5926428-D707-4D5E-B785-82DFE8C5AC85}"
21 | EndProject
22 | Global
23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
24 | Debug|Any CPU = Debug|Any CPU
25 | Release|Any CPU = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 | {5CE8523B-CD37-4F3E-9F41-9A3D2DAB3D39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {5CE8523B-CD37-4F3E-9F41-9A3D2DAB3D39}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {5CE8523B-CD37-4F3E-9F41-9A3D2DAB3D39}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {5CE8523B-CD37-4F3E-9F41-9A3D2DAB3D39}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {74D8BF98-D40C-447E-BB40-29B1BAA363AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {74D8BF98-D40C-447E-BB40-29B1BAA363AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {74D8BF98-D40C-447E-BB40-29B1BAA363AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {74D8BF98-D40C-447E-BB40-29B1BAA363AB}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {97A14F11-44A9-443C-ADC4-CF5696BC64F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {97A14F11-44A9-443C-ADC4-CF5696BC64F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {97A14F11-44A9-443C-ADC4-CF5696BC64F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {97A14F11-44A9-443C-ADC4-CF5696BC64F7}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {5F6B0320-95CE-4D4C-82D6-7A0972243716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {5F6B0320-95CE-4D4C-82D6-7A0972243716}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {5F6B0320-95CE-4D4C-82D6-7A0972243716}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {5F6B0320-95CE-4D4C-82D6-7A0972243716}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {B20723E2-C6FC-41B2-8807-FC1E52B012F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {B20723E2-C6FC-41B2-8807-FC1E52B012F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {B20723E2-C6FC-41B2-8807-FC1E52B012F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {B20723E2-C6FC-41B2-8807-FC1E52B012F0}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {FDDC1E0E-296B-448C-90C2-9364B118F5E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {FDDC1E0E-296B-448C-90C2-9364B118F5E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {FDDC1E0E-296B-448C-90C2-9364B118F5E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {FDDC1E0E-296B-448C-90C2-9364B118F5E2}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {4150259A-1CC5-4BB2-A0D4-891F56338028}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {4150259A-1CC5-4BB2-A0D4-891F56338028}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {4150259A-1CC5-4BB2-A0D4-891F56338028}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {4150259A-1CC5-4BB2-A0D4-891F56338028}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {A5926428-D707-4D5E-B785-82DFE8C5AC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {A5926428-D707-4D5E-B785-82DFE8C5AC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {A5926428-D707-4D5E-B785-82DFE8C5AC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {A5926428-D707-4D5E-B785-82DFE8C5AC85}.Release|Any CPU.Build.0 = Release|Any CPU
60 | EndGlobalSection
61 | GlobalSection(SolutionProperties) = preSolution
62 | HideSolutionNode = FALSE
63 | EndGlobalSection
64 | GlobalSection(ExtensibilityGlobals) = postSolution
65 | SolutionGuid = {5E5A61BE-464E-48CF-88FA-7982CFBAED9E}
66 | EndGlobalSection
67 | EndGlobal
68 |
--------------------------------------------------------------------------------
/src/OA/Controllers/AccountController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using OA.Domain.Auth;
3 | using OA.Service.Contract;
4 |
5 | namespace OA.Controllers;
6 |
7 | [Route("api/[controller]")]
8 | [ApiController]
9 | public class AccountController(IAccountService accountService) : ControllerBase
10 | {
11 | [HttpPost("authenticate")]
12 | public async Task AuthenticateAsync(AuthenticationRequest request)
13 | {
14 | return Ok(await accountService.AuthenticateAsync(request, GenerateIPAddress()));
15 | }
16 | [HttpPost("register")]
17 | public async Task RegisterAsync(RegisterRequest request)
18 | {
19 | var origin = Request.Headers["origin"];
20 | return Ok(await accountService.RegisterAsync(request, origin));
21 | }
22 | [HttpGet("confirm-email")]
23 | public async Task ConfirmEmailAsync([FromQuery] string userId, [FromQuery] string code)
24 | {
25 | var origin = Request.Headers["origin"];
26 | return Ok(await accountService.ConfirmEmailAsync(userId, code));
27 | }
28 | [HttpPost("forgot-password")]
29 | public async Task ForgotPassword(ForgotPasswordRequest model)
30 | {
31 | await accountService.ForgotPassword(model, Request.Headers["origin"]);
32 | return Ok();
33 | }
34 | [HttpPost("reset-password")]
35 | public async Task ResetPassword(ResetPasswordRequest model)
36 | {
37 |
38 | return Ok(await accountService.ResetPassword(model));
39 | }
40 | private string GenerateIPAddress()
41 | {
42 | if (Request.Headers.ContainsKey("X-Forwarded-For"))
43 | return Request.Headers["X-Forwarded-For"];
44 | else
45 | return HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
46 | }
47 | }
--------------------------------------------------------------------------------
/src/OA/Controllers/CustomerController.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.AspNetCore.Authorization;
3 | using Microsoft.AspNetCore.Mvc;
4 | using OA.Service.Features.CustomerFeatures.Commands;
5 | using OA.Service.Features.CustomerFeatures.Queries;
6 |
7 | namespace OA.Controllers;
8 |
9 | [Authorize]
10 | [ApiController]
11 | [Route("api/v{version:apiVersion}/Customer")]
12 | [ApiVersion("1.0")]
13 | public class CustomerController : ControllerBase
14 | {
15 | private IMediator _mediator;
16 | protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService();
17 |
18 | [HttpPost]
19 | public async Task Create(CreateCustomerCommand command)
20 | {
21 | return Ok(await Mediator.Send(command));
22 | }
23 |
24 | [HttpGet]
25 | [Route("")]
26 | public async Task GetAll()
27 | {
28 | return Ok(await Mediator.Send(new GetAllCustomerQuery()));
29 | }
30 |
31 | [HttpGet("{id}")]
32 | public async Task GetById(int id)
33 | {
34 | return Ok(await Mediator.Send(new GetCustomerByIdQuery { Id = id }));
35 | }
36 |
37 | [HttpDelete("{id}")]
38 | public async Task Delete(int id)
39 | {
40 | return Ok(await Mediator.Send(new DeleteCustomerByIdCommand { Id = id }));
41 | }
42 |
43 |
44 | [HttpPut("{id}")]
45 | public async Task Update(int id, UpdateCustomerCommand command)
46 | {
47 | if (id != command.Id)
48 | {
49 | return BadRequest();
50 | }
51 | return Ok(await Mediator.Send(command));
52 | }
53 | }
--------------------------------------------------------------------------------
/src/OA/Controllers/MailController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using OA.Domain.Settings;
3 | using OA.Service.Contract;
4 |
5 | namespace OA.Controllers;
6 |
7 | [ApiController]
8 | [Route("api/v{version:apiVersion}/Mail")]
9 | [ApiVersion("1.0")]
10 | public class MailController(IEmailService mailService) : ControllerBase
11 | {
12 | [HttpPost("send")]
13 | public async Task SendMail([FromForm] MailRequest request)
14 | {
15 | await mailService.SendEmailAsync(request);
16 | return Ok();
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/src/OA/Controllers/MetaController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using System.Diagnostics;
3 |
4 | namespace OA.Controllers;
5 |
6 | public class MetaController : ControllerBase
7 | {
8 | [HttpGet("/info")]
9 | public ActionResult Info()
10 | {
11 | var assembly = typeof(Program).Assembly;
12 |
13 | var lastUpdate = System.IO.File.GetLastWriteTime(assembly.Location);
14 | var version = FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion;
15 |
16 | return Ok($"Version: {version}, Last Updated: {lastUpdate}");
17 | }
18 | }
--------------------------------------------------------------------------------
/src/OA/Customization/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primaryColor: #262f3d;
3 | --secondaryColor: #c40606;
4 | --bgMenuActive: #262f3d;
5 | --bgButton: #c40606;
6 | --logoImageUrl: url('https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/master/docs/img/OnionArchitecture_icon.png');
7 | --bgAside: var(--primaryColor);
8 | }
9 |
--------------------------------------------------------------------------------
/src/OA/OA.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/OA/Program.cs:
--------------------------------------------------------------------------------
1 | using HealthChecks.UI.Client;
2 | using Microsoft.AspNetCore.Diagnostics.HealthChecks;
3 | using Microsoft.Extensions.Diagnostics.HealthChecks;
4 | using Microsoft.FeatureManagement;
5 | using OA.Domain.Settings;
6 | using OA.Infrastructure.Extension;
7 | using OA.Service;
8 | using Serilog;
9 |
10 | var builder = WebApplication.CreateBuilder(args);
11 |
12 |
13 | // Bind AppSettings
14 | var appSettings = new AppSettings();
15 | builder.Configuration.Bind(appSettings);
16 |
17 | // Add services to the container.
18 | builder.Services.AddControllers();
19 | builder.Services.AddDbContext(builder.Configuration);
20 | builder.Services.AddIdentityService(builder.Configuration);
21 | builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
22 | builder.Services.AddScopedServices();
23 | builder.Services.AddTransientServices();
24 | builder.Services.AddSwaggerOpenAPI();
25 | builder.Services.AddMailSetting(builder.Configuration);
26 | builder.Services.AddServiceLayer();
27 | builder.Services.AddVersion();
28 | builder.Services.AddHealthCheck(appSettings, builder.Configuration);
29 | builder.Services.AddFeatureManagement();
30 |
31 | //Setup Serilog
32 | builder.Host.UseSerilog((context, services, configuration) =>
33 | {
34 | configuration.ReadFrom.Configuration(context.Configuration);
35 | });
36 |
37 | var app = builder.Build();
38 |
39 | // Configure the HTTP request pipeline.
40 | if (app.Environment.IsDevelopment())
41 | {
42 | app.UseDeveloperExceptionPage();
43 | }
44 |
45 | // Setup CORS
46 | app.UseCors(options =>
47 | options.WithOrigins("http://localhost:3000")
48 | .AllowAnyHeader()
49 | .AllowAnyMethod());
50 |
51 | // Configure custom exception middleware
52 | app.ConfigureCustomExceptionMiddleware();
53 |
54 | // Setup Serilog logging
55 | app.Logger.LogInformation("Starting the application with Serilog logging.");
56 |
57 | // Health check configuration
58 | app.UseHealthChecks("/healthz", new HealthCheckOptions
59 | {
60 | Predicate = _ => true,
61 | ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
62 | ResultStatusCodes =
63 | {
64 | [HealthStatus.Healthy] = StatusCodes.Status200OK,
65 | [HealthStatus.Degraded] = StatusCodes.Status500InternalServerError,
66 | [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable,
67 | },
68 | });
69 |
70 | app.UseHealthChecksUI(setup =>
71 | {
72 | setup.ApiPath = "/healthcheck";
73 | setup.UIPath = "/healthcheck-ui";
74 | // setup.AddCustomStylesheet("Customization/custom.css");
75 | });
76 |
77 | // Setup routing
78 | app.UseRouting();
79 |
80 | // Enable authentication and authorization
81 | app.UseAuthentication();
82 | app.UseAuthorization();
83 |
84 | // Enable Swagger UI
85 | app.ConfigureSwagger();
86 |
87 | // Map controllers
88 | app.MapControllers();
89 |
90 | // Run the application
91 | app.Run();
92 |
93 |
94 | public partial class Program
95 | {
96 | }
--------------------------------------------------------------------------------
/src/OA/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:53623",
8 | "sslPort": 44356
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "OpenAPI/index.html",
17 | "applicationUrl": "http://localhost:5000",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "https": {
23 | "commandName": "Project",
24 | "dotnetRunMessages": true,
25 | "launchBrowser": true,
26 | "launchUrl": "OpenAPI/index.html",
27 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
28 | "environmentVariables": {
29 | "ASPNETCORE_ENVIRONMENT": "Development"
30 | }
31 | },
32 | "IIS Express": {
33 | "commandName": "IISExpress",
34 | "launchBrowser": true,
35 | "launchUrl": "OpenAPI/index.html",
36 | "environmentVariables": {
37 | "ASPNETCORE_ENVIRONMENT": "Development"
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/OA/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/OA/appsettings.Test.json:
--------------------------------------------------------------------------------
1 | {
2 | "UseInMemoryDatabase": true,
3 | "Serilog": {
4 | "MinimumLevel": "Information",
5 | "WriteTo": [ "Console", "DiagnosticTrace" ],
6 | "Properties": {
7 | "Application": "Onion Architecture application"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/OA/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ApplicationDetail": {
3 | "ApplicationName": "Onion Architecture API",
4 | "Description": "Through this WebAPI you can access details",
5 | "ContactWebsite": "https://amitpnk.github.io/",
6 | "LicenseDetail": "https://opensource.org/licenses/MIT"
7 | },
8 | "Serilog": {
9 | "MinimumLevel": "Information",
10 | "WriteTo": [
11 | {
12 | "Name": "RollingFile",
13 | "Args": {
14 | "pathFormat": "D:\\Logs\\log-{Date}.log",
15 | "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
16 | }
17 | },
18 | {
19 | "Name": "MSSqlServer",
20 | "Args": {
21 | "connectionString": "Data Source=.;Initial Catalog=Onion;Integrated Security=True;MultipleActiveResultSets=True;TrustServerCertificate=True",
22 | "sinkOptionsSection": {
23 | "tableName": "Logs",
24 | "schemaName": "EventLogging",
25 | "autoCreateSqlTable": true
26 | },
27 | "restrictedToMinimumLevel": "Warning"
28 | }
29 | }
30 | ],
31 | "Properties": {
32 | "Application": "Onion Architecture application"
33 | }
34 | },
35 | "AllowedHosts": "*",
36 | "UseInMemoryDatabase": false,
37 | "ConnectionStrings": {
38 | "OnionArchConn": "Data Source=.;Initial Catalog=Onion;Integrated Security=True;MultipleActiveResultSets=True;TrustServerCertificate=True",
39 | "IdentityConnection": "Data Source=.;Initial Catalog=OnionIde;Integrated Security=True;MultipleActiveResultSets=True;TrustServerCertificate=True"
40 | },
41 | "FeatureManagement": {
42 | "EnableEmailService": false
43 | },
44 | "MailSettings": {
45 | "Mail": "amit.naik6226@gmail.com",
46 | "DisplayName": "Amit Naik",
47 | "Password": "YourGmailPassword",
48 | "Host": "smtp.gmail.com",
49 | "Port": 587
50 | },
51 | "JWTSettings": {
52 | "Key": "1105D15CB0D48F5781C103A18D5599E4FF25C9102FA694ABDF1DA6828BF153DE",
53 | "Issuer": "Identity",
54 | "Audience": "IdentityUser",
55 | "DurationInMinutes": 60
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/OATemplate/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Amit P Naik
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/OATemplate/OATemplate.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 15.0
6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
7 | Program
8 | $(DevEnvDir)\devenv.exe
9 | /rootsuffix Exp
10 |
11 |
12 |
13 | Debug
14 | AnyCPU
15 | 2.0
16 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
17 | {A5926428-D707-4D5E-B785-82DFE8C5AC85}
18 | Library
19 | Properties
20 | OATemplate
21 | OATemplate
22 | v4.6
23 | false
24 | false
25 | false
26 | false
27 | false
28 | false
29 |
30 |
31 | true
32 | full
33 | false
34 | bin\Debug\
35 | DEBUG;TRACE
36 | prompt
37 | 4
38 |
39 |
40 | pdbonly
41 | true
42 | bin\Release\
43 | TRACE
44 | prompt
45 | 4
46 |
47 |
48 |
49 |
50 |
51 |
52 | Designer
53 |
54 |
55 | Designer
56 |
57 |
58 |
59 |
60 |
61 | Always
62 | true
63 |
64 |
65 | Always
66 | true
67 |
68 |
69 |
70 | Always
71 | true
72 |
73 |
74 |
75 | true
76 |
77 |
78 |
79 |
80 |
81 | $(MSBuildProjectDirectory)\Properties\wafflebuilder.targets
82 |
83 |
84 |
85 |
86 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
87 |
88 |
89 |
90 |
97 |
--------------------------------------------------------------------------------
/src/OATemplate/OnionArchitecture_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/src/OATemplate/OnionArchitecture_icon.png
--------------------------------------------------------------------------------
/src/OATemplate/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("OATemplateProject")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("OATemplateProject")]
12 | [assembly: AssemblyCopyright("")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // Version information for an assembly consists of the following four values:
22 | //
23 | // Major Version
24 | // Minor Version
25 | // Build Number
26 | // Revision
27 | //
28 | // You can specify all the values or you can default the Build and Revision Numbers
29 | // by using the '*' as shown below:
30 | // [assembly: AssemblyVersion("1.0.*")]
31 | [assembly: AssemblyVersion("1.0.0.0")]
32 | [assembly: AssemblyFileVersion("1.0.0.0")]
33 |
--------------------------------------------------------------------------------
/src/OATemplate/Properties/project-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/src/OATemplate/Properties/project-icon.png
--------------------------------------------------------------------------------
/src/OATemplate/Properties/wafflebuilder.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 | $(MSBuildThisFileFullPath).props
17 |
18 |
19 |
21 |
22 |
23 | Debug
24 | $(MSBuildProjectDirectory)\..\
25 | bin\$(Configuration)\templates\
26 | CSharp\Web\SideWaffle
27 |
28 |
29 |
32 |
33 | $(MSBuildThisFileDirectory)
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | $(ProcessTemplatesDependsOn);
42 | BuildTemplateNuGetPackages;
43 | BuildVsTemplateFiles;
44 |
45 |
46 |
47 |
50 |
51 |
54 |
55 |
56 | $(BuildTemplateNuGetPackagesDependsOn);
57 | FindTemplatePackProjFiles;
58 | BuildTemplatePackNuGetProjFiles;
59 | AddTemplateNuGetPackagesToVsix;
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
75 |
76 |
77 | <_cTemplateProj Condition=" '%(TemplatePackNuGetProj.Identity)' != '' ">%(TemplatePackNuGetProj.Identity)
78 | <_cTemplateProj Condition=" '$(_cTemplateProj)' != '' ">$([System.IO.Path]::GetFullPath('$(_cTemplateProj)'))
79 | <_templateOutputPathFull>$([System.IO.Path]::GetFullPath('$(TemplateOutputPath)'))
80 |
81 |
82 |
83 |
84 |
85 |
86 |
90 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | <_templateOutputFullpath>$([System.IO.Path]::GetFullPath('$(TemplateOutputPath)'))
102 |
103 |
104 |
105 | <_templateNuGetPkgs Include="$(_templateOutputFullpath)**/*.nupkg"
106 | Exclude="$(TemplateNuGetPackagesToExcludeFromVsix)" />
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
119 |
120 |
121 | $(BuildVsTemplateFilesDependsOn);
122 | FindVstemplateFiles;
123 | BuildZipForVstemplateFiles;
124 |
125 |
126 | $(VsTemplateFilesExclude);
127 | $(TemplateSourceRoot)**\bin\**\*;
128 | $(TemplateSourceRoot)**\obj\**\*;
129 |
130 |
131 |
132 |
133 |
134 |
136 |
137 | <_vsTemplateExcludeFiles Include="$(VsTemplateFilesExclude)"/>
138 | <_vstemplateTemp Include="$(TemplateSourceRoot)**/*.vstemplate" Exclude="@(_vsTemplateExcludeFiles)" />
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | <_templateGetNewProjectNodeXpath Condition=" '$(_templateGetNewProjectNodeXpath)'=='' ">dft:VSTemplate/dft:TemplateContent/dft:CustomParameters/dft:CustomParameter[@Name='SideWaffleNewProjNode']/@Value
149 |
150 |
151 |
153 |
154 |
155 | <_filename>%(VsTemplateFiles.Filename)
156 | <_zipoutfile>$(TemplateOutputPath)$(_filename).zip
157 | <_ziprootdir>%(VsTemplateZipDefaultFiles.RootDir)%(VsTemplateZipDefaultFiles.Directory)
158 | <_vstemplatedir>$([System.IO.Path]::GetFullPath('%(VsTemplateFiles.RootDir)%(VsTemplateFiles.Directory)'))
159 | <_vstemplatefullpath>$([System.IO.Path]::GetFullPath('%(VsTemplateFiles.Fullpath)'))
160 |
161 |
168 |
169 |
170 |
172 |
173 |
177 |
178 | <_filename>%(VsTemplateFiles.Filename)
179 | <_zipoutfile>$([System.IO.Path]::GetFullPath('$(TemplateOutputPath)$(_filename).zip'))
180 | <_ziprootdir>$([System.IO.Path]::GetFullPath('%(VsTemplateZipDefaultFiles.RootDir)%(VsTemplateZipDefaultFiles.Directory)'))
181 | <_vstemplatedir>$([System.IO.Path]::GetFullPath('%(VsTemplateFiles.RootDir)%(VsTemplateFiles.Directory)'))
182 | <_vstemplatefullpath>$([System.IO.Path]::GetFullPath('%(VsTemplateFiles.Fullpath)'))
183 |
184 |
185 |
186 |
187 |
191 |
192 |
193 | <_templatefilestoadd Remove="@(_templatefilestoadd)"/>
194 | <_templatefilestoadd Include="$(_vstemplatedir)**/*"/>
195 |
196 |
201 |
202 |
203 | <_npdNodeXpath>dft:VSTemplate/dft:TemplateContent/dft:CustomParameters/dft:CustomParameter[@Name='SideWaffleNewProjNode']/@Value
204 | <_ls-templateFilePath>%(ls-VsNewProjTemplateFiles.FullPath)
205 | http://schemas.microsoft.com/developer/vstemplate/2005
206 |
207 |
210 |
211 |
212 |
213 |
214 | <_npdNode Condition=" '$(_npdNode)'=='' ">$(DefaultNewProjectNode)
215 | <_npdNode Condition="!HasTrailingSlash('$_npdNode')">$(_npdNode)\
216 | <_fullNpdNode>Output\Templates\$(_npdNode)
217 |
218 |
219 |
220 |
221 |
222 | $(_fullNpdNode)
223 |
224 |
225 |
226 |
227 |
230 |
231 | $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll
232 | $(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll
233 | $(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll
234 | $(MSBuildFrameworkToolsPath)\Microsoft.Build.Tasks.v4.0.dll
235 | $(windir)\Microsoft.NET\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 | ();
287 | // if the file is already in the zip remove it and add again
288 | if (zip.Entries != null && zip.Entries.Count > 0) {
289 | List entries = zip.Entries.ToList();
290 | foreach (var entry in entries) {
291 | if (entry.FullName.Equals(relpath, StringComparison.OrdinalIgnoreCase)) {
292 | // entriesToDelete.Add(entry);
293 | Log.LogMessage(MessageImportance.Low, "deleting zip entry for [{0}]", relpath);
294 | entry.Delete();
295 | }
296 | }
297 | }
298 | //if(entriesToDelete != null && entriesToDelete.Count > 0){
299 | // foreach(var entry in entriesToDelete) {
300 | // try {
301 | // entry.Delete();
302 | // }
303 | // catch(Exception ex){
304 | // Log.LogMessage(MessageImportance.Low, "Unable to delete entry from zip. {0}", ex.ToString());
305 | // }
306 | // }
307 | //}
308 | ZipFileExtensions.CreateEntryFromFile(zip, filePath, relpath, level);
309 | }
310 | }
311 | }
312 | catch(Exception ex){
313 | Log.LogError(ex.ToString());
314 | return false;
315 | }
316 |
317 | return true;
318 | ]]>
319 |
320 |
321 |
322 |
323 |
--------------------------------------------------------------------------------
/src/OATemplate/Resources/OnionArchitecture_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Amitpnk/Onion-architecture-ASP.NET-Core/7b36c72dc718ca1fffd200c487a282df361af859/src/OATemplate/Resources/OnionArchitecture_icon.png
--------------------------------------------------------------------------------
/src/OATemplate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Getting Started
9 |
10 |
11 |
12 |
13 |
14 |
Getting Started
15 |
Visual Studio Extensions
16 |
17 |
18 |
19 |
20 |
Creating a Visual Studio Extension
21 |
22 |
This project enables developers to create an extension for Visual Studio. The solution contains a VSIX project that packages the extension into a VSIX file. This file is used to install an extension for Visual Studio.
23 |
Add new features
24 |
25 |
26 |
Right-click the project node in Solution Explorer and select Add>New Item.
27 |
In the Add New Item dialog box, expand the Extensibility node under Visual C# or Visual Basic.
28 |
Choose from the available item templates: Visual Studio Package, Editor Items (Classifier, Margin, Text Adornment, Viewport Adornment), Command, Tool Window, Toolbox Control, and then click Add.
29 |
30 |
31 |
The files for the template that you selected are added to the project. You can start adding functionality to your item template, press F5 to run the project, or add additional item templates.
32 |
33 |
Run and debug
34 |
To run the project, press F5. Visual Studio will:
35 |
36 |
37 |
Build the extension from the VSIX project.
38 |
Create a VSIX package from the VSIX project.
39 |
When debugging, start an experimental instance of Visual Studio with the VSIX package installed.
40 |
41 |
42 |
In the experimental instance of Visual Studio you can test out the functionality of your extension without affecting your Visual Studio installation.