├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CHANGELOG.md
├── CleanArchitectureCodeGenerator.sln
├── LICENSE
├── README.md
├── appveyor.yml
├── art
├── 2022.mp4
├── code.png
├── dialog.png
├── dialog1.png
├── menu.png
├── menu1.png
├── nuget.png
├── task-list.png
└── template.png
├── src
├── CleanArchitectureCodeGenerator.GeneratedMSBuildEditorConfig.editorconfig
├── CleanArchitectureCodeGenerator.csproj
├── CodeGenerator.cs
├── CodeGenerator.vsct
├── CodeGeneratorPackage.cs
├── FileNameDialog.xaml
├── FileNameDialog.xaml.cs
├── Helpers
│ ├── Logger.cs
│ ├── ProjectHelpers.cs
│ ├── Utility.cs
│ ├── VSHelpers.cs
│ └── VsTheme.cs
├── Models
│ ├── IntellisenseObject.cs
│ ├── IntellisenseParser.cs
│ ├── IntellisenseProperty.cs
│ └── IntellisenseType.cs
├── NewItemTarget.cs
├── Properties
│ └── AssemblyInfo.cs
├── Resources
│ ├── icon.png
│ └── logo.png
├── Services
│ ├── GenerationService.cs
│ └── IntellisenseWriter.cs
├── Templatemap.cs
├── Templates
│ ├── .bowerrc.txt
│ ├── .cs-interface.txt
│ ├── .cs.txt
│ ├── .html.txt
│ ├── .json.txt
│ ├── .md.txt
│ ├── .razor.txt
│ ├── .vb-interface.txt
│ ├── .vb.txt
│ ├── Caching
│ │ └── .cachekey.cs.txt
│ ├── Commands
│ │ ├── AddEdit
│ │ │ ├── .cs.txt
│ │ │ └── .validator.cs.txt
│ │ ├── Create
│ │ │ ├── .cs.txt
│ │ │ └── .validator.cs.txt
│ │ ├── Delete
│ │ │ ├── .cs.txt
│ │ │ └── .validator.cs.txt
│ │ ├── Import
│ │ │ ├── .cs.txt
│ │ │ └── .validator.cs.txt
│ │ └── Update
│ │ │ ├── .cs.txt
│ │ │ └── .validator.cs.txt
│ ├── DTOs
│ │ └── .dto.cs.txt
│ ├── EventHandlers
│ │ ├── .created.cs.txt
│ │ ├── .deleted.cs.txt
│ │ └── .updated.cs.txt
│ ├── Events
│ │ ├── .createdevent.cs.txt
│ │ ├── .deletedevent.cs.txt
│ │ └── .updatedevent.cs.txt
│ ├── Mappers
│ │ └── .mapper.cs.txt
│ ├── Pages
│ │ ├── .create.razor.txt
│ │ ├── .edit.razor.txt
│ │ ├── .razor.txt
│ │ ├── .view.razor.txt
│ │ └── Components
│ │ │ ├── .advancedsearchcomponent.razor.txt
│ │ │ └── .formdialog.razor.txt
│ ├── PermissionSet
│ │ └── .cs.txt
│ ├── Persistence
│ │ └── Configurations
│ │ │ └── .configuration.cs.txt
│ ├── Queries
│ │ ├── Export
│ │ │ └── .cs.txt
│ │ ├── GetAll
│ │ │ └── .cs.txt
│ │ ├── GetById
│ │ │ └── .cs.txt
│ │ └── Pagination
│ │ │ └── .cs.txt
│ ├── Specifications
│ │ ├── AdvancedFilter.cs.txt
│ │ ├── AdvancedSpecification.cs.txt
│ │ └── ByIdSpecification.cs.txt
│ ├── bower.json.txt
│ ├── gruntfile.js.txt
│ ├── gulpfile.js.txt
│ └── package.json.txt
├── source.extension.cs
└── source.extension.vsixmanifest
└── vs-publish.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.vsct]
2 |
3 | indent_style = space
4 | indent_size = 4
5 | charset = utf-8-bom
6 |
7 | [*.cs]
8 |
9 | #Core editorconfig formatting - indentation
10 |
11 | indent_style = tab
12 | charset = utf-8-bom
13 |
14 | #Formatting - new line options
15 |
16 | csharp_new_line_before_catch = true
17 | csharp_new_line_before_else = true
18 | csharp_new_line_before_finally = true
19 | csharp_new_line_before_members_in_object_initializers = true
20 | csharp_new_line_before_open_brace = methods, control_blocks, object_collection_array_initializers, types
21 |
22 | #Formatting - organize using options
23 |
24 | dotnet_sort_system_directives_first = false
25 |
26 | #Formatting - spacing options
27 |
28 | csharp_space_after_cast = false
29 | csharp_space_after_colon_in_inheritance_clause = true
30 | csharp_space_after_keywords_in_control_flow_statements = true
31 | csharp_space_before_colon_in_inheritance_clause = true
32 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
33 | csharp_space_between_method_call_name_and_opening_parenthesis = false
34 | csharp_space_between_method_call_parameter_list_parentheses = false
35 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
36 | csharp_space_between_method_declaration_parameter_list_parentheses = false
37 |
38 | #Formatting - wrapping options
39 |
40 | csharp_preserve_single_line_blocks = true
41 |
42 | #Style - Code block preferences
43 |
44 | csharp_prefer_braces = true:suggestion
45 |
46 | #Style - expression bodied member options
47 |
48 | csharp_style_expression_bodied_methods = false:suggestion
49 | csharp_style_expression_bodied_properties = true:suggestion
50 |
51 | #Style - expression level options
52 |
53 | csharp_style_inlined_variable_declaration = true:suggestion
54 | dotnet_style_predefined_type_for_member_access = true:suggestion
55 |
56 | #Style - Expression-level preferences
57 |
58 | dotnet_style_object_initializer = true:suggestion
59 |
60 | #Style - implicit and explicit types
61 |
62 | csharp_style_var_elsewhere = false:suggestion
63 | csharp_style_var_for_built_in_types = false:suggestion
64 | csharp_style_var_when_type_is_apparent = false:suggestion
65 |
66 | #Style - language keyword and framework type options
67 |
68 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
69 |
70 | #Style - modifier options
71 |
72 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
73 |
74 | #Style - Modifier preferences
75 |
76 | csharp_preferred_modifier_order = private,public,internal,protected,static,async,readonly,override:suggestion
77 |
78 | #Style - Pattern matching
79 |
80 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
81 |
82 | #Style - qualification options
83 |
84 | dotnet_style_qualification_for_method = false:suggestion
85 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
2 | name: "Build"
3 |
4 | on:
5 | push:
6 | branches: [main]
7 | pull_request:
8 | branches: [main]
9 |
10 |
11 | jobs:
12 | build:
13 | outputs:
14 | version: ${{ steps.vsix_version.outputs.version-number }}
15 | name: Build
16 | runs-on: windows-2022
17 | env:
18 | Configuration: Release
19 | DeployExtension: False
20 | VsixManifestPath: src\source.extension.vsixmanifest
21 | VsixManifestSourcePath: src\source.extension.cs
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 |
26 | - name: Setup .NET build dependencies
27 | uses: timheuer/bootstrap-dotnet@v1
28 | with:
29 | nuget: 'false'
30 | sdk: 'false'
31 | msbuild: 'true'
32 |
33 | - name: Increment VSIX version
34 | id: vsix_version
35 | uses: timheuer/vsix-version-stamp@v1
36 | with:
37 | manifest-file: ${{ env.VsixManifestPath }}
38 | vsix-token-source-file: ${{ env.VsixManifestSourcePath }}
39 |
40 | - name: Build
41 | run: msbuild /v:m -restore /p:OutDir=../_built
42 |
43 | #- name: Setup test
44 | # uses: darenm/Setup-VSTest@v1
45 |
46 | #- name: Test
47 | # run: vstest.console.exe _built\*test.dll
48 |
49 | - name: Upload artifact
50 | uses: actions/upload-artifact@v4
51 | with:
52 | name: CleanArchitectureCodeGenerator.vsix
53 | path: _built/**/*.vsix
54 |
55 | publish:
56 | needs: build
57 | runs-on: windows-latest
58 |
59 | steps:
60 | - uses: actions/checkout@v4
61 |
62 | - name: Download Package artifact
63 | uses: actions/download-artifact@v4
64 | with:
65 | name: CleanArchitectureCodeGenerator.vsix
66 |
67 | - name: Upload to Open VSIX
68 | uses: timheuer/openvsixpublish@v1
69 | with:
70 | vsix-file: CleanArchitectureCodeGenerator.vsix
71 |
72 |
73 |
74 | - name: Publish extension to Marketplace
75 | uses: cezarypiatek/VsixPublisherAction@1.0
76 | with:
77 | extension-file: CleanArchitectureCodeGenerator.vsix
78 | publish-manifest-file: 'vs-publish.json'
79 | personal-access-code: ${{ secrets.NUGET_API_KEY }}
80 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 | .vs
9 |
10 | # Build results
11 | [Dd]ebug/
12 | [Dd]ebugPublic/
13 | [Rr]elease/
14 | x64/
15 | build/
16 | bld/
17 | [Bb]in/
18 | [Oo]bj/
19 |
20 | # MSTest test Results
21 | [Tt]est[Rr]esult*/
22 | [Bb]uild[Ll]og.*
23 |
24 | #NUNIT
25 | *.VisualState.xml
26 | TestResult.xml
27 |
28 | # Build Results of an ATL Project
29 | [Dd]ebugPS/
30 | [Rr]eleasePS/
31 | dlldata.c
32 |
33 | *_i.c
34 | *_p.c
35 | *_i.h
36 | *.ilk
37 | *.meta
38 | *.obj
39 | *.pch
40 | *.pdb
41 | *.pgc
42 | *.pgd
43 | *.rsp
44 | *.sbr
45 | *.tlb
46 | *.tli
47 | *.tlh
48 | *.tmp
49 | *.tmp_proj
50 | *.log
51 | *.vspscc
52 | *.vssscc
53 | .builds
54 | *.pidb
55 | *.svclog
56 | *.scc
57 |
58 | # Chutzpah Test files
59 | _Chutzpah*
60 |
61 | # Visual C++ cache files
62 | ipch/
63 | *.aps
64 | *.ncb
65 | *.opensdf
66 | *.sdf
67 | *.cachefile
68 |
69 | # Visual Studio profiler
70 | *.psess
71 | *.vsp
72 | *.vspx
73 |
74 | # TFS 2012 Local Workspace
75 | $tf/
76 |
77 | # Guidance Automation Toolkit
78 | *.gpState
79 |
80 | # ReSharper is a .NET coding add-in
81 | _ReSharper*/
82 | *.[Rr]e[Ss]harper
83 | *.DotSettings.user
84 |
85 | # JustCode is a .NET coding addin-in
86 | .JustCode
87 |
88 | # TeamCity is a build add-in
89 | _TeamCity*
90 |
91 | # DotCover is a Code Coverage Tool
92 | *.dotCover
93 |
94 | # NCrunch
95 | *.ncrunch*
96 | _NCrunch_*
97 | .*crunch*.local.xml
98 |
99 | # MightyMoose
100 | *.mm.*
101 | AutoTest.Net/
102 |
103 | # Web workbench (sass)
104 | .sass-cache/
105 |
106 | # Installshield output folder
107 | [Ee]xpress/
108 |
109 | # DocProject is a documentation generator add-in
110 | DocProject/buildhelp/
111 | DocProject/Help/*.HxT
112 | DocProject/Help/*.HxC
113 | DocProject/Help/*.hhc
114 | DocProject/Help/*.hhk
115 | DocProject/Help/*.hhp
116 | DocProject/Help/Html2
117 | DocProject/Help/html
118 |
119 | # Click-Once directory
120 | publish/
121 |
122 | # Publish Web Output
123 | *.[Pp]ublish.xml
124 | *.azurePubxml
125 |
126 | # NuGet Packages Directory
127 | packages/
128 | ## TODO: If the tool you use requires repositories.config uncomment the next line
129 | #!packages/repositories.config
130 |
131 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
132 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
133 | !packages/build/
134 |
135 | # Windows Azure Build Output
136 | csx/
137 | *.build.csdef
138 |
139 | # Windows Store app package directory
140 | AppPackages/
141 |
142 | # Others
143 | sql/
144 | *.Cache
145 | ClientBin/
146 | [Ss]tyle[Cc]op.*
147 | ~$*
148 | *~
149 | *.dbmdl
150 | *.dbproj.schemaview
151 | *.pfx
152 | *.publishsettings
153 | node_modules/
154 |
155 | # RIA/Silverlight projects
156 | Generated_Code/
157 |
158 | # Backup & report files from converting an old project file to a newer
159 | # Visual Studio version. Backup files are not needed, because we have git ;-)
160 | _UpgradeReport_Files/
161 | Backup*/
162 | UpgradeLog*.XML
163 | UpgradeLog*.htm
164 |
165 | # SQL Server files
166 | *.mdf
167 | *.ldf
168 |
169 | # Business Intelligence projects
170 | *.rdl.data
171 | *.bim.layout
172 | *.bim_*.settings
173 |
174 | # Microsoft Fakes
175 | FakesAssemblies/
176 |
177 | # =========================
178 | # Operating System Files
179 | # =========================
180 |
181 | # OSX
182 | # =========================
183 |
184 | .DS_Store
185 | .AppleDouble
186 | .LSOverride
187 |
188 | # Icon must ends with two \r.
189 | Icon
190 |
191 | # Thumbnails
192 | ._*
193 |
194 | # Files that might appear on external disk
195 | .Spotlight-V100
196 | .Trashes
197 |
198 | # Windows
199 | # =========================
200 |
201 | # Windows image file caches
202 | Thumbs.db
203 | ehthumbs.db
204 |
205 | # Folder config file
206 | Desktop.ini
207 |
208 | # Recycle Bin used on file shares
209 | $RECYCLE.BIN/
210 |
211 | # Windows Installer files
212 | *.cab
213 | *.msi
214 | *.msm
215 | *.msp
216 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Road map
2 |
3 | - [ ] Nothing yet...
4 | - [x] Fix for paths above project folder (#37)
5 | - [x] Fix for multi-project scenarios (#35)
6 |
7 | Features that have a checkmark are complete and available for
8 | download in the
9 | [nightly build](http://vsixgallery.com/extension/6A168388-B6A2-42F9-BA5A-B577D3CD4DB5/).
10 |
11 | # Change log
12 |
13 | These are the changes to each version that has been released
14 | on the official Visual Studio extension gallery.
15 |
16 | ## 3.5
17 |
18 | **2016-07-27**
19 |
20 | - [x] Support for solution
21 | - [x] Fixed UTF8 BOM issue
22 |
23 | ## 3.4
24 |
25 | **2016-06-10**
26 |
27 | - [x] Re-implemented the error logger
28 | - [x] Fixed end-of-line
29 | - [x] No longer add new file to SSDT projects. Requires manual include
30 |
31 | ## 3.3
32 |
33 | **2016-05-02**
34 |
35 | - [x] "VS15" support
36 |
37 | ## 3.2
38 |
39 | **2016-04-05**
40 |
41 | - [x] Fixed crash when command is not available
42 | - [x] Added culture to AssemblyInfo
43 | - [x] Using the Shell utils to open file
44 |
45 | ## 3.1
46 |
47 | **2016-03-14**
48 |
49 | - [x] Added changelog/roadmap
50 | - [x] Textbox too small with long folder name (#20)
51 | - [x] C#/VB templates to include folder names (#21)
52 |
53 | ## 3.0
54 |
55 | **2016-03-11**
56 |
57 | - [x] New templating engine
58 | - [x] Templates for C#, VB, HTML and JSON files.
59 | - [x] File name match template support for:
60 | - gulpfile.js
61 | - gruntfile.js
62 | - bower.json
63 | - package.json
64 |
--------------------------------------------------------------------------------
/CleanArchitectureCodeGenerator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31606.5
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitectureCodeGenerator", "src\CleanArchitectureCodeGenerator.csproj", "{CAD947D3-06E2-4A76-8838-68115036B179}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50C409A6-1C74-4C72-887A-3AC964BD505D}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | appveyor.yml = appveyor.yml
12 | CHANGELOG.md = CHANGELOG.md
13 | README.md = README.md
14 | vs-publish.json = vs-publish.json
15 | EndProjectSection
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | CI|Any CPU = CI|Any CPU
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {CAD947D3-06E2-4A76-8838-68115036B179}.CI|Any CPU.ActiveCfg = CI|Any CPU
25 | {CAD947D3-06E2-4A76-8838-68115036B179}.CI|Any CPU.Build.0 = CI|Any CPU
26 | {CAD947D3-06E2-4A76-8838-68115036B179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {CAD947D3-06E2-4A76-8838-68115036B179}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {CAD947D3-06E2-4A76-8838-68115036B179}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {CAD947D3-06E2-4A76-8838-68115036B179}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {D67A511C-18C8-4D62-84F3-B5B9DAF2A310}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) CleanArchitectureCodeGenerator
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Speed up your Clean Architecture development in Visual Studio!
2 |
3 | [](https://github.com/neozhu/CleanArchitectureCodeGenerator/actions/workflows/build.yml)
4 | 
5 | 
6 |
7 | [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) is a software design philosophy that separates the elements of a design into ring levels. The main rule of Clean Architecture is that code dependencies can only move from the outer levels inward. Code on the inner layers can have no knowledge of functions on the outer layers. This extension helps you generate code that adheres to this principle.
8 |
9 | ## Download the extension
10 |
11 | [VS Marketplace](https://marketplace.visualstudio.com/items?itemName=neozhu.247365)
12 |
13 | [Open VSIX Gallery](https://www.vsixgallery.com/extension/CleanArchitecture_CodeGenerator_BlazorApp)
14 |
15 | 
16 | 
17 |
18 |
19 | ### How to start
20 |
21 |
22 | -------------------------------------------------
23 |
24 | A Visual Studio extension for easily create application features code to clean architecture project. Simply hit Shift+F2 to create an empty file in the
25 | selected folder or in the same folder as the selected file.
26 |
27 | See the [changelog](CHANGELOG.md) for updates and roadmap.
28 |
29 |
30 | ### Features
31 |
32 | This extension helps you rapidly scaffold components for your Clean Architecture project:
33 |
34 | #### Core Application Layer Components
35 | Quickly generate essential C# classes for your application layer, including:
36 |
37 | * **Commands and Validators:** For operations that change the state of your application (Add/Edit, Create, Delete, Update, Import).
38 | * `{nameofPlural}/Commands/AddEdit/AddEdit{name}Command.cs`
39 | * `{nameofPlural}/Commands/AddEdit/AddEdit{name}CommandValidator.cs`
40 | * `{nameofPlural}/Commands/Create/Create{name}Command.cs`
41 | * `{nameofPlural}/Commands/Create/Create{name}CommandValidator.cs`
42 | * `{nameofPlural}/Commands/Delete/Delete{name}Command.cs`
43 | * `{nameofPlural}/Commands/Delete/Delete{name}CommandValidator.cs`
44 | * `{nameofPlural}/Commands/Update/Update{name}Command.cs`
45 | * `{nameofPlural}/Commands/Update/Update{name}CommandValidator.cs`
46 | * `{nameofPlural}/Commands/Import/Import{name}Command.cs`
47 | * `{nameofPlural}/Commands/Import/Import{name}CommandValidator.cs`
48 | * **Data Transfer Objects (DTOs):** To define how data is sent and received.
49 | * `{nameofPlural}/DTOs/{name}Dto.cs`
50 | * **Event Handlers:** For domain events (Created, Updated, Deleted).
51 | * `{nameofPlural}/EventHandlers/{name}CreatedEventHandler.cs`
52 | * `{nameofPlural}/EventHandlers/{name}UpdatedEventHandler.cs`
53 | * `{nameofPlural}/EventHandlers/{name}DeletedEventHandler.cs`
54 | * **Queries:** For retrieving data (Export, GetAll, Pagination).
55 | * `{nameofPlural}/Queries/Export/Export{nameofPlural}Query.cs`
56 | * `{nameofPlural}/Queries/GetAll/GetAll{nameofPlural}Query.cs`
57 | * `{nameofPlural}/Queries/Pagination/{nameofPlural}PaginationQuery.cs`
58 |
59 | #### TypeScript Definition Generation
60 | Automatically generate TypeScript definition files (`.d.ts`) for your Data Transfer Objects (DTOs), enabling type-safe interaction with your frontend applications.
61 | * `{nameofPlural}/DTOs/{name}Dto.d.ts`
62 |
63 | ### CleanArchitecture for Blazor Server Application project
64 | Please use this in collaboration with this project.
65 |
66 | Github :[https://github.com/neozhu/RazorPageCleanArchitecture](https://github.com/neozhu/CleanArchitectureWithBlazorServer)
67 | 
68 |
69 | ### How to use
70 |
71 | A new button is added to the context menu in Solution Explorer.
72 |
73 | 
74 |
75 | You can either click that button or use the keybord shortcut **Shift+F2**.
76 |
77 | Select Entity Name from Domain Project
78 |
79 | 
80 |
81 | ### Create folders and namespace
82 |
83 | Create additional folders for your file by using forward-slash to
84 | specify the structure.
85 |
86 | For example, by typing **scripts/test.js** in the dialog, the
87 | folder **scripts** is created if it doesn't exist and the file
88 | **test.js** is then placed into it.
89 |
90 | ### Generate sourcecode
91 | 
92 |
93 | ### Generate to-do list
94 | 
95 |
96 | ### code templates
97 | You can modify these templates according to your own projects
98 | 
99 |
100 | ## Contribute
101 | Check out the [contribution guidelines](.github/CONTRIBUTING.md)
102 | if you want to contribute to this project.
103 |
104 | For cloning and building this project yourself, make sure
105 | to install the
106 | [Extensibility Tools 2015](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityTools)
107 | extension for Visual Studio which enables some features
108 | used by this project.
109 |
110 | ## **License**
111 |
112 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
113 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2017
2 |
3 | install:
4 | - ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex
5 |
6 | before_build:
7 | - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion
8 | - ps: Vsix-TokenReplacement src\source.extension.cs 'Version = "([0-9\\.]+)"' 'Version = "{version}"'
9 |
10 | build_script:
11 | - nuget restore -Verbosity quiet
12 | - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m
13 |
14 | after_test:
15 | - ps: Vsix-PushArtifacts | Vsix-PublishToGallery
16 |
17 | before_deploy:
18 | - ps: Vsix-CreateChocolatyPackage -packageId webessentials2019
19 |
20 | deploy:
21 | - provider: Environment
22 | name: Chocolatey
23 | on:
24 | branch: master
25 | appveyor_repo_commit_message_extended: /\[release\]/
--------------------------------------------------------------------------------
/art/2022.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/2022.mp4
--------------------------------------------------------------------------------
/art/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/code.png
--------------------------------------------------------------------------------
/art/dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/dialog.png
--------------------------------------------------------------------------------
/art/dialog1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/dialog1.png
--------------------------------------------------------------------------------
/art/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/menu.png
--------------------------------------------------------------------------------
/art/menu1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/menu1.png
--------------------------------------------------------------------------------
/art/nuget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/nuget.png
--------------------------------------------------------------------------------
/art/task-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/task-list.png
--------------------------------------------------------------------------------
/art/template.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/art/template.png
--------------------------------------------------------------------------------
/src/CleanArchitectureCodeGenerator.GeneratedMSBuildEditorConfig.editorconfig:
--------------------------------------------------------------------------------
1 | is_global = true
2 | build_property.TargetFramework =
3 | build_property.TargetPlatformMinVersion =
4 | build_property.UsingMicrosoftNETSdkWeb =
5 | build_property.ProjectTypeGuids = {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
6 | build_property.PublishSingleFile =
7 | build_property.IncludeAllContentForSelfExtract =
8 | build_property._SupportedPlatformList =
9 |
--------------------------------------------------------------------------------
/src/CleanArchitectureCodeGenerator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(VisualStudioVersion)
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 | Program
7 | $(DevEnvDir)\devenv.exe
8 | /rootsuffix Exp
9 | true
10 | publish\
11 | true
12 | Disk
13 | false
14 | Foreground
15 | 7
16 | Days
17 | false
18 | false
19 | true
20 | 0
21 | 1.0.0.%2a
22 | false
23 | false
24 | true
25 |
26 |
27 |
28 |
29 |
30 | bin\CI\
31 | TRACE
32 | true
33 | pdbonly
34 | AnyCPU
35 | false
36 | prompt
37 | MinimumRecommendedRules.ruleset
38 | False
39 |
40 |
41 |
42 | Debug
43 | AnyCPU
44 | 2.0
45 | {CAD947D3-06E2-4A76-8838-68115036B179}
46 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
47 | Library
48 | Properties
49 | CleanArchitecture.CodeGenerator
50 | CleanArchitectureCodeGenerator
51 | v4.7.2
52 |
53 |
54 | true
55 | full
56 | false
57 | bin\Debug\
58 | DEBUG;TRACE
59 | prompt
60 | 4
61 |
62 |
63 | pdbonly
64 | true
65 | bin\Release\
66 | TRACE
67 | prompt
68 | 4
69 | false
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | True
88 | True
89 | CodeGenerator.vsct
90 |
91 |
92 | FileNameDialog.xaml
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | True
110 | True
111 | source.extension.vsixmanifest
112 |
113 |
114 |
115 | true
116 |
117 |
118 | true
119 |
120 |
121 | true
122 |
123 |
124 | true
125 |
126 |
127 | true
128 |
129 |
130 | true
131 |
132 |
133 | true
134 |
135 |
136 | true
137 |
138 |
139 | true
140 |
141 |
142 | true
143 |
144 |
145 | true
146 |
147 |
148 | true
149 |
150 |
151 | true
152 |
153 |
154 | true
155 |
156 |
157 | true
158 |
159 |
160 | true
161 |
162 |
163 | true
164 |
165 |
166 | true
167 |
168 |
169 | true
170 |
171 |
172 | true
173 |
174 |
175 | true
176 |
177 |
178 | true
179 |
180 |
181 | true
182 |
183 |
184 | true
185 |
186 |
187 | true
188 |
189 |
190 | true
191 |
192 |
193 | true
194 |
195 |
196 | true
197 |
198 |
199 | true
200 |
201 |
202 | true
203 |
204 |
205 | true
206 |
207 |
208 | true
209 |
210 |
211 | true
212 |
213 |
214 | true
215 |
216 |
217 | true
218 |
219 |
220 | true
221 |
222 |
223 | true
224 |
225 |
226 | true
227 |
228 |
229 | true
230 |
231 |
232 | true
233 |
234 |
235 | true
236 |
237 |
238 | true
239 |
240 |
241 | true
242 |
243 |
244 | true
245 |
246 |
247 |
248 |
249 |
250 | Resources\LICENSE
251 | true
252 |
253 |
254 | Designer
255 | VsixManifestGenerator
256 | source.extension.cs
257 |
258 |
259 | true
260 |
261 |
262 | true
263 |
264 |
265 |
266 |
267 | Menus.ctmenu
268 | Designer
269 | VsctGenerator
270 | CodeGenerator.cs
271 |
272 |
273 |
274 |
275 | true
276 |
277 |
278 |
279 |
280 | Designer
281 | MSBuild:Compile
282 |
283 |
284 |
285 |
286 | False
287 | Microsoft .NET Framework 4.5 %28x86 and x64%29
288 | true
289 |
290 |
291 | False
292 | .NET Framework 3.5 SP1
293 | false
294 |
295 |
296 |
297 |
298 | 6.1.0
299 |
300 |
301 | 17.13.40008
302 | compile; build; native; contentfiles; analyzers; buildtransitive
303 |
304 |
305 | 17.8.8
306 |
307 |
308 | 17.13.2126
309 | runtime; build; native; contentfiles; analyzers; buildtransitive
310 | all
311 |
312 |
313 | 9.0.3
314 |
315 |
316 | 9.0.3
317 |
318 |
319 |
320 |
321 | true
322 |
323 |
324 |
325 |
332 |
--------------------------------------------------------------------------------
/src/CodeGenerator.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | //
3 | // This file was generated by VSIX Synchronizer
4 | //
5 | // ------------------------------------------------------------------------------
6 | namespace CleanArchitecture.CodeGenerator
7 | {
8 | using System;
9 |
10 | ///
11 | /// Helper class that exposes all GUIDs used across VS Package.
12 | ///
13 | internal sealed partial class PackageGuids
14 | {
15 | public const string guidCodeGeneratorPkgString = "da3d4428-7855-4b22-a0e6-a7b9a6c7aa5e";
16 | public static Guid guidCodeGeneratorPkg = new Guid(guidCodeGeneratorPkgString);
17 |
18 | public const string guidCodeGeneratorCmdSetString = "4c7a4f97-2988-45ec-af7c-8984fc72c5e1";
19 | public static Guid guidCodeGeneratorCmdSet = new Guid(guidCodeGeneratorCmdSetString);
20 | }
21 | ///
22 | /// Helper class that encapsulates all CommandIDs uses across VS Package.
23 | ///
24 | internal sealed partial class PackageIds
25 | {
26 | public const int cmdidMyCommand = 0x0100;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/CodeGenerator.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/CodeGeneratorPackage.cs:
--------------------------------------------------------------------------------
1 | using CleanArchitecture.CodeGenerator.Helpers;
2 | using CleanArchitecture.CodeGenerator.Models;
3 | using EnvDTE;
4 | using EnvDTE80;
5 | using Microsoft;
6 | using Microsoft.VisualStudio;
7 | using Microsoft.VisualStudio.Shell;
8 | using Microsoft.VisualStudio.Text;
9 | using Microsoft.VisualStudio.Threading;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.ComponentModel.Design;
13 | using System.IO;
14 | using System.Linq;
15 | using System.Runtime.InteropServices;
16 | using System.Text;
17 | using System.Text.RegularExpressions;
18 | using System.Threading;
19 | using System.Threading.Tasks;
20 | using System.Windows;
21 |
22 | namespace CleanArchitecture.CodeGenerator
23 | {
24 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
25 | [InstalledProductRegistration("#110", "#112", Vsix.Version, IconResourceID = 400)]
26 | [ProvideMenuResource("Menus.ctmenu", 1)]
27 | [Guid(PackageGuids.guidCodeGeneratorPkgString)]
28 | public sealed class CodeGeneratorPackage : AsyncPackage
29 | {
30 | public const string DOMAINPROJECT = "Domain";
31 | public const string UIPROJECT = "Server.UI";
32 | public const string INFRASTRUCTUREPROJECT = "Infrastructure";
33 | public const string APPLICATIONPROJECT = "Application";
34 |
35 | private const string _solutionItemsProjectName = "Solution Items";
36 | private static readonly Regex _reservedFileNamePattern = new Regex($@"(?i)^(PRN|AUX|NUL|CON|COM\d|LPT\d)(\.|$)");
37 | private static readonly HashSet _invalidFileNameChars = new HashSet(Path.GetInvalidFileNameChars());
38 |
39 | public static DTE2 _dte;
40 |
41 | protected async override Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
42 | {
43 | await JoinableTaskFactory.SwitchToMainThreadAsync();
44 |
45 | _dte = await GetServiceAsync(typeof(DTE)) as DTE2;
46 | Assumes.Present(_dte);
47 |
48 | Logger.Initialize(this, Vsix.Name);
49 |
50 | if (await GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService mcs)
51 | {
52 | CommandID menuCommandID = new CommandID(PackageGuids.guidCodeGeneratorCmdSet, PackageIds.cmdidMyCommand);
53 | OleMenuCommand menuItem = new OleMenuCommand(ExecuteAsync, menuCommandID);
54 | mcs.AddCommand(menuItem);
55 | }
56 | }
57 |
58 | private void ExecuteAsync(object sender, EventArgs e)
59 | {
60 | NewItemTarget target = NewItemTarget.Create(_dte);
61 | NewItemTarget domain= NewItemTarget.Create(_dte,"Domain");
62 | NewItemTarget infrastructure = NewItemTarget.Create(_dte, "Infrastructure");
63 | NewItemTarget ui = NewItemTarget.Create(_dte, "Server.UI");
64 | var includes = new string[] { "IEntity", "BaseEntity", "BaseAuditableEntity", "BaseAuditableSoftDeleteEntity", "AuditTrail", "OwnerPropertyEntity","KeyValue" };
65 |
66 | var testlist = ProjectHelpers.GetEntities(domain.Project);
67 | var objectlist = ProjectHelpers.GetEntities(domain.Project)
68 | .Where(x => x.IsEnum || (includes.Contains(x.BaseName) && !includes.Contains(x.Name)));
69 | var entities = objectlist.Where(x=>x.IsEnum==false).Select(x=>x.Name).ToArray();
70 | if (target == null && target.Project.Name == APPLICATIONPROJECT)
71 | {
72 | MessageBox.Show(
73 | "Unable to determine the location for creating the new file. Please select a folder within the Application Project in the Explorer and try again.",
74 | Vsix.Name,
75 | MessageBoxButton.OK,
76 | MessageBoxImage.Error);
77 | return;
78 | }
79 |
80 | string input = PromptForFileName(target.Directory,entities).TrimStart('/', '\\').Replace("/", "\\");
81 |
82 | if (string.IsNullOrEmpty(input))
83 | {
84 | return;
85 | }
86 |
87 | string[] parsedInputs = GetParsedInput(input);
88 |
89 | foreach (string inputname in parsedInputs)
90 | {
91 | try
92 | {
93 | var name = Path.GetFileNameWithoutExtension(inputname);
94 | var nameofPlural = ProjectHelpers.Pluralize(name);
95 | var objectClass = objectlist.Where(x => x.Name == name).First();
96 | var events = new List() {
97 | $"Events/{name}CreatedEvent.cs",
98 | $"Events/{name}DeletedEvent.cs",
99 | $"Events/{name}UpdatedEvent.cs",
100 | };
101 | foreach (var item in events)
102 | {
103 | AddItemAsync(objectClass,item, name, domain, objectlist).Forget();
104 | }
105 | var configurations = new List() {
106 | $"Persistence/Configurations/{name}Configuration.cs",
107 | $"PermissionSet/{nameofPlural}.cs"
108 | };
109 | foreach (var item in configurations)
110 | {
111 | AddItemAsync(objectClass, item, name, infrastructure, objectlist).Forget();
112 | }
113 |
114 | var list = new List()
115 | {
116 | $"{nameofPlural}/Commands/AddEdit/AddEdit{name}Command.cs",
117 | $"{nameofPlural}/Commands/AddEdit/AddEdit{name}CommandValidator.cs",
118 | $"{nameofPlural}/Commands/Create/Create{name}Command.cs",
119 | $"{nameofPlural}/Commands/Create/Create{name}CommandValidator.cs",
120 | $"{nameofPlural}/Commands/Delete/Delete{name}Command.cs",
121 | $"{nameofPlural}/Commands/Delete/Delete{name}CommandValidator.cs",
122 | $"{nameofPlural}/Commands/Update/Update{name}Command.cs",
123 | $"{nameofPlural}/Commands/Update/Update{name}CommandValidator.cs",
124 | $"{nameofPlural}/Commands/Import/Import{nameofPlural}Command.cs",
125 | $"{nameofPlural}/Commands/Import/Import{nameofPlural}CommandValidator.cs",
126 | $"{nameofPlural}/Caching/{name}CacheKey.cs",
127 | $"{nameofPlural}/DTOs/{name}Dto.cs",
128 | /*$"{nameofPlural}/Mappers/{name}Mapper.cs",*/
129 | $"{nameofPlural}/EventHandlers/{name}CreatedEventHandler.cs",
130 | $"{nameofPlural}/EventHandlers/{name}UpdatedEventHandler.cs",
131 | $"{nameofPlural}/EventHandlers/{name}DeletedEventHandler.cs",
132 | $"{nameofPlural}/Specifications/{name}AdvancedFilter.cs",
133 | $"{nameofPlural}/Specifications/{name}AdvancedSpecification.cs",
134 | $"{nameofPlural}/Specifications/{name}ByIdSpecification.cs",
135 | $"{nameofPlural}/Queries/Export/Export{nameofPlural}Query.cs",
136 | $"{nameofPlural}/Queries/GetAll/GetAll{nameofPlural}Query.cs",
137 | $"{nameofPlural}/Queries/GetById/Get{name}ByIdQuery.cs",
138 | $"{nameofPlural}/Queries/Pagination/{nameofPlural}PaginationQuery.cs",
139 |
140 | };
141 | foreach (var item in list)
142 | {
143 | AddItemAsync(objectClass,item, name, target, objectlist).Forget();
144 | }
145 |
146 | var pages = new List()
147 | {
148 | $"Pages/{nameofPlural}/Create{name}.razor",
149 | $"Pages/{nameofPlural}/Edit{name}.razor",
150 | $"Pages/{nameofPlural}/View{name}.razor",
151 | $"Pages/{nameofPlural}/{nameofPlural}.razor",
152 | $"Pages/{nameofPlural}/Components/{name}FormDialog.razor",
153 | $"Pages/{nameofPlural}/Components/{nameofPlural}AdvancedSearchComponent.razor"
154 | };
155 | foreach (var item in pages)
156 | {
157 | AddItemAsync(objectClass,item, name, ui, objectlist).Forget();
158 | }
159 |
160 | }
161 | catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex))
162 | {
163 | Logger.Log(ex);
164 | MessageBox.Show(
165 | $"Error creating file '{inputname}':{Environment.NewLine}{ex.Message}",
166 | Vsix.Name,
167 | MessageBoxButton.OK,
168 | MessageBoxImage.Error);
169 | }
170 | }
171 | }
172 |
173 | private async Task AddItemAsync(IntellisenseObject classObject, string name,string itemname, NewItemTarget target,IEnumerable objectlist=null)
174 | {
175 | // The naming rules that apply to files created on disk also apply to virtual solution folders,
176 | // so regardless of what type of item we are creating, we need to validate the name.
177 | ValidatePath(name);
178 |
179 | if (name.EndsWith("\\", StringComparison.Ordinal))
180 | {
181 | if (target.IsSolutionOrSolutionFolder)
182 | {
183 | GetOrAddSolutionFolder(name, target);
184 | }
185 | else
186 | {
187 |
188 | AddProjectFolder(name, target);
189 | }
190 | }
191 | else
192 | {
193 | await AddFileAsync(classObject,name, itemname, target, objectlist);
194 | }
195 | }
196 |
197 | private void ValidatePath(string path)
198 | {
199 | do
200 | {
201 | string name = Path.GetFileName(path);
202 |
203 | if (_reservedFileNamePattern.IsMatch(name))
204 | {
205 | throw new InvalidOperationException($"The name '{name}' is a system reserved name.");
206 | }
207 |
208 | if (name.Any(c => _invalidFileNameChars.Contains(c)))
209 | {
210 | throw new InvalidOperationException($"The name '{name}' contains invalid characters.");
211 | }
212 |
213 | path = Path.GetDirectoryName(path);
214 | } while (!string.IsNullOrEmpty(path));
215 | }
216 |
217 | private async Task AddFileAsync(IntellisenseObject classObject, string name,string itemname, NewItemTarget target,IEnumerable objectlist=null)
218 | {
219 | await JoinableTaskFactory.SwitchToMainThreadAsync();
220 | FileInfo file;
221 |
222 | // If the file is being added to a solution folder, but that
223 | // solution folder doesn't have a corresponding directory on
224 | // disk, then write the file to the root of the solution instead.
225 | if (target.IsSolutionFolder && !Directory.Exists(target.Directory))
226 | {
227 | file = new FileInfo(Path.Combine(Path.GetDirectoryName(_dte.Solution.FullName), Path.GetFileName(name)));
228 | }
229 | else
230 | {
231 | file = new FileInfo(Path.Combine(target.Directory, name));
232 | }
233 |
234 | // Make sure the directory exists before we create the file. Don't use
235 | // `PackageUtilities.EnsureOutputPath()` because it can silently fail.
236 | Directory.CreateDirectory(file.DirectoryName);
237 |
238 | if (!file.Exists)
239 | {
240 | Project project;
241 |
242 | if (target.IsSolutionOrSolutionFolder)
243 | {
244 | project = GetOrAddSolutionFolder(Path.GetDirectoryName(name), target);
245 | }
246 | else
247 | {
248 | project = target.Project;
249 | }
250 |
251 | int position = await WriteFileAsync(project, classObject, file.FullName, itemname, target.Directory, objectlist);
252 | if (target.ProjectItem != null && target.ProjectItem.IsKind(Constants.vsProjectItemKindVirtualFolder))
253 | {
254 | target.ProjectItem.ProjectItems.AddFromFile(file.FullName);
255 | }
256 | else
257 | {
258 | project.AddFileToProject(file);
259 | }
260 |
261 | VsShellUtilities.OpenDocument(this, file.FullName);
262 |
263 | // Move cursor into position.
264 | if (position > 0)
265 | {
266 | Microsoft.VisualStudio.Text.Editor.IWpfTextView view = ProjectHelpers.GetCurentTextView();
267 |
268 | if (view != null)
269 | {
270 | view.Caret.MoveTo(new SnapshotPoint(view.TextBuffer.CurrentSnapshot, position));
271 | }
272 | }
273 |
274 | ExecuteCommandIfAvailable("SolutionExplorer.SyncWithActiveDocument");
275 | _dte.ActiveDocument.Activate();
276 | }
277 | else
278 | {
279 | //MessageBox.Show($"The file '{file}' already exists.", Vsix.Name, MessageBoxButton.OK, MessageBoxImage.Information);
280 | Console.WriteLine($"The file '{file}' already exists.");
281 | }
282 | }
283 |
284 | private static async Task WriteFileAsync(Project project, IntellisenseObject classObject, string file,string itemname,string selectFolder,IEnumerable objectlist=null)
285 | {
286 | string template = await TemplateMap.GetTemplateFilePathAsync(project, classObject,file, itemname, selectFolder, objectlist);
287 |
288 | if (!string.IsNullOrEmpty(template))
289 | {
290 | int index = template.IndexOf('$');
291 |
292 | if (index > -1)
293 | {
294 | //template = template.Remove(index, 1);
295 | }
296 |
297 | await WriteToDiskAsync(file, template);
298 | return index;
299 | }
300 |
301 | await WriteToDiskAsync(file, string.Empty);
302 |
303 | return 0;
304 | }
305 |
306 | private static async Task WriteToDiskAsync(string file, string content)
307 | {
308 | using (StreamWriter writer = new StreamWriter(file, false, GetFileEncoding(file)))
309 | {
310 | await writer.WriteAsync(content);
311 | }
312 | }
313 |
314 | private static Encoding GetFileEncoding(string file)
315 | {
316 | string[] noBom = { ".cmd", ".bat", ".json" };
317 | string ext = Path.GetExtension(file).ToLowerInvariant();
318 |
319 | if (noBom.Contains(ext))
320 | {
321 | return new UTF8Encoding(false);
322 | }
323 |
324 | return new UTF8Encoding(true);
325 | }
326 |
327 | private Project GetOrAddSolutionFolder(string name, NewItemTarget target)
328 | {
329 | if (target.IsSolution && string.IsNullOrEmpty(name))
330 | {
331 | // An empty solution folder name means we are not creating any solution
332 | // folders for that item, and the file we are adding is intended to be
333 | // added to the solution. Files cannot be added directly to the solution,
334 | // so there is a "Solution Items" folder that they are added to.
335 | return _dte.Solution.FindSolutionFolder(_solutionItemsProjectName)
336 | ?? ((Solution2)_dte.Solution).AddSolutionFolder(_solutionItemsProjectName);
337 | }
338 |
339 | // Even though solution folders are always virtual, if the target directory exists,
340 | // then we will also create the new directory on disk. This ensures that any files
341 | // that are added to this folder will end up in the corresponding physical directory.
342 | if (Directory.Exists(target.Directory))
343 | {
344 | // Don't use `PackageUtilities.EnsureOutputPath()` because it can silently fail.
345 | Directory.CreateDirectory(Path.Combine(target.Directory, name));
346 | }
347 |
348 | Project parent = target.Project;
349 |
350 | foreach (string segment in SplitPath(name))
351 | {
352 | // If we don't have a parent project yet,
353 | // then this folder is added to the solution.
354 | if (parent == null)
355 | {
356 | parent = _dte.Solution.FindSolutionFolder(segment) ?? ((Solution2)_dte.Solution).AddSolutionFolder(segment);
357 | }
358 | else
359 | {
360 | parent = parent.FindSolutionFolder(segment) ?? ((SolutionFolder)parent.Object).AddSolutionFolder(segment);
361 | }
362 | }
363 |
364 | return parent;
365 | }
366 |
367 | private void AddProjectFolder(string name, NewItemTarget target)
368 | {
369 | ThreadHelper.ThrowIfNotOnUIThread();
370 |
371 | // Make sure the directory exists before we add it to the project. Don't
372 | // use `PackageUtilities.EnsureOutputPath()` because it can silently fail.
373 | Directory.CreateDirectory(Path.Combine(target.Directory, name));
374 |
375 | // We can't just add the final directory to the project because that will
376 | // only add the final segment rather than adding each segment in the path.
377 | // Split the name into segments and add each folder individually.
378 | ProjectItems items = target.ProjectItem?.ProjectItems ?? target.Project.ProjectItems;
379 | string parentDirectory = target.Directory;
380 |
381 | foreach (string segment in SplitPath(name))
382 | {
383 | parentDirectory = Path.Combine(parentDirectory, segment);
384 |
385 | // Look for an existing folder in case it's already in the project.
386 | ProjectItem folder = items
387 | .OfType()
388 | .Where(item => segment.Equals(item.Name, StringComparison.OrdinalIgnoreCase))
389 | .Where(item => item.IsKind(Constants.vsProjectItemKindPhysicalFolder, Constants.vsProjectItemKindVirtualFolder))
390 | .FirstOrDefault();
391 |
392 | if (folder == null)
393 | {
394 | folder = items.AddFromDirectory(parentDirectory);
395 | }
396 |
397 | items = folder.ProjectItems;
398 | }
399 | }
400 |
401 | private static string[] SplitPath(string path)
402 | {
403 | return path.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
404 | }
405 |
406 | private static string[] GetParsedInput(string input)
407 | {
408 | // var tests = new string[] { "file1.txt", "file1.txt, file2.txt", ".ignore", ".ignore.(old,new)", "license", "folder/",
409 | // "folder\\", "folder\\file.txt", "folder/.thing", "page.aspx.cs", "widget-1.(html,js)", "pages\\home.(aspx, aspx.cs)",
410 | // "home.(html,js), about.(html,js,css)", "backup.2016.(old, new)", "file.(txt,txt,,)", "file_@#d+|%.3-2...3^&.txt" };
411 | Regex pattern = new Regex(@"[,]?([^(,]*)([\.\/\\]?)[(]?((?<=[^(])[^,]*|[^)]+)[)]?");
412 | List results = new List();
413 | Match match = pattern.Match(input);
414 |
415 | while (match.Success)
416 | {
417 | // Always 4 matches w. Group[3] being the extension, extension list, folder terminator ("/" or "\"), or empty string
418 | string path = match.Groups[1].Value.Trim() + match.Groups[2].Value;
419 | string[] extensions = match.Groups[3].Value.Split(',');
420 |
421 | foreach (string ext in extensions)
422 | {
423 | string value = path + ext.Trim();
424 |
425 | // ensure "file.(txt,,txt)" or "file.txt,,file.txt,File.TXT" returns as just ["file.txt"]
426 | if (value != "" && !value.EndsWith(".", StringComparison.Ordinal) && !results.Contains(value, StringComparer.OrdinalIgnoreCase))
427 | {
428 | results.Add(value);
429 | }
430 | }
431 | match = match.NextMatch();
432 | }
433 | return results.ToArray();
434 | }
435 |
436 | private string PromptForFileName(string folder,string[] entities)
437 | {
438 | DirectoryInfo dir = new DirectoryInfo(folder);
439 | FileNameDialog dialog = new FileNameDialog(dir.Name, entities);
440 |
441 | //IntPtr hwnd = new IntPtr(_dte.MainWindow.HWnd);
442 | //System.Windows.Window window = (System.Windows.Window)HwndSource.FromHwnd(hwnd).RootVisual;
443 | dialog.Owner = Application.Current.MainWindow;
444 |
445 | bool? result = dialog.ShowDialog();
446 | return (result.HasValue && result.Value) ? dialog.Input : string.Empty;
447 | }
448 |
449 | private void ExecuteCommandIfAvailable(string commandName)
450 | {
451 | ThreadHelper.ThrowIfNotOnUIThread();
452 | Command command;
453 |
454 | try
455 | {
456 | command = _dte.Commands.Item(commandName);
457 | }
458 | catch (ArgumentException)
459 | {
460 | // The command does not exist, so we can't execute it.
461 | return;
462 | }
463 |
464 | if (command.IsAvailable)
465 | {
466 | _dte.ExecuteCommand(commandName);
467 | }
468 | }
469 | }
470 | }
--------------------------------------------------------------------------------
/src/FileNameDialog.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/FileNameDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media.Imaging;
6 |
7 | namespace CleanArchitecture.CodeGenerator
8 | {
9 | public partial class FileNameDialog : Window
10 | {
11 | private const string DEFAULT_TEXT = "Select a entity name";
12 | private static readonly List _tips = new List {
13 | "Tip: CQRS stands for Command/Query Responsibility Segregation, and it's a wonderful thing",
14 | "Tip: All business logic is in a use case",
15 | "Tip: Good monolith with clear use cases that you can split in microservices later on, once you’ve learned more about them ",
16 | "Tip: CI/CD processes and solutions help to generate more value for the end-users of software",
17 | "Tip: the architecture is decoupled from the underlying data store",
18 | "Tip: An effective testing strategy that follows the testing pyramid",
19 | };
20 |
21 | public FileNameDialog(string folder,string[] entities)
22 | {
23 | InitializeComponent();
24 | lblFolder.Content = string.Format("{0}/", folder);
25 | foreach(var item in entities)
26 | {
27 | selectName.Items.Add(item);
28 | }
29 | selectName.Text = DEFAULT_TEXT;
30 | selectName.SelectionChanged += (s,e) => {
31 | btnCreate.IsEnabled = true;
32 | };
33 | Loaded += (s, e) =>
34 | {
35 | Icon = BitmapFrame.Create(new Uri("pack://application:,,,/CleanArchitectureCodeGenerator;component/Resources/icon.png", UriKind.RelativeOrAbsolute));
36 | Title = Vsix.Name;
37 | SetRandomTip();
38 |
39 | //txtName.Focus();
40 | //txtName.CaretIndex = 0;
41 | //txtName.Text = DEFAULT_TEXT;
42 | //txtName.Select(0, txtName.Text.Length);
43 |
44 | //txtName.PreviewKeyDown += (a, b) =>
45 | //{
46 | // if (b.Key == Key.Escape)
47 | // {
48 | // if (string.IsNullOrWhiteSpace(txtName.Text) || txtName.Text == DEFAULT_TEXT)
49 | // {
50 | // Close();
51 | // }
52 | // else
53 | // {
54 | // txtName.Text = string.Empty;
55 | // }
56 | // }
57 | // else if (txtName.Text == DEFAULT_TEXT)
58 | // {
59 | // txtName.Text = string.Empty;
60 | // btnCreate.IsEnabled = true;
61 | // }
62 | //};
63 |
64 | };
65 | }
66 |
67 | public string Input => selectName.SelectedItem?.ToString();
68 |
69 | private void SetRandomTip()
70 | {
71 | Random rnd = new Random(DateTime.Now.GetHashCode());
72 | int index = rnd.Next(_tips.Count);
73 | lblTips.Content = _tips[index];
74 | }
75 |
76 | private void Button_Click(object sender, RoutedEventArgs e)
77 | {
78 | DialogResult = true;
79 | Close();
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Helpers/Logger.cs:
--------------------------------------------------------------------------------
1 | using Microsoft;
2 | using Microsoft.VisualStudio.Shell.Interop;
3 |
4 | using System;
5 |
6 | internal static class Logger
7 | {
8 | private static string _name;
9 | private static IVsOutputWindowPane _pane;
10 | private static IVsOutputWindow _output;
11 |
12 | public static void Initialize(IServiceProvider provider, string name)
13 | {
14 | _output = (IVsOutputWindow)provider.GetService(typeof(SVsOutputWindow));
15 | Assumes.Present(_output);
16 | _name = name;
17 | }
18 |
19 | public static void Log(object message)
20 | {
21 | try
22 | {
23 | if (EnsurePane())
24 | {
25 | _pane.OutputString(DateTime.Now.ToString() + ": " + message + Environment.NewLine);
26 | }
27 | }
28 | catch (Exception ex)
29 | {
30 | System.Diagnostics.Debug.Write(ex);
31 | }
32 | }
33 |
34 | private static bool EnsurePane()
35 | {
36 | if (_pane == null)
37 | {
38 | Guid guid = Guid.NewGuid();
39 | _output.CreatePane(ref guid, _name, 1, 1);
40 | _output.GetPane(ref guid, out _pane);
41 | }
42 |
43 | return _pane != null;
44 | }
45 | }
--------------------------------------------------------------------------------
/src/Helpers/ProjectHelpers.cs:
--------------------------------------------------------------------------------
1 | using CleanArchitecture.CodeGenerator.Models;
2 | using EnvDTE;
3 |
4 | using EnvDTE80;
5 |
6 | using Microsoft;
7 | using Microsoft.VisualStudio;
8 | using Microsoft.VisualStudio.ComponentModelHost;
9 | using Microsoft.VisualStudio.Editor;
10 | using Microsoft.VisualStudio.Shell;
11 | using Microsoft.VisualStudio.Shell.Interop;
12 | using Microsoft.VisualStudio.Text.Editor;
13 | using Microsoft.VisualStudio.TextManager.Interop;
14 |
15 | using System;
16 | using System.Collections.Generic;
17 | using System.Data.Entity.Design.PluralizationServices;
18 | using System.Globalization;
19 | using System.IO;
20 | using System.Linq;
21 | using System.Runtime.InteropServices;
22 |
23 | namespace CleanArchitecture.CodeGenerator.Helpers
24 | {
25 | public static class ProjectHelpers
26 | {
27 | private static readonly DTE2 _dte = CodeGeneratorPackage._dte;
28 |
29 | public static string GetRootNamespace(this Project project)
30 | {
31 | if (project == null)
32 | {
33 | return null;
34 | }
35 |
36 | string ns = project.Name ?? string.Empty;
37 |
38 | try
39 | {
40 | Property prop = project.Properties.Item("RootNamespace");
41 |
42 | if (prop != null && prop.Value != null && !string.IsNullOrEmpty(prop.Value.ToString()))
43 | {
44 | ns = prop.Value.ToString();
45 | }
46 | }
47 | catch { /* Project doesn't have a root namespace */ }
48 |
49 | return CleanNameSpace(ns, stripPeriods: false);
50 | }
51 |
52 | public static string CleanNameSpace(string ns, bool stripPeriods = true)
53 | {
54 | if (stripPeriods)
55 | {
56 | ns = ns.Replace(".", "");
57 | }
58 |
59 | ns = ns.Replace(" ", "")
60 | .Replace("-", "")
61 | .Replace("\\", ".");
62 |
63 | return ns;
64 | }
65 |
66 | public static string GetRootFolder(this Project project)
67 | {
68 | if (project == null)
69 | {
70 | return null;
71 | }
72 |
73 | if (project.IsKind("{66A26720-8FB5-11D2-AA7E-00C04F688DDE}")) //ProjectKinds.vsProjectKindSolutionFolder
74 | {
75 | return Path.GetDirectoryName(_dte.Solution.FullName);
76 | }
77 |
78 | if (string.IsNullOrEmpty(project.FullName))
79 | {
80 | return null;
81 | }
82 |
83 | string fullPath;
84 |
85 | try
86 | {
87 | fullPath = project.Properties.Item("FullPath").Value as string;
88 | }
89 | catch (ArgumentException)
90 | {
91 | try
92 | {
93 | // MFC projects don't have FullPath, and there seems to be no way to query existence
94 | fullPath = project.Properties.Item("ProjectDirectory").Value as string;
95 | }
96 | catch (ArgumentException)
97 | {
98 | // Installer projects have a ProjectPath.
99 | fullPath = project.Properties.Item("ProjectPath").Value as string;
100 | }
101 | }
102 |
103 | if (string.IsNullOrEmpty(fullPath))
104 | {
105 | return File.Exists(project.FullName) ? Path.GetDirectoryName(project.FullName) : null;
106 | }
107 |
108 | if (Directory.Exists(fullPath))
109 | {
110 | return fullPath;
111 | }
112 |
113 | if (File.Exists(fullPath))
114 | {
115 | return Path.GetDirectoryName(fullPath);
116 | }
117 |
118 | return null;
119 | }
120 |
121 | public static ProjectItem AddFileToProject(this Project project, FileInfo file, string itemType = null)
122 | {
123 | if (project.IsKind(ProjectTypes.ASPNET_5, ProjectTypes.SSDT))
124 | {
125 | return _dte.Solution.FindProjectItem(file.FullName);
126 | }
127 |
128 | string root = project.GetRootFolder();
129 |
130 | if (string.IsNullOrEmpty(root) || !file.FullName.StartsWith(root, StringComparison.OrdinalIgnoreCase))
131 | {
132 | return null;
133 | }
134 |
135 | ProjectItem item = project.ProjectItems.AddFromFile(file.FullName);
136 | item.SetItemType(itemType);
137 | return item;
138 | }
139 |
140 | public static void SetItemType(this ProjectItem item, string itemType)
141 | {
142 | try
143 | {
144 | if (item == null || item.ContainingProject == null)
145 | {
146 | return;
147 | }
148 |
149 | if (string.IsNullOrEmpty(itemType)
150 | || item.ContainingProject.IsKind(ProjectTypes.WEBSITE_PROJECT)
151 | || item.ContainingProject.IsKind(ProjectTypes.UNIVERSAL_APP))
152 | {
153 | return;
154 | }
155 |
156 | item.Properties.Item("ItemType").Value = itemType;
157 | }
158 | catch (Exception ex)
159 | {
160 | Logger.Log(ex);
161 | }
162 | }
163 |
164 | public static string GetFileName(this ProjectItem item)
165 | {
166 | try
167 | {
168 | return item?.Properties?.Item("FullPath").Value?.ToString();
169 | }
170 | catch (ArgumentException)
171 | {
172 | // The property does not exist.
173 | return null;
174 | }
175 | }
176 |
177 | public static Project FindSolutionFolder(this Solution solution, string name)
178 | {
179 | return solution.Projects.OfType()
180 | .Where(p => p.IsKind(EnvDTE.Constants.vsProjectKindSolutionItems))
181 | .Where(p => p.Name == name)
182 | .FirstOrDefault();
183 | }
184 | public static Project FindProject(this Solution solution, string name)
185 | {
186 | var list= solution.Projects.OfType()
187 | .Where(p => p.IsKind(EnvDTE.Constants.vsProjectItemKindSolutionItems))
188 | .Where(p => p.Name == name)
189 | .FirstOrDefault();
190 |
191 | var project = GetProject(solution.Projects.OfType(), name);
192 |
193 | if (project == null)
194 | {
195 | throw new Exception($"Project {name} not found in solution");
196 | }
197 |
198 | return project;
199 |
200 |
201 | }
202 |
203 | private static Project GetProject(IEnumerable projects, string name)
204 | {
205 | foreach (Project project in projects)
206 | {
207 | var projectName = project.Name;
208 | if (projectName == name)
209 | {
210 | return project;
211 | }
212 | else if (project.Kind is ProjectKinds.vsProjectKindSolutionFolder)
213 | {
214 | var subProjects = project
215 | .ProjectItems
216 | .OfType()
217 | .Where(item => item.SubProject != null)
218 | .Select(item => item.SubProject);
219 |
220 | var projectInFolder = GetProject(subProjects, name);
221 |
222 | if (projectInFolder != null)
223 | {
224 | return projectInFolder;
225 | }
226 | }
227 | }
228 |
229 | return null;
230 | }
231 |
232 | public static Project FindSolutionFolder(this Project project, string name)
233 | {
234 | ThreadHelper.ThrowIfNotOnUIThread();
235 | return project.ProjectItems.OfType()
236 | .Where(p => p.IsKind(EnvDTE.Constants.vsProjectItemKindSolutionItems))
237 | .Where(p => p.Name == name)
238 | .Select(p => p.SubProject)
239 | .FirstOrDefault();
240 | }
241 |
242 | public static bool IsKind(this Project project, params string[] kindGuids)
243 | {
244 | foreach (string guid in kindGuids)
245 | {
246 | if (project.Kind.Equals(guid, StringComparison.OrdinalIgnoreCase))
247 | {
248 | return true;
249 | }
250 | }
251 |
252 | return false;
253 | }
254 |
255 | public static bool IsKind(this ProjectItem projectItem, params string[] kindGuids)
256 | {
257 | foreach (string guid in kindGuids)
258 | {
259 | if (projectItem.Kind.Equals(guid, StringComparison.OrdinalIgnoreCase))
260 | {
261 | return true;
262 | }
263 | }
264 |
265 | return false;
266 | }
267 |
268 | private static IEnumerable GetChildProjects(Project parent)
269 | {
270 | try
271 | {
272 | if (!parent.IsKind("{66A26720-8FB5-11D2-AA7E-00C04F688DDE}") && parent.Collection == null) // Unloaded
273 | {
274 | return Enumerable.Empty();
275 | }
276 |
277 | if (!string.IsNullOrEmpty(parent.FullName))
278 | {
279 | return new[] { parent };
280 | }
281 | }
282 | catch (COMException)
283 | {
284 | return Enumerable.Empty();
285 | }
286 |
287 | return parent.ProjectItems
288 | .Cast()
289 | .Where(p => p.SubProject != null)
290 | .SelectMany(p => GetChildProjects(p.SubProject));
291 | }
292 |
293 | public static Project GetActiveProject()
294 | {
295 | try
296 | {
297 |
298 | if (_dte.ActiveSolutionProjects is Array activeSolutionProjects && activeSolutionProjects.Length > 0)
299 | {
300 | return activeSolutionProjects.GetValue(0) as Project;
301 | }
302 |
303 | Document doc = _dte.ActiveDocument;
304 |
305 | if (doc != null && !string.IsNullOrEmpty(doc.FullName))
306 | {
307 | ProjectItem item = _dte.Solution?.FindProjectItem(doc.FullName);
308 |
309 | if (item != null)
310 | {
311 | return item.ContainingProject;
312 | }
313 | }
314 | }
315 | catch (Exception ex)
316 | {
317 | Logger.Log("Error getting the active project" + ex);
318 | }
319 |
320 | return null;
321 | }
322 |
323 | public static IWpfTextView GetCurentTextView()
324 | {
325 | IComponentModel componentModel = GetComponentModel();
326 | if (componentModel == null)
327 | {
328 | return null;
329 | }
330 |
331 | IVsEditorAdaptersFactoryService editorAdapter = componentModel.GetService();
332 |
333 | return editorAdapter.GetWpfTextView(GetCurrentNativeTextView());
334 | }
335 |
336 | public static IVsTextView GetCurrentNativeTextView()
337 | {
338 | IVsTextManager textManager = (IVsTextManager)ServiceProvider.GlobalProvider.GetService(typeof(SVsTextManager));
339 | Assumes.Present(textManager);
340 |
341 | ErrorHandler.ThrowOnFailure(textManager.GetActiveView(1, null, out IVsTextView activeView));
342 | return activeView;
343 | }
344 |
345 | public static IComponentModel GetComponentModel()
346 | {
347 | return (IComponentModel)CodeGeneratorPackage.GetGlobalService(typeof(SComponentModel));
348 | }
349 |
350 | public static object GetSelectedItem()
351 | {
352 | object selectedObject = null;
353 |
354 | IVsMonitorSelection monitorSelection = (IVsMonitorSelection)Package.GetGlobalService(typeof(SVsShellMonitorSelection));
355 |
356 | try
357 | {
358 | monitorSelection.GetCurrentSelection(out IntPtr hierarchyPointer,
359 | out uint itemId,
360 | out IVsMultiItemSelect multiItemSelect,
361 | out IntPtr selectionContainerPointer);
362 |
363 |
364 | if (Marshal.GetTypedObjectForIUnknown(
365 | hierarchyPointer,
366 | typeof(IVsHierarchy)) is IVsHierarchy selectedHierarchy)
367 | {
368 | ErrorHandler.ThrowOnFailure(selectedHierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ExtObject, out selectedObject));
369 | }
370 |
371 | Marshal.Release(hierarchyPointer);
372 | Marshal.Release(selectionContainerPointer);
373 | }
374 | catch (Exception ex)
375 | {
376 | System.Diagnostics.Debug.Write(ex);
377 | }
378 |
379 | return selectedObject;
380 | }
381 |
382 | public static IEnumerable GetEntities(this Project project)
383 | {
384 | ThreadHelper.ThrowIfNotOnUIThread();
385 | var list=new List();
386 | foreach(ProjectItem projectitem in project.ProjectItems)
387 | {
388 | GetProjectItem(projectitem, list);
389 | }
390 | return list;
391 | }
392 | private static void GetProjectItem(ProjectItem item ,List list)
393 | {
394 | ThreadHelper.ThrowIfNotOnUIThread();
395 | if (item.ProjectItems != null)
396 | {
397 | foreach(ProjectItem projectItem in item.ProjectItems)
398 | {
399 | GetProjectItem(projectItem,list);
400 | }
401 | }
402 | if (item.FileCodeModel !=null)
403 | {
404 | var objects= IntellisenseParser.ProcessFile(item);
405 | if (objects != null)
406 | {
407 | list.AddRange(objects);
408 | }
409 | }
410 |
411 |
412 | }
413 |
414 | public static string Pluralize(string name)
415 | {
416 | return PluralizationService.CreateService(new CultureInfo("en-US")).Pluralize(name);
417 | }
418 | }
419 |
420 | public static class ProjectTypes
421 | {
422 | public const string ASPNET_5 = "{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}";
423 | public const string DOTNET_Core = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}";
424 | public const string WEBSITE_PROJECT = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
425 | public const string UNIVERSAL_APP = "{262852C6-CD72-467D-83FE-5EEB1973A190}";
426 | public const string NODE_JS = "{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}";
427 | public const string SSDT = "{00d1a9c2-b5f0-4af3-8072-f6c62b433612}";
428 | }
429 | }
430 |
--------------------------------------------------------------------------------
/src/Helpers/Utility.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace CleanArchitecture.CodeGenerator.Helpers
4 | {
5 | internal static class Utility
6 | {
7 | public static string CamelCaseClassName(string name)
8 | {
9 |
10 | return CamelCase(name);
11 |
12 | }
13 |
14 | public static string CamelCaseEnumValue(string name)
15 | {
16 |
17 | return CamelCase(name);
18 |
19 | }
20 |
21 | public static string CamelCasePropertyName(string name)
22 | {
23 | return CamelCase(name);
24 |
25 | }
26 |
27 | private static string CamelCase(string name)
28 | {
29 | if (string.IsNullOrWhiteSpace(name))
30 | {
31 | return name;
32 | }
33 | return name[0].ToString(CultureInfo.CurrentCulture).ToLower(CultureInfo.CurrentCulture) + name.Substring(1);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helpers/VSHelpers.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 | using EnvDTE80;
3 | using Microsoft.VisualStudio;
4 | using Microsoft.VisualStudio.Shell;
5 | using Microsoft.VisualStudio.Shell.Interop;
6 | using System;
7 | using System.Diagnostics;
8 | using System.IO;
9 |
10 | namespace CleanArchitecture.CodeGenerator.Helpers
11 | {
12 | public static class VSHelpers
13 | {
14 | private static DTE2 DTE { get; } = Package.GetGlobalService(typeof(DTE)) as DTE2;
15 |
16 | public static ProjectItem GetProjectItem(string fileName)
17 | {
18 | return DTE.Solution.FindProjectItem(fileName);
19 | }
20 |
21 | public static void CheckFileOutOfSourceControl(string file)
22 | {
23 | if (!File.Exists(file) || DTE.Solution.FindProjectItem(file) == null)
24 | return;
25 |
26 | if (DTE.SourceControl.IsItemUnderSCC(file) && !DTE.SourceControl.IsItemCheckedOut(file))
27 | DTE.SourceControl.CheckOutItem(file);
28 |
29 | var info = new FileInfo(file)
30 | {
31 | IsReadOnly = false
32 | };
33 | }
34 |
35 |
36 |
37 | internal static readonly Guid outputPaneGuid = new Guid();
38 |
39 | internal static void WriteOnOutputWindow(string text)
40 | {
41 | WriteOnOutputWindow("TypeScript Definition Generator: " + text, outputPaneGuid);
42 | }
43 | internal static void WriteOnBuildOutputWindow(string text)
44 | {
45 | WriteOnOutputWindow(text, Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid);
46 | }
47 |
48 | internal static void WriteOnOutputWindow(string text, Guid guidBuildOutput)
49 | {
50 | if (!text.EndsWith(Environment.NewLine))
51 | {
52 | text += Environment.NewLine;
53 | }
54 |
55 | // At first write the text on the debug output.
56 | Debug.Write(text);
57 |
58 | // Now get the SVsOutputWindow service from the service provider.
59 | IVsOutputWindow outputWindow = ServiceProvider.GlobalProvider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
60 | if (null == outputWindow)
61 | {
62 | // If the provider doesn't expose the service there is nothing we can do.
63 | // Write a message on the debug output and exit.
64 | Debug.WriteLine("Can not get the SVsOutputWindow service.");
65 | return;
66 | }
67 |
68 | // We can not write on the Output window itself, but only on one of its panes.
69 | // Here we try to use the "General" pane.
70 | IVsOutputWindowPane windowPane;
71 | if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidBuildOutput, out windowPane)) ||
72 | (null == windowPane))
73 | {
74 | if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.CreatePane(ref guidBuildOutput, "TypeScript Definition Generator", 1, 0)))
75 | {
76 | // Nothing to do here, just debug output and exit
77 | Debug.WriteLine("Failed to create the Output window pane.");
78 | return;
79 | }
80 | if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidBuildOutput, out windowPane)) ||
81 | (null == windowPane))
82 | {
83 | // Again, there is nothing we can do to recover from this error, so write on the
84 | // debug output and exit.
85 | Debug.WriteLine("Failed to get the Output window pane.");
86 | return;
87 | }
88 | }
89 | if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.Activate()))
90 | {
91 | Debug.WriteLine("Failed to activate the Output window pane.");
92 | return;
93 | }
94 |
95 | // Finally we can write on the window pane.
96 | if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.OutputString(text)))
97 | {
98 | Debug.WriteLine("Failed to write on the Output window pane.");
99 | }
100 | }
101 | }
102 |
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/Helpers/VsTheme.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 |
6 | using Microsoft.VisualStudio.PlatformUI;
7 | using Microsoft.VisualStudio.Shell;
8 |
9 | namespace CleanArchitecture.CodeGenerator.Helpers
10 | {
11 | public static class VsTheme
12 | {
13 | private static Dictionary _isUsingVsTheme = new Dictionary();
14 | private static Dictionary _originalBackgrounds = new Dictionary();
15 |
16 | public static DependencyProperty UseVsThemeProperty = DependencyProperty.RegisterAttached("UseVsTheme", typeof(bool), typeof(VsTheme), new PropertyMetadata(false, UseVsThemePropertyChanged));
17 |
18 | private static void UseVsThemePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
19 | {
20 | SetUseVsTheme((UIElement)d, (bool)e.NewValue);
21 | }
22 |
23 | public static void SetUseVsTheme(UIElement element, bool value)
24 | {
25 | if (value)
26 | {
27 | if (!_originalBackgrounds.ContainsKey(element) && element is Control c)
28 | {
29 | _originalBackgrounds[element] = c.Background;
30 | }
31 |
32 | ((ContentControl)element).ShouldBeThemed();
33 | }
34 | else
35 | {
36 | ((ContentControl)element).ShouldNotBeThemed();
37 | }
38 |
39 | _isUsingVsTheme[element] = value;
40 | }
41 |
42 | public static bool GetUseVsTheme(UIElement element)
43 | {
44 | return _isUsingVsTheme.TryGetValue(element, out bool value) && value;
45 | }
46 |
47 | private static ResourceDictionary BuildThemeResources()
48 | {
49 | var allResources = new ResourceDictionary();
50 |
51 | try
52 | {
53 | var shellResources = (ResourceDictionary)Application.LoadComponent(new Uri("Microsoft.VisualStudio.Platform.WindowManagement;component/Themes/ThemedDialogDefaultStyles.xaml", UriKind.Relative));
54 | var scrollStyleContainer = (ResourceDictionary)Application.LoadComponent(new Uri("Microsoft.VisualStudio.Shell.UI.Internal;component/Styles/ScrollBarStyle.xaml", UriKind.Relative));
55 | allResources.MergedDictionaries.Add(shellResources);
56 | allResources.MergedDictionaries.Add(scrollStyleContainer);
57 | allResources[typeof(ScrollViewer)] = new Style
58 | {
59 | TargetType = typeof(ScrollViewer),
60 | BasedOn = (Style)scrollStyleContainer[VsResourceKeys.ScrollViewerStyleKey]
61 | };
62 |
63 | allResources[typeof(TextBox)] = new Style
64 | {
65 | TargetType = typeof(TextBox),
66 | BasedOn = (Style)shellResources[typeof(TextBox)],
67 | Setters =
68 | {
69 | new Setter(Control.PaddingProperty, new Thickness(2, 3, 2, 3))
70 | }
71 | };
72 |
73 | allResources[typeof(ComboBox)] = new Style
74 | {
75 | TargetType = typeof(ComboBox),
76 | BasedOn = (Style)shellResources[typeof(ComboBox)],
77 | Setters =
78 | {
79 | new Setter(Control.PaddingProperty, new Thickness(2, 3, 2, 3))
80 | }
81 | };
82 | }
83 | catch
84 | { }
85 |
86 | return allResources;
87 | }
88 |
89 | private static ResourceDictionary ThemeResources { get; } = BuildThemeResources();
90 |
91 | private static void ShouldBeThemed(this FrameworkElement control)
92 | {
93 | if (control.Resources == null)
94 | {
95 | control.Resources = ThemeResources;
96 | }
97 | else if (control.Resources != ThemeResources)
98 | {
99 | var d = new ResourceDictionary();
100 | d.MergedDictionaries.Add(ThemeResources);
101 | d.MergedDictionaries.Add(control.Resources);
102 | control.Resources = null;
103 | control.Resources = d;
104 | }
105 |
106 | if (control is Control c)
107 | {
108 | c.SetResourceReference(Control.BackgroundProperty, (string)EnvironmentColors.StartPageTabBackgroundBrushKey);
109 | }
110 | }
111 |
112 | private static void ShouldNotBeThemed(this FrameworkElement control)
113 | {
114 | if (control.Resources != null)
115 | {
116 | if (control.Resources == ThemeResources)
117 | {
118 | control.Resources = new ResourceDictionary();
119 | }
120 | else
121 | {
122 | control.Resources.MergedDictionaries.Remove(ThemeResources);
123 | }
124 | }
125 |
126 | //If we're themed now and we're something with a background property, reset it
127 | if (GetUseVsTheme(control) && control is Control c)
128 | {
129 | if (_originalBackgrounds.TryGetValue(control, out object background))
130 | {
131 | c.SetValue(Control.BackgroundProperty, background);
132 | }
133 | else
134 | {
135 | c.ClearValue(Control.BackgroundProperty);
136 | }
137 | }
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/src/Models/IntellisenseObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace CleanArchitecture.CodeGenerator.Models
5 | {
6 | public class IntellisenseObject : IEquatable
7 | {
8 | public string Namespace { get; set; }
9 | public string Name { get; set; }
10 | public string BaseNamespace { get; set; }
11 | public string BaseName { get; set; }
12 | public string FullName { get; set; }
13 | public bool IsEnum { get; set; }
14 | public string Summary { get; set; }
15 | public IList Properties { get; private set; }
16 | public HashSet References { get; private set; }
17 |
18 | public IntellisenseObject()
19 | {
20 | Properties = new List();
21 | References = new HashSet();
22 | }
23 |
24 | public IntellisenseObject(IList properties)
25 | {
26 | Properties = properties;
27 | }
28 |
29 | public IntellisenseObject(IList properties, HashSet references)
30 | {
31 | Properties = properties;
32 | References = references;
33 | }
34 |
35 | public void UpdateReferences(IEnumerable moreReferences)
36 | {
37 | References.UnionWith(moreReferences);
38 | }
39 |
40 | public bool Equals(IntellisenseObject other)
41 | {
42 | return !ReferenceEquals(other, null) &&
43 | other.Name == Name &&
44 | other.Namespace == Namespace &&
45 | other.BaseName == BaseName &&
46 | other.BaseNamespace == BaseNamespace &&
47 | other.FullName == FullName;
48 | }
49 |
50 | public override bool Equals(object obj)
51 | {
52 | return Equals(obj as IntellisenseObject);
53 | }
54 |
55 | public override int GetHashCode()
56 | {
57 | return Name.GetHashCode() ^
58 | Namespace.GetHashCode() ^
59 | FullName.GetHashCode();
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/src/Models/IntellisenseParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Numerics;
6 | using System.Runtime.InteropServices;
7 | using System.Text.RegularExpressions;
8 | using System.Xml.Linq;
9 | using CleanArchitecture.CodeGenerator.Helpers;
10 | using CleanArchitecture.CodeGenerator.Services;
11 | using EnvDTE;
12 | using EnvDTE80;
13 |
14 |
15 | namespace CleanArchitecture.CodeGenerator.Models
16 | {
17 | public static class IntellisenseParser
18 | {
19 | private static readonly string DefaultModuleName = "";
20 | private const string ModuleNameAttributeName = "TypeScriptModule";
21 | private static readonly Regex IsNumber = new Regex("^[0-9a-fx]+[ul]{0,2}$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
22 | private static Project _project;
23 |
24 | //internal static class Ext
25 | //{
26 | // public const string TypeScript = ".d.ts";
27 | //}
28 |
29 | internal static IEnumerable ProcessFile(ProjectItem item, HashSet underProcess = null)
30 | {
31 | if (item.FileCodeModel == null || item.ContainingProject == null)
32 | {
33 | return null;
34 | }
35 |
36 | _project = item.ContainingProject;
37 |
38 | var list = new List();
39 |
40 | if (underProcess == null)
41 | {
42 | underProcess = new HashSet();
43 | }
44 |
45 | foreach (CodeElement element in item.FileCodeModel.CodeElements)
46 | {
47 | if (element.Kind == vsCMElement.vsCMElementNamespace)
48 | {
49 | var cn = (CodeNamespace)element;
50 |
51 | foreach (CodeElement member in cn.Members)
52 | {
53 | if (ShouldProcess(member))
54 | {
55 | ProcessElement(member, list, underProcess);
56 | }
57 | }
58 | }
59 | else if (ShouldProcess(element))
60 | {
61 | ProcessElement(element, list, underProcess);
62 | }
63 | }
64 |
65 | return new HashSet(list);
66 | }
67 | private static void ProcessElement(CodeElement element, List list, HashSet underProcess)
68 | {
69 | if (element.Kind == vsCMElement.vsCMElementEnum)
70 | {
71 | ProcessEnum((CodeEnum)element, list);
72 | }
73 | else if (element.Kind == vsCMElement.vsCMElementClass)
74 | {
75 | var cc = (CodeClass)element;
76 |
77 | // Don't re-generate the intellisense.
78 | if (list.Any(x => x.Name == GetClassName(cc) && x.Namespace == GetNamespace(cc)))
79 | {
80 | return;
81 | }
82 |
83 | // Collect inherit classes.
84 | CodeClass baseClass = null;
85 |
86 | try
87 | {
88 | // To recuse from throwing from a weird case
89 | // where user inherit class from struct and save. As such inheritance is disallowed.
90 | baseClass = cc.Bases.Cast()
91 | .FirstOrDefault(c => c.FullName != "System.Object");
92 | }
93 | catch { /* Silently continue. */ }
94 | var baseClasses = new string[] { "BaseAuditableSoftDeleteEntity", "BaseAuditableEntity", "BaseEntity", "IEntity", "ISoftDelete", "OwnerPropertyEntity" };
95 | if (baseClass != null && baseClasses.Contains(GetClassName(baseClass)))
96 | {
97 | ProcessClass(cc, baseClass, list, underProcess);
98 | }
99 | var references = new HashSet();
100 | try
101 | {
102 | // Process Inheritence.
103 | if (baseClass != null && !underProcess.Contains(baseClass) && !HasIntellisense(baseClass.ProjectItem, references))
104 | {
105 | list.Last().UpdateReferences(references);
106 | underProcess.Add(baseClass);
107 | list.AddRange(ProcessFile(baseClass.ProjectItem, underProcess));
108 | }
109 | }
110 | catch
111 | {
112 |
113 | }
114 | }
115 | }
116 |
117 | private static bool ShouldProcess(CodeElement member)
118 | {
119 | return
120 | member.Kind == vsCMElement.vsCMElementClass
121 | || member.Kind == vsCMElement.vsCMElementEnum;
122 | }
123 |
124 | private static void ProcessEnum(CodeEnum element, List list)
125 | {
126 | var data = new IntellisenseObject
127 | {
128 | Name = element.Name,
129 | IsEnum = element.Kind == vsCMElement.vsCMElementEnum,
130 | FullName = element.FullName,
131 | Namespace = GetNamespace(element),
132 | Summary = GetSummary(element)
133 | };
134 |
135 | foreach (CodeVariable codeEnum in element.Members.OfType())
136 | {
137 | var prop = new IntellisenseProperty
138 | {
139 | Name = codeEnum.Name,
140 | Summary = GetSummary(codeEnum),
141 | InitExpression = GetInitializer(codeEnum.InitExpression)
142 | };
143 |
144 | data.Properties.Add(prop);
145 | }
146 |
147 | if (data.Properties.Count > 0)
148 | {
149 | list.Add(data);
150 | }
151 | }
152 |
153 | private static void ProcessClass(CodeClass cc, CodeClass baseClass, List list, HashSet underProcess)
154 | {
155 | string baseNs = null;
156 | string baseClassName = null;
157 | var ns = GetNamespace(cc);
158 | var className = GetClassName(cc);
159 | var references = new HashSet();
160 | IList properties = GetProperties(cc.Members, new HashSet(), references).ToList();
161 |
162 | foreach (CodeElement member in cc.Members)
163 | {
164 | if (ShouldProcess(member))
165 | {
166 | ProcessElement(member, list, underProcess);
167 | }
168 | }
169 |
170 | if (baseClass != null)
171 | {
172 | baseClassName = GetClassName(baseClass);
173 | baseNs = GetNamespace(baseClass);
174 | }
175 |
176 | var intellisenseObject = new IntellisenseObject(properties.ToList(), references)
177 | {
178 | Namespace = ns,
179 | Name = className,
180 | BaseNamespace = baseNs,
181 | BaseName = baseClassName,
182 | FullName = cc.FullName,
183 | Summary = GetSummary(cc)
184 | };
185 |
186 | list.Add(intellisenseObject);
187 | }
188 |
189 | private static IEnumerable GetProperties(CodeElements props, HashSet traversedTypes, HashSet references = null)
190 | {
191 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
192 | return from p in props.OfType()
193 | where !p.Attributes.Cast().Any(HasIgnoreAttribute)
194 | where vsCMAccess.vsCMAccessPublic == p.Access && p.Getter != null && !p.Getter.IsShared && IsPublic(p.Getter)
195 | select new IntellisenseProperty
196 | {
197 | Name = GetName(p),
198 | Type = GetType(p.Parent, p.Type, traversedTypes, references),
199 | Summary = GetSummary(p)
200 | };
201 | }
202 |
203 | private static bool HasIgnoreAttribute(CodeAttribute attribute)
204 | {
205 | return attribute.FullName == "System.Runtime.Serialization.IgnoreDataMemberAttribute" ||
206 | attribute.FullName == "Newtonsoft.Json.JsonIgnoreAttribute" ||
207 | attribute.FullName == "System.Web.Script.Serialization.ScriptIgnoreAttribute";
208 | }
209 |
210 | private static bool IsPublic(CodeFunction cf)
211 | {
212 | vsCMElement fun = cf.Kind;
213 |
214 | var retVal = false;
215 | try
216 | {
217 | retVal = cf.Access == vsCMAccess.vsCMAccessPublic;
218 | }
219 | catch (COMException)
220 | {
221 | var cp = cf.Parent as CodeProperty;
222 | if (cp != null)
223 | {
224 | retVal = cp.Access == vsCMAccess.vsCMAccessPublic;
225 | }
226 |
227 | }
228 | return retVal;
229 | }
230 |
231 | private static string GetClassName(CodeClass cc)
232 | {
233 | return GetDataContractName(cc, "Name") ?? cc.Name;
234 | }
235 |
236 | private static string GetNamespace(CodeClass cc)
237 | {
238 | return GetDataContractName(cc, "Namespace") ?? GetNamespace(cc.Attributes);
239 | }
240 |
241 | private static string GetDataContractName(CodeClass cc, string attrName)
242 | {
243 | IEnumerable dataContractAttribute = cc.Attributes.Cast().Where(a => a.Name == "DataContract");
244 | return GetDataContractNameInner(dataContractAttribute, attrName);
245 | }
246 |
247 | private static string GetNamespace(CodeEnum cc)
248 | {
249 | return GetDataContractName(cc, "Namespace") ?? GetNamespace(cc.Attributes);
250 | }
251 | private static string GetDataContractName(CodeEnum cc, string attrName)
252 | {
253 | IEnumerable dataContractAttribute = cc.Attributes.Cast().Where(a => a.Name == "DataContract");
254 | return GetDataContractNameInner(dataContractAttribute, attrName);
255 | }
256 | private static string GetDataContractNameInner(IEnumerable dataContractAttribute, string attrName)
257 | {
258 | if (!dataContractAttribute.Any())
259 | {
260 | return null;
261 | }
262 |
263 | string name = null;
264 | var keyValues = dataContractAttribute.First().Children.OfType()
265 | .ToDictionary(a => a.Name, a => (a.Value ?? "").Trim('\"', '\''));
266 |
267 | if (keyValues.ContainsKey(attrName))
268 | {
269 | name = keyValues[attrName];
270 | }
271 |
272 | return name;
273 | }
274 |
275 | private static string GetNamespace(CodeElements attrs)
276 | {
277 | if (attrs == null)
278 | {
279 | return DefaultModuleName;
280 | }
281 |
282 | IEnumerable namespaceFromAttr = from a in attrs.Cast()
283 | where a.Name.EndsWith(ModuleNameAttributeName, StringComparison.OrdinalIgnoreCase)
284 | from arg in a.Arguments.Cast()
285 | let v = (arg.Value ?? "").Trim('\"')
286 | where !string.IsNullOrWhiteSpace(v)
287 | select v;
288 |
289 | return namespaceFromAttr.FirstOrDefault() ?? DefaultModuleName;
290 | }
291 |
292 | private static IntellisenseType GetType(CodeClass rootElement, CodeTypeRef codeTypeRef, HashSet traversedTypes, HashSet references)
293 | {
294 | var isArray = codeTypeRef.TypeKind == vsCMTypeRef.vsCMTypeRefArray;
295 | var isCollection = codeTypeRef.AsString.StartsWith("System.Collections", StringComparison.Ordinal);
296 | var isDictionary = false;
297 |
298 | CodeTypeRef effectiveTypeRef = codeTypeRef;
299 | if (isArray && codeTypeRef.ElementType != null)
300 | {
301 | effectiveTypeRef = effectiveTypeRef.ElementType;
302 | }
303 | else if (isCollection)
304 | {
305 | effectiveTypeRef = TryToGuessGenericArgument(rootElement, effectiveTypeRef);
306 | }
307 |
308 | if (isCollection)
309 | {
310 | isDictionary = codeTypeRef.AsString.StartsWith("System.Collections.Generic.Dictionary", StringComparison.Ordinal)
311 | || codeTypeRef.AsString.StartsWith("System.Collections.Generic.IDictionary", StringComparison.Ordinal);
312 | }
313 |
314 | var typeName = effectiveTypeRef.AsFullName;
315 |
316 | try
317 | {
318 |
319 | var codeClass = effectiveTypeRef.CodeType as CodeClass2;
320 | var codeEnum = effectiveTypeRef.CodeType as CodeEnum;
321 | var isPrimitive = IsPrimitive(effectiveTypeRef);
322 |
323 | var result = new IntellisenseType
324 | {
325 | IsArray = !isDictionary && (isArray || isCollection),
326 | IsDictionary = isDictionary,
327 | CodeName = effectiveTypeRef.AsString,
328 | ClientSideReferenceName =
329 | effectiveTypeRef.TypeKind == vsCMTypeRef.vsCMTypeRefCodeType &&
330 | effectiveTypeRef.CodeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject
331 | ?
332 | (codeClass != null && HasIntellisense(codeClass.ProjectItem, references) ? (GetNamespace(codeClass) + "." + Utility.CamelCaseClassName(GetClassName(codeClass))) : null) ??
333 | (codeEnum != null && HasIntellisense(codeEnum.ProjectItem, references) ? (GetNamespace(codeEnum) + "." + Utility.CamelCaseClassName(codeEnum.Name)) : null)
334 | : null
335 | };
336 |
337 | if (!isPrimitive && codeClass != null && !traversedTypes.Contains(effectiveTypeRef.CodeType.FullName) && !isCollection)
338 | {
339 | traversedTypes.Add(effectiveTypeRef.CodeType.FullName);
340 | result.Shape = GetProperties(effectiveTypeRef.CodeType.Members, traversedTypes, references).ToList();
341 | traversedTypes.Remove(effectiveTypeRef.CodeType.FullName);
342 | }
343 |
344 | return result;
345 | }
346 | catch (InvalidCastException)
347 | {
348 | VSHelpers.WriteOnOutputWindow(string.Format("ERROR - Cannot find definition for {0}", typeName));
349 | throw new ArgumentException(string.Format("Cannot find definition of {0}", typeName));
350 | }
351 | }
352 |
353 | private static CodeTypeRef TryToGuessGenericArgument(CodeClass rootElement, CodeTypeRef codeTypeRef)
354 | {
355 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
356 | var codeTypeRef2 = codeTypeRef as CodeTypeRef2;
357 | if (codeTypeRef2 == null || !codeTypeRef2.IsGeneric)
358 | {
359 | return codeTypeRef;
360 | }
361 |
362 | // There is no way to extract generic parameter as CodeTypeRef or something similar
363 | // (see http://social.msdn.microsoft.com/Forums/vstudio/en-US/09504bdc-2b81-405a-a2f7-158fb721ee90/envdte-envdte80-codetyperef2-and-generic-types?forum=vsx)
364 | // but we can make it work at least for some simple case with the following heuristic:
365 | // 1) get the argument's local name by parsing the type reference's full text
366 | // 2) if it's a known primitive (i.e. string, int, etc.), return that
367 | // 3) otherwise, guess that it's a type from the same namespace and same project,
368 | // and use the project CodeModel to retrieve it by full name
369 | // 4) if CodeModel returns null - well, bad luck, don't have any more guesses
370 | var typeNameAsInCode = codeTypeRef2.AsString.Split('<', '>').ElementAtOrDefault(1) ?? "";
371 | CodeModel projCodeModel;
372 |
373 | try
374 | {
375 | projCodeModel = rootElement.ProjectItem.ContainingProject.CodeModel;
376 | }
377 | catch (COMException)
378 | {
379 | projCodeModel = _project.CodeModel;
380 | }
381 |
382 | CodeType codeType = projCodeModel.CodeTypeFromFullName(TryToGuessFullName(typeNameAsInCode));
383 |
384 | if (codeType != null)
385 | {
386 | return projCodeModel.CreateCodeTypeRef(codeType);
387 | }
388 |
389 | return codeTypeRef;
390 | }
391 |
392 | private static readonly Dictionary _knownPrimitiveTypes = new Dictionary(StringComparer.OrdinalIgnoreCase) {
393 | { "string", typeof( string ) },
394 | { "int", typeof( int ) },
395 | { "long", typeof( long ) },
396 | { "short", typeof( short ) },
397 | { "byte", typeof( byte ) },
398 | { "uint", typeof( uint ) },
399 | { "ulong", typeof( ulong ) },
400 | { "ushort", typeof( ushort ) },
401 | { "sbyte", typeof( sbyte ) },
402 | { "float", typeof( float ) },
403 | { "double", typeof( double ) },
404 | { "decimal", typeof( decimal ) },
405 | { "Guid", typeof( Guid ) },
406 | { "DateTime", typeof( DateTime ) },
407 | { "DateTimeOffset", typeof( DateTimeOffset ) },
408 | { "bool", typeof( bool ) },
409 | { "BigInteger", typeof( BigInteger ) },
410 | { "char", typeof( char ) }
411 |
412 | };
413 |
414 | private static string TryToGuessFullName(string typeName)
415 | {
416 | if (_knownPrimitiveTypes.TryGetValue(typeName, out Type primitiveType))
417 | {
418 | return primitiveType.FullName;
419 | }
420 |
421 | return typeName;
422 | }
423 |
424 | private static bool IsPrimitive(CodeTypeRef codeTypeRef)
425 | {
426 | if (codeTypeRef.TypeKind != vsCMTypeRef.vsCMTypeRefOther && codeTypeRef.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType)
427 | {
428 | return true;
429 | }
430 |
431 | if (codeTypeRef.AsString.EndsWith("DateTime", StringComparison.Ordinal))
432 | {
433 | return true;
434 | }
435 |
436 | return false;
437 | }
438 |
439 | private static bool HasIntellisense(ProjectItem projectItem, HashSet references)
440 | {
441 | for (short i = 0; i < projectItem.FileCount; i++)
442 | {
443 | var fileName = GenerationService.GenerateFileName(projectItem.FileNames[i]);
444 |
445 | if (File.Exists(fileName))
446 | {
447 | references.Add(fileName);
448 | return true;
449 | }
450 | }
451 |
452 | return false;
453 | }
454 |
455 | // Maps attribute name to array of attribute properties to get resultant name from
456 | private static readonly IReadOnlyDictionary nameAttributes = new Dictionary
457 | {
458 | { "DataMember", new [] { "Name" } },
459 | { "JsonProperty", new [] { "", "PropertyName" } }
460 | };
461 |
462 | private static string GetName(CodeProperty property)
463 | {
464 | foreach (CodeAttribute attr in property.Attributes)
465 | {
466 | var className = Path.GetExtension(attr.Name);
467 |
468 | if (string.IsNullOrEmpty(className))
469 | {
470 | className = attr.Name;
471 | }
472 |
473 | if (!nameAttributes.TryGetValue(className, out var argumentNames))
474 | {
475 | continue;
476 | }
477 |
478 | CodeAttributeArgument value = attr.Children.OfType().FirstOrDefault(a => argumentNames.Contains(a.Name));
479 |
480 | if (value == null)
481 | {
482 | break;
483 | }
484 |
485 | // Strip the leading & trailing quotes
486 | return value.Value.Trim('@', '\'', '"');
487 | }
488 |
489 | return property.Name.Trim('@');
490 | }
491 |
492 | // External items throw an exception from the DocComment getter
493 | private static string GetSummary(CodeProperty property) { return property.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject ? null : GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); }
494 |
495 | private static string GetSummary(CodeClass property) { return GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); }
496 |
497 | private static string GetSummary(CodeEnum property) { return GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); }
498 |
499 | private static string GetSummary(CodeVariable property) { return GetSummary(property.InfoLocation, property.DocComment, property.Comment, property.FullName); }
500 |
501 | private static string GetSummary(vsCMInfoLocation location, string xmlComment, string inlineComment, string fullName)
502 | {
503 | if (location != vsCMInfoLocation.vsCMInfoLocationProject || (string.IsNullOrWhiteSpace(xmlComment) && string.IsNullOrWhiteSpace(inlineComment)))
504 | {
505 | return null;
506 | }
507 |
508 | try
509 | {
510 | var summary = "";
511 | if (!string.IsNullOrWhiteSpace(xmlComment))
512 | {
513 | summary = XElement.Parse(xmlComment)
514 | .Descendants("summary")
515 | .Select(x => x.Value)
516 | .FirstOrDefault();
517 | }
518 | if (!string.IsNullOrEmpty(summary))
519 | {
520 | return summary.Trim();
521 | }
522 |
523 | if (!string.IsNullOrWhiteSpace(inlineComment))
524 | {
525 | return inlineComment.Trim();
526 | }
527 |
528 | return null;
529 | }
530 | catch (Exception ex)
531 | {
532 | return null;
533 | }
534 | }
535 |
536 | private static string GetInitializer(object initExpression)
537 | {
538 | if (initExpression != null)
539 | {
540 | var initializer = initExpression.ToString();
541 | if (IsNumber.IsMatch(initializer))
542 | {
543 | return initializer;
544 | }
545 | }
546 | return null;
547 | }
548 | }
549 | }
550 |
--------------------------------------------------------------------------------
/src/Models/IntellisenseProperty.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace CleanArchitecture.CodeGenerator.Models
4 | {
5 | public class IntellisenseProperty
6 | {
7 | public IntellisenseProperty()
8 | {
9 |
10 | }
11 | public IntellisenseProperty(IntellisenseType type, string propertyName)
12 | {
13 | Type = type;
14 | Name = propertyName;
15 | }
16 |
17 | public string Name { get; set; }
18 |
19 | public string NameWithOption { get { return (this.Type != null && this.Type.IsOptional) ? this.Name + "?" : this.Name; } }
20 |
21 | [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
22 | Justification = "Unambiguous in this context.")]
23 | public IntellisenseType Type { get; set; }
24 |
25 | public string Summary { get; set; }
26 | public string InitExpression { get; set; }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Models/IntellisenseType.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Globalization;
3 |
4 | namespace CleanArchitecture.CodeGenerator.Models
5 | {
6 | public class IntellisenseType
7 | {
8 | ///
9 | /// This is the name of this type as it appears in the source code
10 | ///
11 | public string CodeName { get; set; }
12 |
13 | ///
14 | /// Indicates whether this type is array. Is this property is true, then all other properties
15 | /// describe not the type itself, but rather the type of the array's elements.
16 | ///
17 | public bool IsArray { get; set; }
18 |
19 | public bool IsDictionary { get; set; }
20 |
21 | public bool IsOptional { get { return CodeName.EndsWith("?"); } }
22 |
23 | ///
24 | /// If this type is itself part of a source code file that has a .d.ts definitions file attached,
25 | /// this property will contain the full (namespace-qualified) client-side name of that type.
26 | /// Otherwise, this property is null.
27 | ///
28 | public string ClientSideReferenceName { get; set; }
29 |
30 | ///
31 | /// This is TypeScript-formed shape of the type (i.e. inline type definition). It is used for the case where
32 | /// the type is not primitive, but does not have its own named client-side definition.
33 | ///
34 | public IEnumerable Shape { get; set; }
35 |
36 | public bool IsKnownType
37 | {
38 | get { return TypeScriptName != "any"; }
39 | }
40 |
41 | public string TypeScriptName
42 | {
43 | get
44 | {
45 | if (IsDictionary) return GetKVPTypes();
46 | return GetTargetName(CodeName, false);
47 | }
48 | }
49 |
50 | private string GetTargetName(string codeName, bool js)
51 | {
52 | var t = codeName.ToLowerInvariant().TrimEnd('?');
53 | switch (t)
54 | {
55 | case "int16":
56 | case "int32":
57 | case "int64":
58 | case "short":
59 | case "int":
60 | case "long":
61 | case "float":
62 | case "double":
63 | case "decimal":
64 | case "biginteger":
65 | return js ? "Number" : "number";
66 |
67 | case "datetime":
68 | case "datetimeoffset":
69 | case "system.datetime":
70 | case "system.datetimeoffset":
71 | return "Date";
72 |
73 | case "string":
74 | return js ? "String" : "string";
75 |
76 | case "bool":
77 | case "boolean":
78 | return js ? "Boolean" : "boolean";
79 | }
80 | return js ? "Object" : GetComplexTypeScriptName();
81 | }
82 |
83 | private string GetComplexTypeScriptName()
84 | {
85 | return ClientSideReferenceName ?? "any";
86 | }
87 |
88 | private string GetKVPTypes()
89 | {
90 | var type = CodeName.ToLowerInvariant().TrimEnd('?');
91 | var types = type.Split('<', '>')[1].Split(',');
92 | string keyType = GetTargetName(types[0].Trim(), false);
93 |
94 | if (keyType != "string" && keyType != "number")
95 | { // only string or number are allowed for keys
96 | keyType = "string";
97 | }
98 |
99 | string valueType = GetTargetName(types[1].Trim(), false);
100 |
101 | return string.Format(CultureInfo.CurrentCulture, "{{ [index: {0}]: {1} }}", keyType, valueType);
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/src/NewItemTarget.cs:
--------------------------------------------------------------------------------
1 | using CleanArchitecture.CodeGenerator.Helpers;
2 | using EnvDTE;
3 | using EnvDTE80;
4 | using Microsoft.VisualStudio.Shell;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 |
10 | namespace CleanArchitecture.CodeGenerator
11 | {
12 | public class NewItemTarget
13 | {
14 | public static NewItemTarget Create(DTE2 dte)
15 | {
16 | NewItemTarget item = null;
17 |
18 | // If a document is active, try to use the document's containing directory.
19 | if (dte.ActiveWindow is Window2 window && window.Type == vsWindowType.vsWindowTypeDocument)
20 | {
21 | item = CreateFromActiveDocument(dte);
22 | }
23 |
24 | // If no document was selected, or we could not get a selected item from
25 | // the document, then use the selected item in the Solution Explorer window.
26 | if (item == null)
27 | {
28 | item = CreateFromSolutionExplorerSelection(dte);
29 | }
30 |
31 | return item;
32 | }
33 | public static NewItemTarget Create(DTE2 dte,string projectName)
34 | {
35 | NewItemTarget item = null;
36 |
37 | item = CreateFromProject(dte, projectName);
38 |
39 | return item;
40 | }
41 | private static NewItemTarget CreateFromProject(DTE2 dte, string projectName)
42 | {
43 | Project project = dte.Solution.FindProject(projectName);
44 | if (project != null)
45 | {
46 | return new NewItemTarget(project.GetRootFolder(), project, null, isSolutionOrSolutionFolder: false);
47 | }
48 | return null;
49 | }
50 |
51 | private static NewItemTarget CreateFromActiveDocument(DTE2 dte)
52 | {
53 | ThreadHelper.ThrowIfNotOnUIThread();
54 | string fileName = dte.ActiveDocument?.FullName;
55 | if (File.Exists(fileName))
56 | {
57 | ProjectItem docItem = dte.Solution.FindProjectItem(fileName);
58 | if (docItem != null)
59 | {
60 | return CreateFromProjectItem(docItem);
61 | }
62 | }
63 |
64 | return null;
65 | }
66 |
67 | private static NewItemTarget CreateFromSolutionExplorerSelection(DTE2 dte)
68 | {
69 | ThreadHelper.ThrowIfNotOnUIThread();
70 |
71 | Array items = (Array)dte.ToolWindows.SolutionExplorer.SelectedItems;
72 |
73 | if (items.Length == 1)
74 | {
75 | UIHierarchyItem selection = items.Cast().First();
76 |
77 | if (selection.Object is Solution solution)
78 | {
79 | return new NewItemTarget(Path.GetDirectoryName(solution.FullName), null, null, isSolutionOrSolutionFolder: true);
80 | }
81 | else if (selection.Object is Project project)
82 | {
83 | if (project.IsKind(Constants.vsProjectKindSolutionItems))
84 | {
85 | return new NewItemTarget(GetSolutionFolderPath(project), project, null, isSolutionOrSolutionFolder: true);
86 | }
87 | else
88 | {
89 | return new NewItemTarget(project.GetRootFolder(), project, null, isSolutionOrSolutionFolder: false);
90 | }
91 | }
92 | else if (selection.Object is ProjectItem projectItem)
93 | {
94 | return CreateFromProjectItem(projectItem);
95 | }
96 | }
97 |
98 | return null;
99 | }
100 |
101 | private static NewItemTarget CreateFromProjectItem(ProjectItem projectItem)
102 | {
103 | ThreadHelper.ThrowIfNotOnUIThread();
104 | if (projectItem.IsKind(Constants.vsProjectItemKindSolutionItems))
105 | {
106 | return new NewItemTarget(
107 | GetSolutionFolderPath(projectItem.ContainingProject),
108 | projectItem.ContainingProject,
109 | null,
110 | isSolutionOrSolutionFolder: true);
111 | }
112 | else
113 | {
114 | // The selected item needs a directory. This project item could be
115 | // a virtual folder, so resolve it to a physical file or folder.
116 | projectItem = ResolveToPhysicalProjectItem(projectItem);
117 | string fileName = projectItem?.GetFileName();
118 |
119 | if (string.IsNullOrEmpty(fileName))
120 | {
121 | return null;
122 | }
123 |
124 | // If the file exists, then it must be a file and we can get the
125 | // directory name from it. If the file does not exist, then it
126 | // must be a directory, and the directory name is the file name.
127 | string directory = File.Exists(fileName) ? Path.GetDirectoryName(fileName) : fileName;
128 | return new NewItemTarget(directory, projectItem.ContainingProject, projectItem, isSolutionOrSolutionFolder: false);
129 | }
130 | }
131 |
132 | private static ProjectItem ResolveToPhysicalProjectItem(ProjectItem projectItem)
133 | {
134 | ThreadHelper.ThrowIfNotOnUIThread();
135 |
136 | if (projectItem.IsKind(Constants.vsProjectItemKindVirtualFolder))
137 | {
138 | // Find the first descendant item that is not a virtual folder.
139 | return projectItem.ProjectItems
140 | .Cast()
141 | .Select(item => ResolveToPhysicalProjectItem(item))
142 | .FirstOrDefault(item => item != null);
143 | }
144 |
145 | return projectItem;
146 | }
147 |
148 | private static string GetSolutionFolderPath(Project folder)
149 | {
150 | ThreadHelper.ThrowIfNotOnUIThread();
151 |
152 | string solutionDirectory = Path.GetDirectoryName(folder.DTE.Solution.FullName);
153 | List segments = new List();
154 |
155 | // Record the names of each folder up the
156 | // hierarchy until we reach the solution.
157 | do
158 | {
159 | segments.Add(folder.Name);
160 | folder = folder.ParentProjectItem?.ContainingProject;
161 | } while (folder != null);
162 |
163 | // Because we walked up the hierarchy,
164 | // the path segments are in reverse order.
165 | segments.Reverse();
166 |
167 | return Path.Combine(new[] { solutionDirectory }.Concat(segments).ToArray());
168 | }
169 |
170 | private NewItemTarget(string directory, Project project, ProjectItem projectItem, bool isSolutionOrSolutionFolder)
171 | {
172 | Directory = directory;
173 | Project = project;
174 | ProjectItem = projectItem;
175 | IsSolutionOrSolutionFolder = isSolutionOrSolutionFolder;
176 | }
177 |
178 | public string Directory { get; }
179 |
180 | public Project Project { get; }
181 |
182 | public ProjectItem ProjectItem { get; }
183 |
184 | public bool IsSolutionOrSolutionFolder { get; }
185 |
186 | public bool IsSolution => IsSolutionOrSolutionFolder && Project == null;
187 |
188 | public bool IsSolutionFolder => IsSolutionOrSolutionFolder && Project != null;
189 |
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using CleanArchitecture.CodeGenerator;
2 | using System;
3 | using System.Reflection;
4 | using System.Resources;
5 | using System.Runtime.InteropServices;
6 |
7 | [assembly: AssemblyTitle(Vsix.Name)]
8 | [assembly: AssemblyDescription(Vsix.Description)]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany(Vsix.Author)]
11 | [assembly: AssemblyProduct(Vsix.Name)]
12 | [assembly: AssemblyCopyright(Vsix.Author)]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 | [assembly: ComVisible(false)]
16 | [assembly: CLSCompliant(false)]
17 | [assembly: NeutralResourcesLanguage(Vsix.Language)]
18 |
19 | [assembly: AssemblyVersion(Vsix.Version)]
20 | [assembly: AssemblyFileVersion(Vsix.Version)]
--------------------------------------------------------------------------------
/src/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/src/Resources/icon.png
--------------------------------------------------------------------------------
/src/Resources/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neozhu/CleanArchitectureCodeGenerator/e776c86717a4a83fb08bd7e0bc9fdca9fc94c3c1/src/Resources/logo.png
--------------------------------------------------------------------------------
/src/Services/GenerationService.cs:
--------------------------------------------------------------------------------
1 | using CleanArchitecture.CodeGenerator.Helpers;
2 | using CleanArchitecture.CodeGenerator.Models;
3 | using EnvDTE;
4 | using Microsoft.VisualStudio.Text;
5 | using Microsoft.VisualStudio.Text.Editor;
6 | using Microsoft.VisualStudio.Utilities;
7 | using System;
8 | using System.ComponentModel.Composition;
9 | using System.IO;
10 | using System.Windows.Threading;
11 |
12 | namespace CleanArchitecture.CodeGenerator.Services
13 | {
14 | [Export(typeof(IWpfTextViewCreationListener))]
15 | [ContentType("csharp")]
16 | [ContentType("basic")]
17 | [TextViewRole(PredefinedTextViewRoles.PrimaryDocument)]
18 | public class GenerationService : IWpfTextViewCreationListener
19 | {
20 | private ProjectItem _item;
21 |
22 | [Import]
23 | public ITextDocumentFactoryService _documentService { get; set; }
24 |
25 | public void TextViewCreated(IWpfTextView textView)
26 | {
27 | if (!_documentService.TryGetTextDocument(textView.TextBuffer, out var doc))
28 | return;
29 |
30 | _item = VSHelpers.GetProjectItem(doc.FilePath);
31 |
32 | if (_item?.ContainingProject == null ||
33 | !_item.ContainingProject.IsKind(ProjectTypes.DOTNET_Core, ProjectTypes.ASPNET_5, ProjectTypes.WEBSITE_PROJECT))
34 | return;
35 |
36 | doc.FileActionOccurred += FileActionOccurred;
37 | }
38 |
39 | private void FileActionOccurred(object sender, TextDocumentFileActionEventArgs e)
40 | {
41 | if (e.FileActionType != FileActionTypes.ContentSavedToDisk)
42 | return;
43 | _item = VSHelpers.GetProjectItem(e.FilePath);
44 | string fileName = GenerationService.GenerateFileName(e.FilePath);
45 |
46 | if (File.Exists(fileName))
47 | {
48 | CreateDtsFile(_item);
49 | }
50 | }
51 |
52 | public static string ConvertToTypeScript(ProjectItem sourceItem)
53 | {
54 | try
55 | {
56 | VSHelpers.WriteOnOutputWindow(string.Format("{0} - Started", sourceItem.Name));
57 | var list = IntellisenseParser.ProcessFile(sourceItem);
58 | VSHelpers.WriteOnOutputWindow(string.Format("{0} - Completed", sourceItem.Name));
59 | return IntellisenseWriter.WriteTypeScript(list);
60 | }
61 | catch (Exception ex)
62 | {
63 | VSHelpers.WriteOnOutputWindow(string.Format("{0} - Failure", sourceItem.Name));
64 | return null;
65 | }
66 | }
67 |
68 | public static string GenerateFileName(string sourceFile)
69 | {
70 |
71 | return Path.ChangeExtension(sourceFile, ".d.ts");
72 |
73 | }
74 |
75 | public static void CreateDtsFile(ProjectItem sourceItem)
76 | {
77 | string sourceFile = sourceItem.FileNames[1];
78 | string dtsFile = GenerationService.GenerateFileName(sourceFile);
79 | string dts = ConvertToTypeScript(sourceItem);
80 |
81 | VSHelpers.CheckFileOutOfSourceControl(dtsFile);
82 | File.WriteAllText(dtsFile, dts);
83 |
84 | if (sourceItem.ContainingProject.IsKind(ProjectTypes.DOTNET_Core, ProjectTypes.ASPNET_5))
85 | {
86 | Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
87 | {
88 | var dtsItem = VSHelpers.GetProjectItem(dtsFile);
89 |
90 | if (dtsItem != null)
91 | dtsItem.Properties.Item("DependentUpon").Value = sourceItem.Name;
92 |
93 | }), DispatcherPriority.ApplicationIdle, null);
94 | }
95 | else if (sourceItem.ContainingProject.IsKind(ProjectTypes.WEBSITE_PROJECT))
96 | {
97 | sourceItem.ContainingProject.ProjectItems.AddFromFile(dtsFile);
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Services/IntellisenseWriter.cs:
--------------------------------------------------------------------------------
1 | using CleanArchitecture.CodeGenerator.Helpers;
2 | using CleanArchitecture.CodeGenerator.Models;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Text.RegularExpressions;
8 |
9 |
10 |
11 | namespace CleanArchitecture.CodeGenerator.Services
12 | {
13 | internal static class IntellisenseWriter
14 | {
15 | private static readonly Regex _whitespaceTrimmer = new Regex(@"^\s+|\s+$|\s*[\r\n]+\s*", RegexOptions.Compiled);
16 |
17 | public static string WriteTypeScript(IEnumerable objects)
18 | {
19 | var sb = new StringBuilder();
20 |
21 | foreach (IGrouping ns in objects.GroupBy(o => o.Namespace))
22 | {
23 |
24 | sb.AppendFormat("declare module {0} {{\r\n", ns.Key);
25 |
26 |
27 | foreach (IntellisenseObject io in ns)
28 | {
29 | if (!string.IsNullOrEmpty(io.Summary))
30 | {
31 | sb.AppendLine("\t/** " + _whitespaceTrimmer.Replace(io.Summary, "") + " */");
32 | }
33 |
34 | if (io.IsEnum)
35 | {
36 |
37 | sb.AppendLine("\tconst enum " + Utility.CamelCaseClassName(io.Name) + " {");
38 |
39 | foreach (IntellisenseProperty p in io.Properties)
40 | {
41 | WriteTypeScriptComment(p, sb);
42 |
43 | if (p.InitExpression != null)
44 | {
45 | sb.AppendLine("\t\t" + Utility.CamelCaseEnumValue(p.Name) + " = " + CleanEnumInitValue(p.InitExpression) + ",");
46 | }
47 | else
48 | {
49 | sb.AppendLine("\t\t" + Utility.CamelCaseEnumValue(p.Name) + ",");
50 | }
51 | }
52 |
53 | sb.AppendLine("\t}");
54 |
55 | }
56 | else
57 | {
58 | var type = "\tinterface ";
59 | sb.Append(type).Append(Utility.CamelCaseClassName(io.Name)).Append(" ");
60 |
61 | if (!string.IsNullOrEmpty(io.BaseName))
62 | {
63 | sb.Append("extends ");
64 |
65 | if (!string.IsNullOrEmpty(io.BaseNamespace) && io.BaseNamespace != io.Namespace)
66 | {
67 | sb.Append(io.BaseNamespace).Append(".");
68 | }
69 |
70 | sb.Append(Utility.CamelCaseClassName(io.BaseName)).Append(" ");
71 | }
72 |
73 | WriteTSInterfaceDefinition(sb, "\t", io.Properties);
74 | sb.AppendLine();
75 | }
76 | }
77 |
78 |
79 | sb.AppendLine("}");
80 |
81 | }
82 |
83 | return sb.ToString();
84 | }
85 |
86 | private static string CleanEnumInitValue(string value)
87 | {
88 | value = value.TrimEnd('u', 'U', 'l', 'L'); //uint ulong long
89 | if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
90 | {
91 | return value;
92 | }
93 |
94 | var trimedValue = value.TrimStart('0'); // prevent numbers to be parsed as octal in js.
95 | if (trimedValue.Length > 0)
96 | {
97 | return trimedValue;
98 | }
99 |
100 | return "0";
101 | }
102 |
103 |
104 | private static void WriteTypeScriptComment(IntellisenseProperty p, StringBuilder sb)
105 | {
106 | if (string.IsNullOrEmpty(p.Summary))
107 | {
108 | return;
109 | }
110 |
111 | sb.AppendLine("\t\t/** " + _whitespaceTrimmer.Replace(p.Summary, "") + " */");
112 | }
113 |
114 | private static void WriteTSInterfaceDefinition(StringBuilder sb, string prefix,
115 | IEnumerable props)
116 | {
117 | sb.AppendLine("{");
118 |
119 | foreach (IntellisenseProperty p in props)
120 | {
121 | WriteTypeScriptComment(p, sb);
122 | sb.AppendFormat("{0}\t{1}: ", prefix, Utility.CamelCasePropertyName(p.NameWithOption));
123 |
124 | if (p.Type.IsKnownType)
125 | {
126 | sb.Append(p.Type.TypeScriptName);
127 | }
128 | else
129 | {
130 | if (p.Type.Shape == null)
131 | {
132 | sb.Append("any");
133 | }
134 | else
135 | {
136 | WriteTSInterfaceDefinition(sb, prefix + "\t", p.Type.Shape);
137 | }
138 | }
139 | if (p.Type.IsArray)
140 | {
141 | sb.Append("[]");
142 | }
143 |
144 | sb.AppendLine(";");
145 | }
146 |
147 | sb.Append(prefix).Append("}");
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/Templates/.bowerrc.txt:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "wwwroot/lib"$
3 | }
--------------------------------------------------------------------------------
/src/Templates/.cs-interface.txt:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace {namespace}
8 | {
9 | public interface {itemname}
10 | {
11 | $
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Templates/.cs.txt:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace {namespace}
8 | {
9 | public class {itemname}
10 | {
11 | $
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Templates/.html.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {itemname}
5 |
6 |
7 | $
8 |
9 |
--------------------------------------------------------------------------------
/src/Templates/.json.txt:
--------------------------------------------------------------------------------
1 | {
2 | $
3 | }
--------------------------------------------------------------------------------
/src/Templates/.md.txt:
--------------------------------------------------------------------------------
1 | # Markdown File
2 |
3 | $
--------------------------------------------------------------------------------
/src/Templates/.razor.txt:
--------------------------------------------------------------------------------
1 | {itemname}
2 |
3 | $
4 |
5 | @code {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/Templates/.vb-interface.txt:
--------------------------------------------------------------------------------
1 | Public Interface {itemname}
2 | $
3 | End Interface
4 |
--------------------------------------------------------------------------------
/src/Templates/.vb.txt:
--------------------------------------------------------------------------------
1 | Public Class {itemname}
2 | $
3 | End Class
4 |
--------------------------------------------------------------------------------
/src/Templates/Caching/.cachekey.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines static methods and properties for managing cache keys and expiration
12 | // settings for {itemname}-related data. This includes creating unique cache keys for
13 | // various {itemnamelowercase} queries (such as getting all {itemnamelowercase}s, {itemnamelowercase}s by ID, etc.),
14 | // managing the cache expiration tokens to control cache validity, and providing a
15 | // mechanism to refresh cached data in a thread-safe manner.
16 | //
17 | //------------------------------------------------------------------------------
18 | #nullable enable
19 | #nullable disable warnings
20 |
21 | namespace {namespace};
22 | ///
23 | /// Static class for managing cache keys and expiration for {itemname}-related data.
24 | ///
25 | public static class {itemname}CacheKey
26 | {
27 | public const string GetAllCacheKey = "all-{nameofPlural}";
28 | public static string GetPaginationCacheKey(string parameters) {
29 | return $"{itemname}CacheKey:{nameofPlural}WithPaginationQuery,{parameters}";
30 | }
31 | public static string GetExportCacheKey(string parameters) {
32 | return $"{itemname}CacheKey:ExportCacheKey,{parameters}";
33 | }
34 | public static string GetByNameCacheKey(string parameters) {
35 | return $"{itemname}CacheKey:GetByNameCacheKey,{parameters}";
36 | }
37 | public static string GetByIdCacheKey(string parameters) {
38 | return $"{itemname}CacheKey:GetByIdCacheKey,{parameters}";
39 | }
40 | public static IEnumerable? Tags => new string[] { "{itemnamelowercase}" };
41 | public static void Refresh()
42 | {
43 | FusionCacheFactory.RemoveByTags(Tags);
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/src/Templates/Commands/AddEdit/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Command for adding/editing a {itemnamelowercase} entity with validation, mapping,
7 | // domain events, and cache invalidation.
8 | // Documentation: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
9 | //
10 | //------------------------------------------------------------------------------
11 | #nullable enable
12 | #nullable disable warnings
13 |
14 |
15 | using {selectns}.{nameofPlural}.Caching;
16 | using {selectns}.{nameofPlural}.DTOs;
17 | namespace {namespace};
18 |
19 | public class AddEdit{itemname}Command: ICacheInvalidatorRequest>
20 | {
21 | [Description("Id")]
22 | public int Id { get; set; }
23 | {dtoFieldDefinitionWithoutList}
24 |
25 | public string CacheKey => {itemname}CacheKey.GetAllCacheKey;
26 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
27 | private class Mapping : Profile
28 | {
29 | public Mapping()
30 | {
31 | CreateMap<{itemname}Dto, AddEdit{itemname}Command>(MemberList.None);
32 | CreateMap(MemberList.None);
33 | }
34 | }
35 | }
36 |
37 | public class AddEdit{itemname}CommandHandler : IRequestHandler>
38 | {
39 | private readonly IMapper _mapper;
40 | private readonly IApplicationDbContext _context;
41 | public AddEdit{itemname}CommandHandler(
42 | IMapper mapper,
43 | IApplicationDbContext context)
44 | {
45 | _mapper = mapper;
46 | _context = context;
47 | }
48 | public async Task> Handle(AddEdit{itemname}Command request, CancellationToken cancellationToken)
49 | {
50 | if (request.Id > 0)
51 | {
52 | var item = await _context.{nameofPlural}.FindAsync(request.Id, cancellationToken);
53 | if (item == null)
54 | {
55 | return await Result.FailureAsync($"{itemname} with id: [{request.Id}] not found.");
56 | }
57 | item = _mapper.Map(request, item);
58 | // raise a update domain event
59 | item.AddDomainEvent(new {itemname}UpdatedEvent(item));
60 | await _context.SaveChangesAsync(cancellationToken);
61 | return await Result.SuccessAsync(item.Id);
62 | }
63 | else
64 | {
65 | var item = _mapper.Map<{itemname}>(request);
66 | // raise a create domain event
67 | item.AddDomainEvent(new {itemname}CreatedEvent(item));
68 | _context.{nameofPlural}.Add(item);
69 | await _context.SaveChangesAsync(cancellationToken);
70 | return await Result.SuccessAsync(item.Id);
71 | }
72 |
73 | }
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/src/Templates/Commands/AddEdit/.validator.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Validator for AddEdit{itemname}Command: enforces field length and required property rules for {itemname} entities.
7 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
8 | //
9 | //------------------------------------------------------------------------------
10 | #nullable enable
11 | #nullable disable warnings
12 |
13 |
14 | namespace {namespace};
15 |
16 | public class AddEdit{itemname}CommandValidator : AbstractValidator
17 | {
18 | public AddEdit{itemname}CommandValidator()
19 | {
20 | {commandValidatorRuleFor}
21 | }
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Create/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Command and handler for creating a new {itemname}.
7 | // Uses caching invalidation and domain events for data consistency.
8 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
9 | //
10 | //------------------------------------------------------------------------------
11 | #nullable enable
12 | #nullable disable warnings
13 |
14 | using {selectns}.{nameofPlural}.Caching;
15 |
16 | namespace {namespace};
17 |
18 | public class Create{itemname}Command: ICacheInvalidatorRequest>
19 | {
20 | [Description("Id")]
21 | public int Id { get; set; }
22 | {dtoFieldDefinitionWithoutList}
23 | public string CacheKey => {itemname}CacheKey.GetAllCacheKey;
24 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
25 | private class Mapping : Profile
26 | {
27 | public Mapping()
28 | {
29 | CreateMap(MemberList.None);
30 | }
31 | }
32 | }
33 |
34 | public class Create{itemname}CommandHandler : IRequestHandler>
35 | {
36 | private readonly IMapper _mapper;
37 | private readonly IApplicationDbContext _context;
38 | public Create{itemname}CommandHandler(
39 | IMapper mapper,
40 | IApplicationDbContext context)
41 | {
42 | _mapper = mapper;
43 | _context = context;
44 | }
45 | public async Task> Handle(Create{itemname}Command request, CancellationToken cancellationToken)
46 | {
47 | var item = _mapper.Map<{itemname}>(request);
48 | // raise a create domain event
49 | item.AddDomainEvent(new {itemname}CreatedEvent(item));
50 | _context.{nameofPlural}.Add(item);
51 | await _context.SaveChangesAsync(cancellationToken);
52 | return await Result.SuccessAsync(item.Id);
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Create/.validator.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Validator for Create{itemname}Command: enforces max lengths and required fields for {itemname} entities.
7 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
8 | //
9 | //------------------------------------------------------------------------------
10 | #nullable enable
11 | #nullable disable warnings
12 |
13 | namespace {namespace};
14 |
15 | public class Create{itemname}CommandValidator : AbstractValidator
16 | {
17 | public Create{itemname}CommandValidator()
18 | {
19 | {commandValidatorRuleFor}
20 | }
21 |
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Delete/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Command and handler for deleting {itemname} entities.
7 | // Implements cache invalidation and triggers domain events.
8 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
9 | //
10 | //------------------------------------------------------------------------------
11 | #nullable enable
12 | #nullable disable warnings
13 |
14 | using {selectns}.{nameofPlural}.Caching;
15 |
16 | namespace {namespace};
17 |
18 | public class Delete{itemname}Command: ICacheInvalidatorRequest
19 | {
20 | public int[] Id { get; }
21 | public string CacheKey => {itemname}CacheKey.GetAllCacheKey;
22 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
23 | public Delete{itemname}Command(int[] id)
24 | {
25 | Id = id;
26 | }
27 | }
28 |
29 | public class Delete{itemname}CommandHandler :
30 | IRequestHandler
31 |
32 | {
33 | private readonly IApplicationDbContext _context;
34 | public Delete{itemname}CommandHandler(
35 | IApplicationDbContext context)
36 | {
37 | _context = context;
38 | }
39 | public async Task Handle(Delete{itemname}Command request, CancellationToken cancellationToken)
40 | {
41 | var items = await _context.{nameofPlural}.Where(x=>request.Id.Contains(x.Id)).ToListAsync(cancellationToken);
42 | foreach (var item in items)
43 | {
44 | // raise a delete domain event
45 | item.AddDomainEvent(new {itemname}DeletedEvent(item));
46 | _context.{nameofPlural}.Remove(item);
47 | }
48 | await _context.SaveChangesAsync(cancellationToken);
49 | return await Result.SuccessAsync();
50 | }
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Delete/.validator.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Validator for Delete{itemname}Command: ensures the ID list for {itemnamelowercase} is not null and contains only positive IDs.
7 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
8 | //
9 | //------------------------------------------------------------------------------
10 | #nullable enable
11 | #nullable disable warnings
12 |
13 | namespace {namespace};
14 |
15 | public class Delete{itemname}CommandValidator : AbstractValidator
16 | {
17 | public Delete{itemname}CommandValidator()
18 | {
19 |
20 | RuleFor(v => v.Id).NotNull().ForEach(v=>v.GreaterThan(0));
21 |
22 | }
23 | }
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Import/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Import command & template for {itemnamelowercase}s.
7 | // Validates Excel data, prevents duplicates, and provides a template for bulk entry.
8 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
9 | //
10 | //------------------------------------------------------------------------------
11 | #nullable enable
12 | #nullable disable warnings
13 |
14 | using {selectns}.{nameofPlural}.DTOs;
15 | using {selectns}.{nameofPlural}.Caching;
16 |
17 | namespace {namespace};
18 |
19 | public class Import{nameofPlural}Command: ICacheInvalidatorRequest>
20 | {
21 | public string FileName { get; set; }
22 | public byte[] Data { get; set; }
23 | public string CacheKey => {itemname}CacheKey.GetAllCacheKey;
24 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
25 | public Import{nameofPlural}Command(string fileName,byte[] data)
26 | {
27 | FileName = fileName;
28 | Data = data;
29 | }
30 | }
31 | public record class Create{nameofPlural}TemplateCommand : IRequest>
32 | {
33 |
34 | }
35 |
36 | public class Import{nameofPlural}CommandHandler :
37 | IRequestHandler>,
38 | IRequestHandler>
39 | {
40 | private readonly IApplicationDbContext _context;
41 | private readonly IStringLocalizer _localizer;
42 | private readonly IExcelService _excelService;
43 | private readonly {itemname}Dto _dto = new();
44 | private readonly IMapper _mapper;
45 | public Import{nameofPlural}CommandHandler(
46 | IApplicationDbContext context,
47 | IMapper mapper,
48 | IExcelService excelService,
49 | IStringLocalizer localizer)
50 | {
51 | _context = context;
52 | _localizer = localizer;
53 | _excelService = excelService;
54 | _mapper = mapper;
55 | }
56 | #nullable disable warnings
57 | public async Task> Handle(Import{nameofPlural}Command request, CancellationToken cancellationToken)
58 | {
59 |
60 | var result = await _excelService.ImportAsync(request.Data, mappers: new Dictionary>
61 | {
62 | {importFuncExpression}
63 | }, _localizer[_dto.GetClassDescription()]);
64 | if (result.Succeeded && result.Data is not null)
65 | {
66 | foreach (var dto in result.Data)
67 | {
68 | var exists = await _context.{nameofPlural}.AnyAsync(x => x.Name == dto.Name, cancellationToken);
69 | if (!exists)
70 | {
71 | var item = _mapper.Map<{itemname}>(dto);
72 | // add create domain events if this entity implement the IHasDomainEvent interface
73 | // item.AddDomainEvent(new {itemname}CreatedEvent(item));
74 | await _context.{nameofPlural}.AddAsync(item, cancellationToken);
75 | }
76 | }
77 | await _context.SaveChangesAsync(cancellationToken);
78 | return await Result.SuccessAsync(result.Data.Count());
79 | }
80 | else
81 | {
82 | return await Result.FailureAsync(result.Errors);
83 | }
84 | }
85 | public async Task> Handle(Create{nameofPlural}TemplateCommand request, CancellationToken cancellationToken)
86 | {
87 | // TODO: Implement Import{nameofPlural}CommandHandler method
88 | var fields = new string[] {
89 | // TODO: Define the fields that should be generate in the template, for example:
90 | {templateFieldDefinition}
91 | };
92 | var result = await _excelService.CreateTemplateAsync(fields, _localizer[_dto.GetClassDescription()]);
93 | return await Result.SuccessAsync(result);
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Import/.validator.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Validator for Import{itemname}sCommand: ensures the Data property is non-null and non-empty for {itemnamelowercase} import.
7 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
8 | //
9 | //------------------------------------------------------------------------------
10 | #nullable enable
11 | #nullable disable warnings
12 |
13 | namespace {namespace};
14 |
15 | public class Import{nameofPlural}CommandValidator : AbstractValidator
16 | {
17 | public Import{nameofPlural}CommandValidator()
18 | {
19 |
20 | RuleFor(v => v.Data)
21 | .NotNull()
22 | .NotEmpty();
23 |
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Update/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Update{itemname}Command & handler: updates an existing {itemname} with cache invalidation and raises {itemname}UpdatedEvent.
7 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
8 | //
9 | //------------------------------------------------------------------------------
10 | #nullable enable
11 | #nullable disable warnings
12 |
13 | using {selectns}.{nameofPlural}.DTOs;
14 | using {selectns}.{nameofPlural}.Caching;
15 |
16 | namespace {namespace};
17 |
18 | public class Update{itemname}Command: ICacheInvalidatorRequest>
19 | {
20 | [Description("Id")]
21 | public int Id { get; set; }
22 | {dtoFieldDefinitionWithoutList}
23 | public string CacheKey => {itemname}CacheKey.GetAllCacheKey;
24 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
25 |
26 | private class Mapping : Profile
27 | {
28 | public Mapping()
29 | {
30 | CreateMap(MemberList.None);
31 | CreateMap<{itemname}Dto,Update{itemname}Command>(MemberList.None);
32 | }
33 | }
34 |
35 | }
36 |
37 | public class Update{itemname}CommandHandler : IRequestHandler>
38 | {
39 | private readonly IApplicationDbContext _context;
40 | private readonly IMapper _mapper;
41 | public Update{itemname}CommandHandler(
42 | IMapper mapper,
43 | IApplicationDbContext context)
44 | {
45 | _context = context;
46 | _mapper = mapper;
47 | }
48 | public async Task> Handle(Update{itemname}Command request, CancellationToken cancellationToken)
49 | {
50 |
51 | var item = await _context.{nameofPlural}.FindAsync(request.Id, cancellationToken);
52 | if (item == null)
53 | {
54 | return await Result.FailureAsync($"{itemname} with id: [{request.Id}] not found.");
55 | }
56 | item = _mapper.Map(request, item);
57 | // raise a update domain event
58 | item.AddDomainEvent(new {itemname}UpdatedEvent(item));
59 | await _context.SaveChangesAsync(cancellationToken);
60 | return await Result.SuccessAsync(item.Id);
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/src/Templates/Commands/Update/.validator.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Validator for Update{itemname}Command: ensures required fields (e.g., Id, non-empty Name, max length for properties) are valid for updating a {itemnamelowercase}.
7 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
8 | //
9 | //------------------------------------------------------------------------------
10 | #nullable enable
11 | #nullable disable warnings
12 |
13 | namespace {namespace};
14 |
15 | public class Update{itemname}CommandValidator : AbstractValidator
16 | {
17 | public Update{itemname}CommandValidator()
18 | {
19 | RuleFor(v => v.Id).NotNull();
20 | {commandValidatorRuleFor}
21 |
22 | }
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/Templates/DTOs/.dto.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // {itemname}Dto: transfers {itemnamelowercase} data between layers.
7 | // Docs: https://docs.cleanarchitectureblazor.com/features/{itemnamelowercase}
8 | //
9 | //------------------------------------------------------------------------------
10 | #nullable enable
11 | #nullable disable warnings
12 |
13 |
14 | namespace {namespace};
15 |
16 | [Description("{nameofPlural}")]
17 | public class {itemname}Dto
18 | {
19 | [Description("Id")]
20 | public int Id { get; set; }
21 | {dtoFieldDefinition}
22 |
23 | private class Mapping : Profile
24 | {
25 | public Mapping()
26 | {
27 | CreateMap<{itemname}, {itemname}Dto>(MemberList.None);
28 | CreateMap<{itemname}Dto, {itemname}>(MemberList.None)
29 | .ForMember(dest => dest.Created, opt => opt.Ignore())
30 | .ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
31 | .ForMember(dest => dest.LastModified, opt => opt.Ignore())
32 | .ForMember(dest => dest.LastModifiedBy, opt => opt.Ignore())
33 | .ForMember(dest => dest.DomainEvents, opt => opt.Ignore());
34 | }
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/src/Templates/EventHandlers/.created.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Handles {itemname}CreatedEvent: triggered when a new {itemnamelowercase} is created.
7 | // Extendable for additional actions (e.g., notifications, system updates).
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | namespace {namespace};
13 |
14 | public class {itemname}CreatedEventHandler : INotificationHandler<{itemname}CreatedEvent>
15 | {
16 | private readonly ILogger<{itemname}CreatedEventHandler> _logger;
17 |
18 | public {itemname}CreatedEventHandler(
19 | ILogger<{itemname}CreatedEventHandler> logger
20 | )
21 | {
22 | _logger = logger;
23 | }
24 | public Task Handle({itemname}CreatedEvent notification, CancellationToken cancellationToken)
25 | {
26 | _logger.LogInformation("Handled domain event '{EventType}' with notification: {@Notification} ", notification.GetType().Name, notification);
27 | return Task.CompletedTask;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Templates/EventHandlers/.deleted.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Handles {itemname}DeletedEvent: triggered when a {itemnamelowercase} is deleted.
7 | // Extendable for additional actions (e.g., notifications, system updates).
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | namespace {namespace};
13 |
14 | public class {itemname}DeletedEventHandler : INotificationHandler<{itemname}DeletedEvent>
15 | {
16 | private readonly ILogger<{itemname}DeletedEventHandler> _logger;
17 |
18 | public {itemname}DeletedEventHandler(
19 | ILogger<{itemname}DeletedEventHandler> logger
20 | )
21 | {
22 | _logger = logger;
23 | }
24 | public Task Handle({itemname}DeletedEvent notification, CancellationToken cancellationToken)
25 | {
26 | _logger.LogInformation("Handled domain event '{EventType}' with notification: {@Notification} ", notification.GetType().Name, notification);
27 | return Task.CompletedTask;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Templates/EventHandlers/.updated.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Handles {itemname}UpdatedEvent: triggered when a {itemnamelowercase} is updated.
7 | // Extendable for additional actions (e.g., notifications, system updates).
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | namespace {namespace};
13 |
14 | public class {itemname}UpdatedEventHandler : INotificationHandler<{itemname}UpdatedEvent>
15 | {
16 | private readonly ILogger<{itemname}UpdatedEventHandler> _logger;
17 |
18 | public {itemname}UpdatedEventHandler(
19 | ILogger<{itemname}UpdatedEventHandler> logger
20 | )
21 | {
22 | _logger = logger;
23 | }
24 | public Task Handle({itemname}UpdatedEvent notification, CancellationToken cancellationToken)
25 | {
26 | _logger.LogInformation("Handled domain event '{EventType}' with notification: {@Notification} ", notification.GetType().Name, notification);
27 | return Task.CompletedTask;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Templates/Events/.createdevent.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Domain event for when a new {itemnamelowercase} is created.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 |
11 | namespace {namespace};
12 |
13 | public class {itemname}CreatedEvent : DomainEvent
14 | {
15 | public {itemname}CreatedEvent({itemname} item)
16 | {
17 | Item = item;
18 | }
19 |
20 | public {itemname} Item { get; }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/Templates/Events/.deletedevent.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Domain event for when a {itemnamelowercase} is deleted.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 |
11 | namespace {namespace};
12 |
13 | public class {itemname}DeletedEvent : DomainEvent
14 | {
15 | public {itemname}DeletedEvent({itemname} item)
16 | {
17 | Item = item;
18 | }
19 |
20 | public {itemname} Item { get; }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/Templates/Events/.updatedevent.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Domain event for when a {itemnamelowercase} is updated.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 |
11 | namespace {namespace};
12 |
13 |
14 | public class {itemname}UpdatedEvent : DomainEvent
15 | {
16 | public {itemname}UpdatedEvent({itemname} item)
17 | {
18 | Item = item;
19 | }
20 |
21 | public {itemname} Item { get; }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/Templates/Mappers/.mapper.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under one or more agreements.
5 | // The .NET Foundation licenses this file to you under the MIT license.
6 | // See the LICENSE file in the project root for more information.
7 | //
8 | // Author: {author}
9 | // Created Date: {createddate}
10 | // Last Modified: {createddate}
11 | // Description:
12 | // Defines mapping methods between `{itemname}` entities and related DTOs/commands
13 | // within the CleanArchitecture.Blazor application. This mapper facilitates
14 | // conversions to support different operations, such as creating, updating,
15 | // and projecting {itemnamelowercase} data.
16 | //
17 | //------------------------------------------------------------------------------
18 |
19 | using {selectns}.{nameofPlural}.Commands.AddEdit;
20 | using {selectns}.{nameofPlural}.Commands.Create;
21 | using {selectns}.{nameofPlural}.Commands.Update;
22 | using {selectns}.{nameofPlural}.DTOs;
23 |
24 | namespace {namespace};
25 |
26 | #pragma warning disable RMG020
27 | #pragma warning disable RMG012
28 | [Mapper]
29 | public static partial class {itemname}Mapper
30 | {
31 | public static partial {itemname}Dto ToDto({itemname} source);
32 | public static partial {itemname} FromDto({itemname}Dto dto);
33 | public static partial {itemname} FromEditCommand(AddEdit{itemname}Command command);
34 | public static partial {itemname} FromCreateCommand(Create{itemname}Command command);
35 | public static partial Update{itemname}Command ToUpdateCommand({itemname}Dto dto);
36 | public static partial AddEdit{itemname}Command CloneFromDto({itemname}Dto dto);
37 | public static partial void ApplyChangesFrom(Update{itemname}Command source, {itemname} target);
38 | public static partial void ApplyChangesFrom(AddEdit{itemname}Command source, {itemname} target);
39 | public static partial IQueryable<{itemname}Dto> ProjectTo(this IQueryable<{itemname}> q);
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/Templates/Pages/.create.razor.txt:
--------------------------------------------------------------------------------
1 | @page "/pages/{nameofplurallowercase}/create"
2 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Commands.Create
3 |
4 | @inherits MudComponentBase
5 | @inject IValidationService Validator
6 | @inject IStringLocalizer<{nameofPlural}> L
7 | @attribute [Authorize(Policy = Permissions.{nameofPlural}.Create)]
8 |
9 | @Title
10 |
11 |
12 |
13 |
14 |
15 | @Title
16 |
17 |
18 |
19 |
20 |
21 | {mudFormFieldDefinition}
22 |
23 |
24 |
25 |
26 | @ConstantString.Save
27 |
28 |
29 |
30 |
31 |
32 | @code {
33 | public string? Title { get; private set; }
34 | MudForm _{itemnamelowercase}Form = new();
35 | private bool _saving = false;
36 | private List? _breadcrumbItems;
37 | private Create{itemname}Command _model = new();
38 | protected override Task OnInitializedAsync()
39 | {
40 | Title = L["New {itemname}"];
41 | _breadcrumbItems = new List
42 | {
43 | new BreadcrumbItem(L["Home"], href: "/"),
44 | new BreadcrumbItem(L["{nameofPlural}"], href: "/pages/{nameofplurallowercase}"),
45 | new BreadcrumbItem(L["Create {itemname}"], href:null, disabled:true)
46 | };
47 | return Task.CompletedTask;
48 | }
49 | async Task OnSubmit()
50 | {
51 | try
52 | {
53 | _saving = true;
54 | await _{itemnamelowercase}Form.Validate().ConfigureAwait(false);
55 | if (!_{itemnamelowercase}Form.IsValid)
56 | return;
57 | var result = await Mediator.Send(_model);
58 | result.Match(
59 | data=>
60 | {
61 | Snackbar.Add(ConstantString.SaveSuccess, MudBlazor.Severity.Info);
62 | Navigation.NavigateTo($"/pages/{nameofPlural}");
63 | },
64 | errors=>
65 | {
66 | Snackbar.Add(errors, MudBlazor.Severity.Error);
67 | });
68 | }
69 | finally
70 | {
71 | _saving = false;
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/src/Templates/Pages/.edit.razor.txt:
--------------------------------------------------------------------------------
1 | @page "/pages/{nameofplurallowercase}/edit/{id:int}"
2 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Commands.Update
3 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Queries.GetById
4 | @using CleanArchitecture.Blazor.Server.UI.Components.Fusion
5 |
6 | @inherits MudComponentBase
7 | @inject IValidationService Validator
8 | @inject IStringLocalizer<{nameofPlural}> L
9 | @attribute [Authorize(Policy = Permissions.{nameofPlural}.Edit)]
10 |
11 | @Title
12 |
13 |
14 | @if (_model != null)
15 | {
16 |
17 |
18 |
19 | @Title
20 |
21 |
22 |
23 |
24 |
25 |
26 | {mudFormFieldDefinition}
27 |
28 |
29 |
30 |
31 | @ConstantString.Save
32 |
33 |
34 | }
35 |
36 |
37 |
38 | @code {
39 | public string? Title { get; private set; }
40 | [Parameter]
41 | public int Id { get; set; }
42 | MudForm _{itemnamelowercase}Form = new();
43 | private bool _saving = false;
44 | private List? _breadcrumbItems;
45 | private Update{itemname}Command? _model;
46 | protected override async Task OnInitializedAsync()
47 | {
48 | Title = L["Edit {itemname}"];
49 | _breadcrumbItems = new List
50 | {
51 | new BreadcrumbItem(L["Home"], href: "/"),
52 | new BreadcrumbItem(L["{nameofPlural}"], href: "/pages/{nameofplurallowercase}")
53 | };
54 | var result = await Mediator.Send(new Get{itemname}ByIdQuery() { Id = Id });
55 | result.Map(data =>
56 | {
57 | _model = Mapper.Map(data);
58 | return data;
59 | }).Match(data =>
60 | {
61 | _breadcrumbItems.Add(new BreadcrumbItem(data.Name, href: $"/pages/{nameofplurallowercase}/edit/{Id}"));
62 | }, errors =>
63 | {
64 | Snackbar.Add($"{errors}", Severity.Error);
65 | });
66 |
67 | }
68 | async Task OnSubmit()
69 | {
70 | try
71 | {
72 | _saving = true;
73 | await _{itemnamelowercase}Form.Validate().ConfigureAwait(false);
74 | if (!_{itemnamelowercase}Form.IsValid)
75 | return;
76 | var result = await Mediator.Send(_model);
77 | result.Match(
78 | data=>
79 | {
80 | Snackbar.Add(ConstantString.SaveSuccess, MudBlazor.Severity.Info);
81 | },
82 | errors=>
83 | {
84 | Snackbar.Add(errors, MudBlazor.Severity.Error);
85 | });
86 | }
87 | finally
88 | {
89 | _saving = false;
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Templates/Pages/.razor.txt:
--------------------------------------------------------------------------------
1 | @page "/pages/{nameofplurallowercase}"
2 |
3 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Caching
4 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.DTOs
5 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Specifications
6 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Commands.Delete
7 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Commands.Import
8 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Queries.Export
9 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Queries.Pagination
10 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Commands.AddEdit
11 | @using CleanArchitecture.Blazor.Server.UI.Pages.{nameofPlural}.Components
12 |
13 | @inject IStringLocalizer<{nameofPlural}> L
14 | @inject BlazorDownloadFileService BlazorDownloadFileService
15 |
16 | @attribute [Authorize(Policy = Permissions.{nameofPlural}.View)]
17 | @Title
18 |
19 |
31 |
32 |
33 |
34 |
35 |
36 | @Title
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 | @ConstantString.Refresh
47 |
48 | @if (_accessRights.Create)
49 | {
50 |
52 | @ConstantString.New
53 |
54 | }
55 |
56 | @if (_accessRights.Create)
57 | {
58 | @ConstantString.Clone
59 | }
60 | @if (_accessRights.Delete)
61 | {
62 |
64 | @ConstantString.Delete
65 |
66 | }
67 | @if (_accessRights.Export)
68 | {
69 |
71 | @ConstantString.Export
72 |
73 | }
74 | @if (_accessRights.Import)
75 | {
76 |
77 |
78 |
79 |
82 | @ConstantString.Import
83 |
84 |
85 |
86 |
87 | }
88 |
89 |
90 |
91 | @if (_accessRights.Search)
92 | {
93 |
95 |
96 | }
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | @if (_accessRights.Edit || _accessRights.Delete)
106 | {
107 |
110 | @if (_accessRights.Edit)
111 | {
112 | @ConstantString.Edit
113 | }
114 | @if (_accessRights.Delete)
115 | {
116 | @ConstantString.Delete
117 | }
118 |
119 | }
120 | else
121 | {
122 |
127 | @ConstantString.NoAllowed
128 |
129 | }
130 |
131 |
132 | @*TODO: Define the fields that should be displayed in data table*@
133 | {mudTdHeaderDefinition}
134 |
135 |
136 | @ConstantString.NoRecords
137 |
138 |
139 | @ConstantString.Loading
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | @code {
149 | public string? Title { get; private set; }
150 | private int _defaultPageSize = 15;
151 | private HashSet<{itemname}Dto> _selected{nameofPlural} = new HashSet<{itemname}Dto>();
152 | private MudDataGrid<{itemname}Dto> _{nameofplurallowercase}Grid = default!;
153 | private {itemname}Dto _{itemnamelowercase}Dto = new();
154 | private bool _loading;
155 | private bool _uploading;
156 | private bool _exporting;
157 | [CascadingParameter]
158 | private Task AuthState { get; set; } = default!;
159 | [CascadingParameter]
160 | private UserProfile? UserProfile { get; set; }
161 |
162 |
163 | private {nameofPlural}WithPaginationQuery _{nameofplurallowercase}Query { get; set; } = new();
164 | private {nameofPlural}AccessRights _accessRights = new();
165 |
166 | protected override async Task OnInitializedAsync()
167 | {
168 | Title = L[_{itemnamelowercase}Dto.GetClassDescription()];
169 | _accessRights = await PermissionService.GetAccessRightsAsync<{nameofPlural}AccessRights>();
170 | }
171 |
172 | private async Task> ServerReload(GridState<{itemname}Dto> state)
173 | {
174 | try
175 | {
176 | _loading = true;
177 | _{nameofplurallowercase}Query.CurrentUser = UserProfile;
178 | var sortDefinition = state.SortDefinitions.FirstOrDefault();
179 | _{nameofplurallowercase}Query.OrderBy = sortDefinition?.SortBy ?? "Id";
180 | _{nameofplurallowercase}Query.SortDirection = (sortDefinition != null && sortDefinition.Descending)
181 | ? SortDirection.Descending.ToString()
182 | : SortDirection.Ascending.ToString();
183 | _{nameofplurallowercase}Query.PageNumber = state.Page + 1;
184 | _{nameofplurallowercase}Query.PageSize = state.PageSize;
185 | var result = await Mediator.Send(_{nameofplurallowercase}Query).ConfigureAwait(false);
186 | return new GridData<{itemname}Dto>() { TotalItems = result.TotalItems, Items = result.Items };
187 | }
188 | finally
189 | {
190 | _loading = false;
191 | }
192 |
193 | }
194 | private async Task OnSearch(string text)
195 | {
196 | _selected{nameofPlural}.Clear();
197 | _{nameofplurallowercase}Query.Keyword = text;
198 | await _{nameofplurallowercase}Grid.ReloadServerData();
199 | }
200 | private async Task OnListViewChanged({itemname}ListView listview)
201 | {
202 | _{nameofplurallowercase}Query.ListView = listview;
203 | await _{nameofplurallowercase}Grid.ReloadServerData();
204 | }
205 | private async Task OnRefresh()
206 | {
207 | {itemname}CacheKey.Refresh();
208 | _selected{nameofPlural}.Clear();
209 | _{nameofplurallowercase}Query.Keyword = string.Empty;
210 | await _{nameofplurallowercase}Grid.ReloadServerData();
211 | }
212 | private async Task ShowEditFormDialog(string title, AddEdit{itemname}Command command)
213 | {
214 | return DialogServiceHelper.ShowFormDialogAsync<{itemname}FormDialog, AddEdit{itemname}Command>(
215 | title,
216 | command,
217 | async () =>
218 | {
219 | await _{nameofplurallowercase}Grid.ReloadServerData();
220 | _selected{nameofPlural}.Clear();
221 | });
222 | }
223 | private void OnDataGridRowClick({itemname}Dto dto)
224 | {
225 | Navigation.NavigateTo($"/pages/{nameofplurallowercase}/view/{dto.Id}");
226 | }
227 | private Task OnCreate()
228 | {
229 | var command = new AddEdit{itemname}Command();
230 | return ShowEditFormDialog(L["New {itemname}"], command);
231 | }
232 | private Task OnClone{itemname}()
233 | {
234 | var dto = _selected{nameofPlural}.First();
235 | var command = new AddEdit{itemname}Command()
236 | {
237 | {fieldAssignmentDefinition}
238 | };
239 | return ShowEditFormDialog(L["Clone {itemname}"], command);
240 | }
241 | private Task OnEdit{itemname}({itemname}Dto dto)
242 | {
243 | //var command = Mapper.Map(dto);
244 | //return ShowEditFormDialog(L["Edit {itemname}"], command);
245 | Navigation.NavigateTo($"/pages/{nameofplurallowercase}/edit/{dto.Id}");
246 | return Task.CompletedTask;
247 | }
248 |
249 | private Task OnDelete{itemname}({itemname}Dto dto)
250 | {
251 | var contentText = string.Format(ConstantString.DeleteConfirmation, dto.Name);
252 | var command = new Delete{itemname}Command(new int[] { dto.Id });
253 | return Delete{nameofPlural}Internal(command, contentText);
254 | }
255 |
256 | private Task OnDeleteSelected{nameofPlural}()
257 | {
258 | var contentText = string.Format(ConstantString.DeleteConfirmWithSelected, _selected{nameofPlural}.Count);
259 | var command = new Delete{itemname}Command(_selected{nameofPlural}.Select(x => x.Id).ToArray());
260 | return Delete{nameofPlural}Internal(command, contentText);
261 | }
262 |
263 | private Task Delete{nameofPlural}Internal(Delete{itemname}Command command, string contentText)
264 | {
265 | return DialogServiceHelper.ShowDeleteConfirmationDialogAsync(
266 | command,
267 | ConstantString.DeleteConfirmationTitle,
268 | contentText,
269 | async () =>
270 | {
271 | await _{nameofplurallowercase}Grid.ReloadServerData();
272 | _selected{nameofPlural}.Clear();
273 | });
274 | }
275 |
276 |
277 | private async Task OnExport()
278 | {
279 | _exporting = true;
280 | var request = new Export{nameofPlural}Query()
281 | {
282 | Keyword = _{nameofplurallowercase}Query.Keyword,
283 | CurrentUser = UserProfile,
284 | ListView = _{nameofplurallowercase}Query.ListView,
285 | OrderBy = _{nameofplurallowercase}Grid.SortDefinitions.Values.FirstOrDefault()?.SortBy ?? "Id",
286 | SortDirection = (_{nameofplurallowercase}Grid.SortDefinitions.Values.FirstOrDefault()?.Descending ?? true) ? SortDirection.Descending.ToString() : SortDirection.Ascending.ToString()
287 | };
288 | var result = await Mediator.Send(request);
289 | await result.MatchAsync(
290 | async data =>
291 | {
292 | await BlazorDownloadFileService.DownloadFileAsync($"{L["{nameofPlural}"]}.xlsx", result.Data, contentType:"application/octet-stream");
293 | Snackbar.Add($"{ConstantString.ExportSuccess}", MudBlazor.Severity.Info);
294 | },
295 | errors =>
296 | {
297 | Snackbar.Add($"{errors}", MudBlazor.Severity.Error);
298 | return Task.CompletedTask;
299 | });
300 | _exporting = false;
301 | }
302 | private async Task OnImportData(IBrowserFile file)
303 | {
304 | _uploading = true;
305 | var stream = new MemoryStream();
306 | await file.OpenReadStream().CopyToAsync(stream);
307 | var command = new Import{nameofPlural}Command(file.Name, stream.ToArray());
308 | var result = await Mediator.Send(command);
309 | await result.MatchAsync(
310 | async data =>
311 | {
312 | await _{nameofplurallowercase}Grid.ReloadServerData();
313 | Snackbar.Add($"{ConstantString.ImportSuccess}", MudBlazor.Severity.Info);
314 | }, errors =>
315 | {
316 | Snackbar.Add($"{errors}", MudBlazor.Severity.Error);
317 | return Task.CompletedTask;
318 | });
319 | _uploading = false;
320 | }
321 |
322 | }
323 |
--------------------------------------------------------------------------------
/src/Templates/Pages/.view.razor.txt:
--------------------------------------------------------------------------------
1 | @page "/pages/{nameofplurallowercase}/view/{id:int}"
2 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Commands.Delete
3 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.DTOs
4 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Queries.GetById
5 | @inherits MudComponentBase
6 | @inject IStringLocalizer<{nameofPlural}> L
7 | @attribute [Authorize(Policy = Permissions.{nameofPlural}.View)]
8 | @Title
9 |
10 |
11 | @if (_model != null)
12 | {
13 |
14 |
15 |
16 | @Title
17 |
18 |
19 |
20 |
21 | {readonlyFieldDefinition}
22 |
23 |
24 |
25 |
26 | }
27 |
28 |
29 |
30 | @code {
31 | public string? Title { get; private set; }
32 | [Parameter]
33 | public int Id { get; set; }
34 | private List? _breadcrumbItems;
35 | private {itemname}Dto? _model;
36 | protected override async Task OnInitializedAsync()
37 | {
38 | Title = L["{itemname}"];
39 | _breadcrumbItems = new List
40 | {
41 | new BreadcrumbItem(L["Home"], href: "/"),
42 | new BreadcrumbItem(L["{nameofPlural}"], href: "/pages/{nameofplurallowercase}")
43 | };
44 | var result = await Mediator.Send(new Get{itemname}ByIdQuery() { Id = Id });
45 | result.Map(data =>
46 | {
47 | _model = data;
48 | return data;
49 | }).Match(data =>
50 | {
51 | _breadcrumbItems.Add(new BreadcrumbItem(data.Name, null, disabled: true));
52 | }, errors =>
53 | {
54 | Snackbar.Add(errors, MudBlazor.Severity.Error);
55 | });
56 |
57 | }
58 | void GoEdit()
59 | {
60 | Navigation.NavigateTo($"/pages/{nameofPlural}/edit/{Id}");
61 | }
62 | async Task Delete()
63 | {
64 | var contentText = string.Format(ConstantString.DeleteConfirmation, _model.Name);
65 | var command = new Delete{itemname}Command(new int[] { _model.Id });
66 | await DialogServiceHelper.ShowDeleteConfirmationDialogAsync(command, ConstantString.DeleteConfirmationTitle, contentText, async () =>
67 | {
68 | await InvokeAsync(() =>
69 | {
70 | Navigation.NavigateTo($"/pages/{nameofPlural}");
71 | });
72 | });
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Templates/Pages/Components/.advancedsearchcomponent.razor.txt:
--------------------------------------------------------------------------------
1 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Queries.Pagination
2 | @inject IStringLocalizer<{nameofPlural}> L
3 |
4 |
6 |
7 |
8 |
9 | @*
13 | *@
14 |
15 |
16 |
17 |
18 | @code {
19 | [EditorRequired][Parameter] public {nameofPlural}WithPaginationQuery {nameofPlural}Query { get; set; } = null!;
20 | [EditorRequired][Parameter] public EventCallback OnConditionChanged { get; set; }
21 | private bool _advancedSearchExpanded;
22 | private async Task TextChanged(string str)
23 | {
24 | if (_advancedSearchExpanded)
25 | {
26 | await OnConditionChanged.InvokeAsync(str);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Templates/Pages/Components/.formdialog.razor.txt:
--------------------------------------------------------------------------------
1 | @using CleanArchitecture.Blazor.Application.Features.{nameofPlural}.Commands.AddEdit
2 |
3 | @inherits MudComponentBase
4 | @inject IValidationService Validator
5 | @inject IStringLocalizer<{nameofPlural}> L
6 |
7 |
8 |
9 |
10 |
11 | @*TODO: define mudform that should be edit fields, for example:*@
12 | {mudFormFieldDefinition}
13 |
14 |
15 |
16 |
17 | @ConstantString.Cancel
18 | @ConstantString.SaveAndNew
19 | @ConstantString.Save
20 |
21 |
22 |
23 | @code {
24 | MudForm _{itemnamelowercase}Form = new();
25 | private bool _saving = false;
26 | private bool _savingnew = false;
27 | [CascadingParameter]
28 | IMudDialogInstance MudDialog { get; set; } = default!;
29 | [EditorRequired] [Parameter] public AddEdit{itemname}Command _model { get; set; } = null!;
30 | async Task OnSubmit()
31 | {
32 | try
33 | {
34 | _saving = true;
35 | await _{itemnamelowercase}Form.Validate().ConfigureAwait(false);
36 | if (!_{itemnamelowercase}Form.IsValid)
37 | return;
38 | var result = await Mediator.Send(_model);
39 | result.Match(data =>
40 | {
41 | MudDialog.Close(DialogResult.Ok(true));
42 | Snackbar.Add(ConstantString.SaveSuccess, MudBlazor.Severity.Info);
43 | }, errors =>
44 | {
45 | Snackbar.Add(errors, MudBlazor.Severity.Error);
46 | });
47 | }
48 | finally
49 | {
50 | _saving = false;
51 | }
52 | }
53 | async Task OnSaveAndNew()
54 | {
55 | try
56 | {
57 | _savingnew = true;
58 | await _{itemnamelowercase}Form.Validate().ConfigureAwait(false);
59 | if (!_{itemnamelowercase}Form.IsValid)
60 | return;
61 | var result = await Mediator.Send(_model);
62 | await result.MatchAsync(async data =>
63 | {
64 | Snackbar.Add(ConstantString.SaveSuccess, MudBlazor.Severity.Info);
65 | await Task.Delay(300);
66 | _model = new AddEdit{itemname}Command() { };
67 | }, errors =>
68 | {
69 | Snackbar.Add(errors, MudBlazor.Severity.Error);
70 | return Task.CompletedTask;
71 | });
72 | }
73 | finally
74 | {
75 | _savingnew = false;
76 | }
77 | }
78 | void Cancel() => MudDialog.Cancel();
79 | }
--------------------------------------------------------------------------------
/src/Templates/PermissionSet/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // CleanArchitecture.Blazor - MIT Licensed.
4 | // Author: {author}
5 | // Created/Modified: {createddate}
6 | // Defines permission constants for {itemnamelowercase} operations (view, create, edit, delete, etc.).
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 |
11 | using System.ComponentModel;
12 |
13 | namespace {namespace};
14 |
15 | public static partial class Permissions
16 | {
17 | [DisplayName("{itemname} Permissions")]
18 | [Description("Set permissions for {itemnamelowercase} operations.")]
19 | public static class {nameofPlural}
20 | {
21 | [Description("Allows viewing {itemnamelowercase} details.")]
22 | public const string View = "Permissions.{nameofPlural}.View";
23 | [Description("Allows creating {itemnamelowercase} records.")]
24 | public const string Create = "Permissions.{nameofPlural}.Create";
25 | [Description("Allows modifying existing {itemnamelowercase} details.")]
26 | public const string Edit = "Permissions.{nameofPlural}.Edit";
27 | [Description("Allows deleting {itemnamelowercase} records.")]
28 | public const string Delete = "Permissions.{nameofPlural}.Delete";
29 | [Description("Allows printing {itemnamelowercase} details.")]
30 | public const string Print = "Permissions.{nameofPlural}.Print";
31 | [Description("Allows searching {itemnamelowercase} records.")]
32 | public const string Search = "Permissions.{nameofPlural}.Search";
33 | [Description("Allows exporting {itemnamelowercase} records.")]
34 | public const string Export = "Permissions.{nameofPlural}.Export";
35 | [Description("Allows importing {itemnamelowercase} records.")]
36 | public const string Import = "Permissions.{nameofPlural}.Import";
37 | }
38 | }
39 |
40 | public class {nameofPlural}AccessRights
41 | {
42 | public bool View { get; set; }
43 | public bool Create { get; set; }
44 | public bool Edit { get; set; }
45 | public bool Delete { get; set; }
46 | public bool Print { get; set; }
47 | public bool Search { get; set; }
48 | public bool Export { get; set; }
49 | public bool Import { get; set; }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Templates/Persistence/Configurations/.configuration.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Configures the properties and behaviors for the `{itemname}` entity in the
12 | // database. Specifies property constraints such as maximum length and required fields.
13 | //
14 | //------------------------------------------------------------------------------
15 |
16 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
17 |
18 | namespace {namespace};
19 |
20 | #nullable disable
21 | public class {itemname}Configuration : IEntityTypeConfiguration<{itemname}>
22 | {
23 | public void Configure(EntityTypeBuilder<{itemname}> builder)
24 | {
25 | {entityTypeBuilderConfirmation}
26 | builder.Ignore(e => e.DomainEvents);
27 | }
28 | }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Templates/Queries/Export/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines a query to export {itemnamelowercase} data to an Excel file. This query
12 | // applies advanced filtering options and generates an Excel file with
13 | // the specified {itemnamelowercase} details.
14 | //
15 | //------------------------------------------------------------------------------
16 | #nullable enable
17 | #nullable disable warnings
18 |
19 | using {selectns}.{nameofPlural}.DTOs;
20 | using {selectns}.{nameofPlural}.Caching;
21 | using {selectns}.{nameofPlural}.Specifications;
22 |
23 | namespace {namespace};
24 |
25 | public class Export{nameofPlural}Query : {itemname}AdvancedFilter, ICacheableRequest>
26 | {
27 | public {itemname}AdvancedSpecification Specification => new {itemname}AdvancedSpecification(this);
28 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
29 | public override string ToString()
30 | {
31 | return $"Listview:{ListView}:{CurrentUser?.UserId}, Search:{Keyword}, {OrderBy}, {SortDirection}";
32 | }
33 | public string CacheKey => {itemname}CacheKey.GetExportCacheKey($"{this}");
34 | }
35 |
36 | public class Export{nameofPlural}QueryHandler :
37 | IRequestHandler>
38 | {
39 | private readonly IMapper _mapper;
40 | private readonly IApplicationDbContext _context;
41 | private readonly IExcelService _excelService;
42 | private readonly IStringLocalizer _localizer;
43 | private readonly {itemname}Dto _dto = new();
44 | public Export{nameofPlural}QueryHandler(
45 | IMapper mapper,
46 | IApplicationDbContext context,
47 | IExcelService excelService,
48 | IStringLocalizer localizer
49 | )
50 | {
51 | _mapper = mapper;
52 | _context = context;
53 | _excelService = excelService;
54 | _localizer = localizer;
55 | }
56 | #nullable disable warnings
57 | public async Task> Handle(Export{nameofPlural}Query request, CancellationToken cancellationToken)
58 | {
59 | var data = await _context.{nameofPlural}.ApplySpecification(request.Specification)
60 | .OrderBy($"{request.OrderBy} {request.SortDirection}")
61 | .ProjectTo<{itemname}Dto>(_mapper.ConfigurationProvider)
62 | .AsNoTracking()
63 | .ToListAsync(cancellationToken);
64 | var result = await _excelService.ExportAsync(data,
65 | new Dictionary>()
66 | {
67 | // TODO: Define the fields that should be exported, for example:
68 | {exportFuncExpression}
69 | }
70 | , _localizer[_dto.GetClassDescription()]);
71 | return await Result.SuccessAsync(result);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Templates/Queries/GetAll/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines a query to retrieve all {itemnamelowercase}s from the database. The result
12 | // is cached to improve performance and reduce database load for repeated
13 | // queries.
14 | //
15 | //------------------------------------------------------------------------------
16 | #nullable enable
17 | #nullable disable warnings
18 |
19 | using {selectns}.{nameofPlural}.DTOs;
20 | using {selectns}.{nameofPlural}.Caching;
21 |
22 | namespace {namespace};
23 |
24 | public class GetAll{nameofPlural}Query : ICacheableRequest>
25 | {
26 | public string CacheKey => {itemname}CacheKey.GetAllCacheKey;
27 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
28 | }
29 |
30 | public class GetAll{nameofPlural}QueryHandler :
31 | IRequestHandler>
32 | {
33 | private readonly IApplicationDbContext _context;
34 | private readonly IMapper _mapper;
35 | public GetAll{nameofPlural}QueryHandler(
36 | IMapper mapper,
37 | IApplicationDbContext context)
38 | {
39 | _mapper = mapper;
40 | _context = context;
41 | }
42 |
43 | public async Task> Handle(GetAll{nameofPlural}Query request, CancellationToken cancellationToken)
44 | {
45 | var data = await _context.{nameofPlural}.ProjectTo<{itemname}Dto>(_mapper.ConfigurationProvider)
46 | .AsNoTracking()
47 | .ToListAsync(cancellationToken);
48 | return data;
49 | }
50 | }
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/Templates/Queries/GetById/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines a query to retrieve a {itemnamelowercase} by its ID. The result is cached
12 | // to optimize performance for repeated retrievals of the same {itemnamelowercase}.
13 | //
14 | //------------------------------------------------------------------------------
15 | #nullable enable
16 | #nullable disable warnings
17 |
18 | using {selectns}.{nameofPlural}.DTOs;
19 | using {selectns}.{nameofPlural}.Caching;
20 | using {selectns}.{nameofPlural}.Specifications;
21 |
22 | namespace {namespace};
23 |
24 | public class Get{itemname}ByIdQuery : ICacheableRequest>
25 | {
26 | public required int Id { get; set; }
27 | public string CacheKey => {itemname}CacheKey.GetByIdCacheKey($"{Id}");
28 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
29 | }
30 |
31 | public class Get{itemname}ByIdQueryHandler :
32 | IRequestHandler>
33 | {
34 | private readonly IApplicationDbContext _context;
35 | private readonly IMapper _mapper;
36 | public Get{itemname}ByIdQueryHandler(
37 | IMapper mapper,
38 | IApplicationDbContext context)
39 | {
40 | _mapper = mapper;
41 | _context = context;
42 | }
43 |
44 | public async Task> Handle(Get{itemname}ByIdQuery request, CancellationToken cancellationToken)
45 | {
46 | var data = await _context.{nameofPlural}.ApplySpecification(new {itemname}ByIdSpecification(request.Id))
47 | .ProjectTo<{itemname}Dto>(_mapper.ConfigurationProvider)
48 | .FirstAsync(cancellationToken) ?? throw new NotFoundException($"{itemname} with id: [{request.Id}] not found.");
49 | return await Result<{itemname}Dto>.SuccessAsync(data);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Templates/Queries/Pagination/.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines a query for retrieving {itemnamelowercase}s with pagination and filtering
12 | // options. The result is cached to enhance performance for repeated queries.
13 | //
14 | //------------------------------------------------------------------------------
15 | #nullable enable
16 | #nullable disable warnings
17 |
18 | using {selectns}.{nameofPlural}.DTOs;
19 | using {selectns}.{nameofPlural}.Caching;
20 | using {selectns}.{nameofPlural}.Specifications;
21 |
22 | namespace {namespace};
23 |
24 | public class {nameofPlural}WithPaginationQuery : {itemname}AdvancedFilter, ICacheableRequest>
25 | {
26 | public override string ToString()
27 | {
28 | return $"Listview:{ListView}:{CurrentUser?.UserId}, Search:{Keyword}, {OrderBy}, {SortDirection}, {PageNumber}, {PageSize}";
29 | }
30 | public string CacheKey => {itemname}CacheKey.GetPaginationCacheKey($"{this}");
31 | public IEnumerable? Tags => {itemname}CacheKey.Tags;
32 | public {itemname}AdvancedSpecification Specification => new {itemname}AdvancedSpecification(this);
33 | }
34 |
35 | public class {nameofPlural}WithPaginationQueryHandler :
36 | IRequestHandler<{nameofPlural}WithPaginationQuery, PaginatedData<{itemname}Dto>>
37 | {
38 | private readonly IApplicationDbContext _context;
39 | private readonly IMapper _mapper;
40 | public {nameofPlural}WithPaginationQueryHandler(
41 | IMapper mapper,
42 | IApplicationDbContext context)
43 | {
44 | _mapper = mapper;
45 | _context = context;
46 | }
47 |
48 | public async Task> Handle({nameofPlural}WithPaginationQuery request, CancellationToken cancellationToken)
49 | {
50 | var data = await _context.{nameofPlural}.OrderBy($"{request.OrderBy} {request.SortDirection}")
51 | .ProjectToPaginatedDataAsync<{itemname}, {itemname}Dto>(request.Specification,
52 | request.PageNumber,
53 | request.PageSize,
54 | _mapper.ConfigurationProvider,
55 | cancellationToken);
56 | return data;
57 | }
58 | }
--------------------------------------------------------------------------------
/src/Templates/Specifications/AdvancedFilter.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines the available views for filtering {itemnamelowercase}s and provides advanced
12 | // filtering options for {itemnamelowercase} lists. This includes pagination and various
13 | // filters such as view types and user-specific filters.
14 | //
15 | //------------------------------------------------------------------------------
16 | #nullable enable
17 | #nullable disable warnings
18 |
19 | namespace {namespace};
20 |
21 | #nullable disable warnings
22 | ///
23 | /// Specifies the different views available for the {itemname} list.
24 | ///
25 | public enum {itemname}ListView
26 | {
27 | [Description("All")]
28 | All,
29 | [Description("My")]
30 | My,
31 | [Description("Created Toady")]
32 | TODAY,
33 | [Description("Created within the last 30 days")]
34 | LAST_30_DAYS
35 | }
36 | ///
37 | /// A class for applying advanced filtering options to {itemname} lists.
38 | ///
39 | public class {itemname}AdvancedFilter: PaginationFilter
40 | {
41 | public {itemname}ListView ListView { get; set; } = {itemname}ListView.All;
42 | public UserProfile? CurrentUser { get; set; }
43 | }
--------------------------------------------------------------------------------
/src/Templates/Specifications/AdvancedSpecification.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines a specification for applying advanced filtering options to the
12 | // {itemname} entity, supporting different views and keyword-based searches.
13 | //
14 | //------------------------------------------------------------------------------
15 | #nullable enable
16 | #nullable disable warnings
17 |
18 | namespace {namespace};
19 | #nullable disable warnings
20 | ///
21 | /// Specification class for advanced filtering of {nameofPlural}.
22 | ///
23 | public class {itemname}AdvancedSpecification : Specification<{itemname}>
24 | {
25 | public {itemname}AdvancedSpecification({itemname}AdvancedFilter filter)
26 | {
27 | DateTime today = DateTime.UtcNow;
28 | var todayrange = today.GetDateRange({itemname}ListView.TODAY.ToString(), filter.CurrentUser.LocalTimeOffset);
29 | var last30daysrange = today.GetDateRange({itemname}ListView.LAST_30_DAYS.ToString(),filter.CurrentUser.LocalTimeOffset);
30 |
31 | Query.Where(q => q.Name != null)
32 | .Where(filter.Keyword,!string.IsNullOrEmpty(filter.Keyword))
33 | .Where(q => q.CreatedBy == filter.CurrentUser.UserId, filter.ListView == {itemname}ListView.My && filter.CurrentUser is not null)
34 | .Where(x => x.Created >= todayrange.Start && x.Created < todayrange.End.AddDays(1), filter.ListView == {itemname}ListView.TODAY)
35 | .Where(x => x.Created >= last30daysrange.Start, filter.ListView == {itemname}ListView.LAST_30_DAYS);
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Templates/Specifications/ByIdSpecification.cs.txt:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This file is part of the CleanArchitecture.Blazor project.
4 | // Licensed to the .NET Foundation under the MIT license.
5 | // See the LICENSE file in the project root for more information.
6 | //
7 | // Author: {author}
8 | // Created Date: {createddate}
9 | // Last Modified: {createddate}
10 | // Description:
11 | // Defines a specification for filtering a {itemname} entity by its ID.
12 | //
13 | //------------------------------------------------------------------------------
14 | #nullable enable
15 | #nullable disable warnings
16 |
17 | namespace {namespace};
18 | #nullable disable warnings
19 | ///
20 | /// Specification class for filtering {nameofPlural} by their ID.
21 | ///
22 | public class {itemname}ByIdSpecification : Specification<{itemname}>
23 | {
24 | public {itemname}ByIdSpecification(int id)
25 | {
26 | Query.Where(q => q.Id == id);
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Templates/bower.json.txt:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mypackage",
3 | "private": true,
4 | "dependencies": {
5 | $
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Templates/gruntfile.js.txt:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.initConfig({
3 | $
4 | });
5 | };
--------------------------------------------------------------------------------
/src/Templates/gulpfile.js.txt:
--------------------------------------------------------------------------------
1 | var gulp = require("gulp");
2 |
3 | $
--------------------------------------------------------------------------------
/src/Templates/package.json.txt:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "name": "mypackage",
4 | "private": true,
5 | "devDependencies": {
6 | $
7 | }
8 | }
--------------------------------------------------------------------------------
/src/source.extension.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | //
3 | // This file was generated by VSIX Synchronizer
4 | //
5 | // ------------------------------------------------------------------------------
6 | namespace CleanArchitecture.CodeGenerator
7 | {
8 | internal sealed partial class Vsix
9 | {
10 | public const string Id = "CleanArchitecture_CodeGenerator_BlazorApp";
11 | public const string Name = "CleanArchitecture CodeGenerator For Blazor App";
12 | public const string Description = @"The fastest and easiest way to generate application features code that up to clean architecture principles for Blazor Server Application";
13 | public const string Language = "en-US";
14 | public const string Version = "0.1";
15 | public const string Author = "neozhu";
16 | public const string Tags = "templat,CleanArchitecture,CodeGenerator";
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CleanArchitecture CodeGenerator For Blazor App
6 | The fastest and easiest way to generate application features code that up to clean architecture principles for Blazor Server Application
7 | https://marketplace.visualstudio.com/items?itemName=CleanArchitecture.CodeGenerator
8 | Resources\LICENSE
9 | https://raw.githubusercontent.com/neozhu/CleanCleanArchitectureCodeGenerator/main/README.md
10 | Resources\logo.png
11 | Resources\logo.png
12 | template, CleanArchitecture, CodeGenerator
13 | true
14 |
15 |
16 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/vs-publish.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/vsix-publish",
3 | "categories": [ "other", "coding" ],
4 | "identity": {
5 | "internalName": "247365"
6 | },
7 | "assetFiles": [
8 | {
9 | "pathOnDisk": "art/dialog.png",
10 | "targetPath": "art/dialog.png"
11 | },
12 | {
13 | "pathOnDisk": "art/menu.png",
14 | "targetPath": "art/menu.png"
15 | }
16 | ],
17 | "overview": "README.md",
18 | "publisher": "neozhu",
19 | "repo": "https://github.com/neozhu/CleanArchitectureCodeGenerator"
20 | }
21 |
--------------------------------------------------------------------------------