├── .gitignore
├── LICENSE.md
├── Logo.png
├── README.md
└── SignalRSwaggerGen
├── .editorconfig
├── SignalRSwaggerGen.sln
├── SignalRSwaggerGen
├── Attributes
│ ├── SignalRHiddenAttribute.cs
│ ├── SignalRHubAttribute.cs
│ ├── SignalRMethodAttribute.cs
│ ├── SignalRParamAttribute.cs
│ ├── SignalRRequestBodyAttribute.cs
│ └── SignalRReturnAttribute.cs
├── Constants.cs
├── Enums
│ ├── AutoDiscover.cs
│ └── Operation.cs
├── Naming
│ ├── NameTransformer.cs
│ ├── ToCamelTransformer.cs
│ ├── ToLowerTransformer.cs
│ └── ToUpperTransformer.cs
├── SignalRSwaggerGen.cs
├── SignalRSwaggerGen.csproj
├── SignalRSwaggerGen.xml
├── SignalRSwaggerGenOptions.cs
├── SwaggerGenOptionsExtensions.cs
└── Utils
│ ├── Comparison
│ └── SignalRReturnAttributeComparer.cs
│ ├── EnumerableUtils.cs
│ ├── ReflectionUtils.cs
│ ├── StringUtils.cs
│ └── XmlComments
│ ├── XmlComments.cs
│ └── XmlCommentsUtils.cs
└── TestWebApi
├── Controllers
└── TestController.cs
├── Hubs
├── StronglyTypedTestHub.cs
├── TestHub.cs
└── XmlCommentsHub.cs
├── Program.cs
├── Properties
└── launchSettings.json
├── Startup.cs
├── TestWebApi.csproj
├── TestWebApi.xml
├── WeatherForecast.cs
├── appsettings.Development.json
└── appsettings.json
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Dorin Mocan
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 |
--------------------------------------------------------------------------------
/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/essencebit/SignalRSwaggerGen/ff9c7fe94d8027dd5c1607d26c2f05611a967209/Logo.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Go to wiki: https://github.com/essencebit/SignalRSwaggerGen/wiki
2 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CS1591: Missing XML comment for publicly visible type or member
4 | dotnet_diagnostic.CS1591.severity = silent
5 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31306.274
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SignalRSwaggerGen", "SignalRSwaggerGen\SignalRSwaggerGen.csproj", "{7CB27570-0187-4ED6-AC83-81D79C6E89F7}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F50A6213-4D74-41A0-B4F2-6913378CE896}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | EndProjectSection
12 | EndProject
13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApi", "TestWebApi\TestWebApi.csproj", "{F75822CA-1FEF-45B9-8D63-B3F8789448C3}"
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {7CB27570-0187-4ED6-AC83-81D79C6E89F7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
22 | {7CB27570-0187-4ED6-AC83-81D79C6E89F7}.Debug|Any CPU.Build.0 = Release|Any CPU
23 | {7CB27570-0187-4ED6-AC83-81D79C6E89F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {7CB27570-0187-4ED6-AC83-81D79C6E89F7}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {F75822CA-1FEF-45B9-8D63-B3F8789448C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {F75822CA-1FEF-45B9-8D63-B3F8789448C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {F75822CA-1FEF-45B9-8D63-B3F8789448C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {F75822CA-1FEF-45B9-8D63-B3F8789448C3}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {A2754AA3-AF10-4DF9-88EB-49F150583FD3}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Attributes/SignalRHiddenAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SignalRSwaggerGen.Attributes
4 | {
5 | ///
6 | /// Use this attribute to disable Swagger documentation for specific components
7 | ///
8 | [AttributeUsage(
9 | AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct
10 | | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.ReturnValue,
11 | AllowMultiple = false, Inherited = false)]
12 | public sealed class SignalRHiddenAttribute : Attribute
13 | {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Attributes/SignalRHubAttribute.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Enums;
2 | using SignalRSwaggerGen.Naming;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | namespace SignalRSwaggerGen.Attributes
8 | {
9 | ///
10 | /// Use this attribute to enable Swagger documentation for hubs
11 | ///
12 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
13 | public sealed class SignalRHubAttribute : Attribute
14 | {
15 | public string Path { get; }
16 | public AutoDiscover AutoDiscover { get; }
17 | public IEnumerable DocumentNames { get; }
18 | public NameTransformer NameTransformer { get; }
19 | public string Tag { get; }
20 | public bool XmlCommentsDisabled { get; }
21 |
22 | /// Path of the hub. If path contains "[Hub]", this part will be replaced with the name of the type holding this attribute(hub name).
23 | /// If not specified, the func from 'SignalRSwaggerGenOptions' will be used to get the path.
24 | /// A flag indicating what components will have Swagger documentation enabled automatically.
25 | /// If 'AutoDiscover.Inherit' specified, the value from 'SignalRSwaggerGenOptions' will be used.
26 | /// An array of names of the Swagger documents the hub will be displayed in.
27 | /// If null specified, then the value from 'SignalRSwaggerGenOptions' will be used. If empty array specified, then the hub will be displayed in all documents.
28 | /// The type of the name transformer. The type must inherit from 'SignalRSwaggerGen.Naming.NameTransformer' class, be non-abstract and have public parameterless constructor.
29 | /// The name transformer will be used to transform the name of the hub and its methods. If null specified, the transformer from 'SignalRSwaggerGenOptions' will be used.
30 | /// The namespace 'SignalRSwaggerGen.Naming' already contains some predefined name transformers, so check 'em out.
31 | /// The tag under which the hub will be placed in Swagger doc. If null specified, the summary section of the XML comments of the hub will be used.
32 | /// If XML comments missing or not enabled, the name of the type holding this attribute will be used.
33 | /// A flag indicating if XML comments are disabled for the hub
34 | /// Thrown if
35 | /// - value not allowed for this attribute
36 | /// - is abstract or does not inherit from 'SignalRSwaggerGen.Naming.NameTransformer' class or has no public parameterless constructor
37 | public SignalRHubAttribute(
38 | string path = null,
39 | AutoDiscover autoDiscover = AutoDiscover.Inherit,
40 | string[] documentNames = null,
41 | Type nameTransformerType = null,
42 | string tag = null,
43 | bool xmlCommentsDisabled = false)
44 | {
45 | if (!_validAutoDiscoverValues.Contains(autoDiscover)) throw new ArgumentException($"Value {autoDiscover} not allowed for this attribute", nameof(autoDiscover));
46 | ValidateNameTransformerType(nameTransformerType);
47 | Path = path;
48 | AutoDiscover = autoDiscover;
49 | DocumentNames = documentNames;
50 | NameTransformer = nameTransformerType == null ? null : (NameTransformer)Activator.CreateInstance(nameTransformerType);
51 | Tag = tag;
52 | XmlCommentsDisabled = xmlCommentsDisabled;
53 | }
54 |
55 | private static void ValidateNameTransformerType(Type nameTransformerType)
56 | {
57 | if (nameTransformerType == null) return;
58 | if (nameTransformerType.IsAbstract) throw new ArgumentException($"Type {nameTransformerType.Name} is abstract", nameof(nameTransformerType));
59 | if (!nameTransformerType.IsSubclassOf(typeof(NameTransformer))) throw new ArgumentException($"Type {nameTransformerType.Name} is not a subtype of {typeof(NameTransformer).Name}", nameof(nameTransformerType));
60 | if (nameTransformerType.GetConstructor(Type.EmptyTypes) == null) throw new ArgumentException($"Type {nameTransformerType.Name} has no public parameterless constructor", nameof(nameTransformerType));
61 | }
62 |
63 | private static readonly IEnumerable _validAutoDiscoverValues = new List
64 | {
65 | AutoDiscover.Inherit,
66 | AutoDiscover.None,
67 | AutoDiscover.Methods,
68 | AutoDiscover.Params,
69 | AutoDiscover.MethodsAndParams,
70 | };
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Attributes/SignalRMethodAttribute.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Enums;
2 | using SignalRSwaggerGen.Utils;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | namespace SignalRSwaggerGen.Attributes
8 | {
9 | ///
10 | /// Use this attribute to enable Swagger documentation for hub methods
11 | ///
12 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
13 | public sealed class SignalRMethodAttribute : Attribute
14 | {
15 | public string Name { get; }
16 | public Operation Operation { get; }
17 | public AutoDiscover AutoDiscover { get; }
18 | public string Summary { get; }
19 | public string Description { get; }
20 |
21 | /// Name of the method which will be invoked on the client side.
22 | /// If name contains "[Method]", this part will be replaced with the name of the method holding this attribute.
23 | /// Same as HTTP verb. If 'Operation.Inherit' specified, then the value from 'SignalRSwaggerGenOptions' will be used.
24 | /// A flag indicating what components will have Swagger documentation enabled automatically.
25 | /// If 'AutoDiscover.Inherit' specified, the value from the hub will be used. If the hub also has this value specified, then the value from 'SignalRSwaggerGenOptions' will be used.
26 | /// The text that will appear in summary section of the decorated method in Swagger document.
27 | /// If null specified and XML comments not disabled, the summary section of the XML comments of the method will be used.
28 | /// The text that will appear in description section of decorated method in Swagger document
29 | /// Thrown if
30 | /// - is null or empty
31 | /// - value not allowed for this attribute
32 | public SignalRMethodAttribute(
33 | string name = Constants.MethodNamePlaceholder,
34 | Operation operation = Operation.Inherit,
35 | AutoDiscover autoDiscover = AutoDiscover.Inherit,
36 | string summary = null,
37 | string description = null)
38 | {
39 | if (name.IsNullOrEmpty()) throw new ArgumentException("Name is null or empty", nameof(name));
40 | if (!_validAutoDiscoverValues.Contains(autoDiscover)) throw new ArgumentException($"Value {autoDiscover} not allowed for this attribute", nameof(autoDiscover));
41 | Name = name;
42 | Operation = operation;
43 | AutoDiscover = autoDiscover;
44 | Summary = summary;
45 | Description = description;
46 | }
47 |
48 | private static readonly IEnumerable _validAutoDiscoverValues = new List
49 | {
50 | AutoDiscover.Inherit,
51 | AutoDiscover.None,
52 | AutoDiscover.Params,
53 | };
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Attributes/SignalRParamAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SignalRSwaggerGen.Attributes
4 | {
5 | ///
6 | /// Use this attribute to enable Swagger documentation for method params
7 | ///
8 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
9 | public sealed class SignalRParamAttribute : Attribute
10 | {
11 | public string Description { get; }
12 | public Type ParamType { get; }
13 |
14 | /// The text that will appear in description section of decorated parameter in Swagger document
15 | /// Parameter type. If null specified, the type of the parameter holding this attribute will be picked up.
16 | public SignalRParamAttribute(string description = null, Type paramType = null)
17 | {
18 | Description = description;
19 | ParamType = paramType;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Attributes/SignalRRequestBodyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SignalRSwaggerGen.Attributes
4 | {
5 | ///
6 | /// Use this attribute to enable Swagger documentation for request body
7 | ///
8 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
9 | public class SignalRRequestBodyAttribute : Attribute
10 | {
11 | public Type BodyType { get; }
12 | public bool IsRequired { get; }
13 | public string Description { get; }
14 |
15 | /// The type of the request body
16 | /// The value that indicates if the request body is required or not
17 | /// The text that will appear in description section of the corresponding request body in Swagger document
18 | /// Thrown if is null
19 | public SignalRRequestBodyAttribute(Type bodyType, bool isRequired = false, string description = null)
20 | {
21 | BodyType = bodyType ?? throw new ArgumentNullException(nameof(bodyType));
22 | IsRequired = isRequired;
23 | Description = description;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Attributes/SignalRReturnAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SignalRSwaggerGen.Attributes
4 | {
5 | ///
6 | /// Use this attribute to enable Swagger documentation for method return type
7 | ///
8 | [AttributeUsage(AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
9 | public sealed class SignalRReturnAttribute : Attribute
10 | {
11 | public Type ReturnType { get; }
12 | public int StatusCode { get; }
13 | public string Description { get; }
14 |
15 | /// Return type. If null specified, the return type of the method will be picked up.
16 | /// The text that will appear in status code section of the corresponding response in Swagger document
17 | /// The text that will appear in description section of the corresponding response in Swagger document
18 | public SignalRReturnAttribute(Type returnType = null, int statusCode = 200, string description = "Success")
19 | {
20 | ReturnType = returnType;
21 | StatusCode = statusCode;
22 | Description = description;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Constants.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Enums;
2 |
3 | namespace SignalRSwaggerGen
4 | {
5 | public class Constants
6 | {
7 | public const string HubNamePlaceholder = "[Hub]";
8 | public const string MethodNamePlaceholder = "[Method]";
9 | public const string DefaultHubPathTemplate = "/hubs/" + HubNamePlaceholder;
10 | public const AutoDiscover DefaultAutoDiscover = AutoDiscover.MethodsAndParams;
11 | public const Operation DefaultOperation = Operation.Post;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Enums/AutoDiscover.cs:
--------------------------------------------------------------------------------
1 | namespace SignalRSwaggerGen.Enums
2 | {
3 | ///
4 | /// A flag indicating what components will have Swagger documentation enabled automatically
5 | ///
6 | public enum AutoDiscover
7 | {
8 | ///
9 | /// Inherit value from higher level configurations
10 | ///
11 | Inherit = -1,
12 |
13 | ///
14 | /// None
15 | ///
16 | None = 0,
17 |
18 | ///
19 | /// Public non-static methods
20 | ///
21 | Methods = 1,
22 |
23 | ///
24 | /// Method params
25 | ///
26 | Params = 2,
27 |
28 | ///
29 | /// Public non-static methods and their params
30 | ///
31 | MethodsAndParams = 3,
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Enums/Operation.cs:
--------------------------------------------------------------------------------
1 | namespace SignalRSwaggerGen.Enums
2 | {
3 | ///
4 | /// Same as HTTP verb
5 | ///
6 | public enum Operation
7 | {
8 | ///
9 | /// Inherit value from higher level configurations
10 | ///
11 | Inherit = -1,
12 | Get = 0,
13 | Put = 1,
14 | Post = 2,
15 | Delete = 3,
16 | Options = 4,
17 | Head = 5,
18 | Patch = 6,
19 | Trace = 7,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Naming/NameTransformer.cs:
--------------------------------------------------------------------------------
1 | namespace SignalRSwaggerGen.Naming
2 | {
3 | ///
4 | /// Inherit from this class in order to create a name transformer
5 | ///
6 | public abstract class NameTransformer
7 | {
8 | ///
9 | /// Name transformation function
10 | ///
11 | /// The name to transform
12 | /// Transformed name
13 | public abstract string Transform(string name);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Naming/ToCamelTransformer.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Utils;
2 |
3 | namespace SignalRSwaggerGen.Naming
4 | {
5 | public class ToCamelTransformer : NameTransformer
6 | {
7 | ///
8 | /// Transforms the name to camel case
9 | ///
10 | /// The name to transform
11 | /// Camel case name
12 | public override string Transform(string name)
13 | {
14 | if (name.IsNullOrEmpty()) return name;
15 | return char.ToLowerInvariant(name[0]) + name.Substring(1);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Naming/ToLowerTransformer.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Utils;
2 |
3 | namespace SignalRSwaggerGen.Naming
4 | {
5 | public class ToLowerTransformer : NameTransformer
6 | {
7 | ///
8 | /// Transforms the name to lower case
9 | ///
10 | /// The name to transform
11 | /// Lower case name
12 | public override string Transform(string name)
13 | {
14 | if (name.IsNullOrEmpty()) return name;
15 | return name.ToLowerInvariant();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Naming/ToUpperTransformer.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Utils;
2 |
3 | namespace SignalRSwaggerGen.Naming
4 | {
5 | public class ToUpperTransformer : NameTransformer
6 | {
7 | ///
8 | /// Transforms the name to upper case
9 | ///
10 | /// The name to transform
11 | /// Upper case name
12 | public override string Transform(string name)
13 | {
14 | if (name.IsNullOrEmpty()) return name;
15 | return name.ToUpperInvariant();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/SignalRSwaggerGen.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.OpenApi.Models;
3 | using SignalRSwaggerGen.Attributes;
4 | using SignalRSwaggerGen.Enums;
5 | using SignalRSwaggerGen.Utils;
6 | using SignalRSwaggerGen.Utils.Comparison;
7 | using SignalRSwaggerGen.Utils.XmlComments;
8 | using Swashbuckle.AspNetCore.SwaggerGen;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.IO;
12 | using System.Linq;
13 | using System.Reflection;
14 | using System.Threading.Tasks;
15 | using System.Xml.Serialization;
16 |
17 | namespace SignalRSwaggerGen
18 | {
19 | internal sealed class SignalRSwaggerGen : IDocumentFilter
20 | {
21 | private static readonly SignalRReturnAttributeComparer _returnAttributeComparer = new SignalRReturnAttributeComparer();
22 | private readonly SignalRSwaggerGenOptions _options;
23 | private readonly List _xmlComments;
24 |
25 | public SignalRSwaggerGen(SignalRSwaggerGenOptions options)
26 | {
27 | _options = options ?? throw new ArgumentNullException(nameof(options));
28 | if (_options.Assemblies.Count == 0) _options.ScanAssembly(Assembly.GetEntryAssembly());
29 | _xmlComments = new List();
30 | LoadXmlComments();
31 | }
32 |
33 | public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
34 | {
35 | var hubs = GetHubs();
36 | foreach (var hub in hubs)
37 | {
38 | var xmlComments = GetXmlComments(hub);
39 | ProcessHub(swaggerDoc, context, hub, xmlComments);
40 | }
41 | }
42 |
43 | private void ProcessHub(OpenApiDocument swaggerDoc, DocumentFilterContext context, Type hub, XmlComments xmlComments)
44 | {
45 | var hubAttribute = hub.GetCustomAttribute();
46 | if (!HubShouldBeDisplayedOnDocument(context, hubAttribute)) return;
47 | var hubXml = GetHubXml(hub, xmlComments);
48 | var hubPath = GetHubPath(hub, hubAttribute);
49 | var tag = GetTag(hub, hubAttribute, hubXml);
50 | var methods = GetHubMethods(hub, hubAttribute);
51 | foreach (var method in methods)
52 | {
53 | var methodXml = GetMethodXml(method, xmlComments);
54 | ProcessMethod(swaggerDoc, context, hub, hubAttribute, hubPath, tag, method, methodXml);
55 | }
56 | }
57 |
58 | private void ProcessMethod(
59 | OpenApiDocument swaggerDoc,
60 | DocumentFilterContext context,
61 | Type hub,
62 | SignalRHubAttribute hubAttribute,
63 | string hubPath,
64 | string tag,
65 | MethodInfo method,
66 | MemberElement methodXml)
67 | {
68 | var methodAttribute = method.GetCustomAttribute();
69 | var methodPath = GetMethodPath(hubPath, method, hubAttribute, methodAttribute);
70 | var methodParams = GetMethodParams(method, hubAttribute, methodAttribute);
71 | var methodReturnParam = method.ReturnParameter;
72 | var operation = GetOperation(methodAttribute);
73 | var summary = GetMethodSummary(hubAttribute, methodAttribute, methodXml);
74 | var description = methodAttribute?.Description;
75 | AddOpenApiPath(swaggerDoc, context, hub, hubAttribute, tag, methodPath, operation, summary, description, methodParams, methodReturnParam, method, methodXml);
76 | }
77 |
78 | private static void AddOpenApiPath(
79 | OpenApiDocument swaggerDoc,
80 | DocumentFilterContext context,
81 | Type hub,
82 | SignalRHubAttribute hubAttribute,
83 | string tag,
84 | string methodPath,
85 | Operation operation,
86 | string summary,
87 | string description,
88 | IEnumerable methodParams,
89 | ParameterInfo methodReturnParam,
90 | MethodInfo method,
91 | MemberElement methodXml)
92 | {
93 | swaggerDoc.Paths.Add(
94 | methodPath,
95 | new OpenApiPathItem
96 | {
97 | Operations = new Dictionary
98 | {
99 | {
100 | (OperationType)operation,
101 | new OpenApiOperation
102 | {
103 | Summary = summary,
104 | Description = description,
105 | Tags = new List { new OpenApiTag { Name = tag } },
106 | Parameters = ToOpenApiParameters(context, hubAttribute, methodParams, methodXml).ToList(),
107 | Responses = ToOpenApiResponses(context, methodReturnParam),
108 | RequestBody = GetOpenApiRequestBody(context, method),
109 | Security = GetSecurity(hub, method),
110 | }
111 | }
112 | }
113 | });
114 | }
115 |
116 | private static IList GetSecurity(Type hub, MethodInfo method)
117 | {
118 | var securityEnabled =
119 | hub.GetCustomAttribute() != null
120 | && method.GetCustomAttribute() == null
121 | || method.GetCustomAttribute() != null;
122 |
123 | return securityEnabled
124 | ? new List
125 | {
126 | new OpenApiSecurityRequirement
127 | {
128 | {
129 | new OpenApiSecurityScheme
130 | {
131 | Reference = new OpenApiReference
132 | {
133 | Type = ReferenceType.SecurityScheme,
134 | Id = "basic",
135 | }
136 | },
137 | Array.Empty()
138 | }
139 | }
140 | }
141 | : null;
142 | }
143 |
144 | private static IEnumerable ToOpenApiParameters(
145 | DocumentFilterContext context,
146 | SignalRHubAttribute hubAttribute,
147 | IEnumerable parameters,
148 | MemberElement methodXml)
149 | {
150 | return parameters.Select(param =>
151 | {
152 | var paramXml = methodXml?.Params?.FirstOrDefault(x => x.Name == param.Name);
153 | var paramAttribute = param.GetCustomAttribute();
154 | var description = GetParamDescription(hubAttribute, paramAttribute, paramXml);
155 | var type = paramAttribute?.ParamType ?? param.ParameterType;
156 | return new OpenApiParameter
157 | {
158 | Name = param.Name,
159 | In = ParameterLocation.Query,
160 | Description = description,
161 | Schema = GetOpenApiSchema(context, type),
162 | };
163 | });
164 | }
165 |
166 | private static OpenApiResponses ToOpenApiResponses(DocumentFilterContext context, ParameterInfo returnParam)
167 | {
168 | if (returnParam.GetCustomAttribute() != null) return null;
169 | var responses = new OpenApiResponses();
170 | var returnAttributes = returnParam.GetCustomAttributes().Distinct(_returnAttributeComparer).ToList();
171 | if (!returnAttributes.Any()) returnAttributes.Add(new SignalRReturnAttribute());
172 | foreach (var returnAttribute in returnAttributes)
173 | {
174 | var type = returnAttribute.ReturnType ?? returnParam.ParameterType;
175 | if (!TryGetReturnType(type, out type)) continue;
176 | var mediaType = new OpenApiMediaType
177 | {
178 | Schema = GetOpenApiSchema(context, type)
179 | };
180 | responses.Add(returnAttribute.StatusCode.ToString(), new OpenApiResponse
181 | {
182 | Description = returnAttribute.Description,
183 | Content = GetContentByMediaType(mediaType)
184 | });
185 | }
186 | return responses;
187 | }
188 |
189 | private static OpenApiRequestBody GetOpenApiRequestBody(DocumentFilterContext context, MethodInfo method)
190 | {
191 | var requestBodyAttribute = method.GetCustomAttribute();
192 | if (requestBodyAttribute == null) return null;
193 | var mediaType = new OpenApiMediaType
194 | {
195 | Schema = GetOpenApiSchema(context, requestBodyAttribute.BodyType)
196 | };
197 | return new OpenApiRequestBody
198 | {
199 | Required = requestBodyAttribute.IsRequired,
200 | Description = requestBodyAttribute.Description,
201 | Content = GetContentByMediaType(mediaType)
202 | };
203 | }
204 |
205 | private static OpenApiSchema GetOpenApiSchema(DocumentFilterContext context, Type type)
206 | {
207 | if (!context.SchemaRepository.TryLookupByType(type, out OpenApiSchema schema))
208 | {
209 | schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
210 | }
211 | return schema.Reference == null
212 | ? schema
213 | : new OpenApiSchema
214 | {
215 | Reference = new OpenApiReference
216 | {
217 | Id = schema.Reference.Id,
218 | Type = ReferenceType.Schema
219 | }
220 | };
221 | }
222 |
223 | private static Dictionary GetContentByMediaType(OpenApiMediaType mediaType)
224 | {
225 | return new Dictionary
226 | {
227 | { "application/json", mediaType },
228 | { "text/json", mediaType },
229 | { "text/plain", mediaType },
230 | };
231 | }
232 |
233 | private bool HubShouldBeDisplayedOnDocument(DocumentFilterContext context, SignalRHubAttribute hubAttribute)
234 | {
235 | var documentNames = hubAttribute.DocumentNames ?? _options.DocumentNames;
236 | return !documentNames.Any()
237 | || documentNames.Contains(context.DocumentName);
238 | }
239 |
240 | private string GetHubPath(Type hub, SignalRHubAttribute hubAttribute)
241 | {
242 | var hubName = GetHubName(hub);
243 | var nameTransformer = hubAttribute.NameTransformer ?? _options.NameTransformer;
244 | if (nameTransformer != null) hubName = nameTransformer.Transform(hubName);
245 | if (hubAttribute.Path != null) return hubAttribute.Path.Replace(Constants.HubNamePlaceholder, hubName);
246 | return _options.HubPathFunc(hubName);
247 | }
248 |
249 | private static string GetHubName(Type hub)
250 | {
251 | var hubName = hub.IsInterface && hub.Name[0] == 'I'
252 | ? hub.Name.Substring(1)
253 | : hub.Name;
254 | return hubName.Split('`')[0];
255 | }
256 |
257 | private string GetTag(Type hub, SignalRHubAttribute hubAttribute, MemberElement hubXml)
258 | {
259 | if (hubAttribute.Tag != null) return hubAttribute.Tag;
260 | if (!hubAttribute.XmlCommentsDisabled
261 | && _options.UseHubXmlCommentsSummaryAsTag
262 | && hubXml?.Summary?.Text != null) return hubXml.Summary.Text;
263 | return GetHubName(hub);
264 | }
265 |
266 | private string GetMethodPath(string hubPath, MethodInfo method, SignalRHubAttribute hubAttribute, SignalRMethodAttribute methodAttribute)
267 | {
268 | var methodPathSuffix = new string(' ', method.GetParameters().Length);
269 | var methodName = methodAttribute == null
270 | ? method.Name
271 | : methodAttribute.Name.Replace(Constants.MethodNamePlaceholder, method.Name);
272 | var nameTransformer = hubAttribute.NameTransformer ?? _options.NameTransformer;
273 | if (nameTransformer != null) methodName = nameTransformer.Transform(methodName);
274 | return $"{hubPath}/{methodName}{methodPathSuffix}";
275 | }
276 |
277 | private Operation GetOperation(SignalRMethodAttribute methodAttribute)
278 | {
279 | var operation = methodAttribute?.Operation ?? Operation.Inherit;
280 | if (operation == Operation.Inherit) operation = _options.Operation;
281 | return operation;
282 | }
283 |
284 | private IEnumerable GetHubs()
285 | {
286 | return _options.Assemblies
287 | .SelectMany(a =>
288 | a.GetTypes()
289 | .Where(t =>
290 | t.GetCustomAttribute() != null
291 | && t.GetCustomAttribute() == null));
292 | }
293 |
294 | private IEnumerable GetHubMethods(Type hub, SignalRHubAttribute hubAttribute)
295 | {
296 | var autoDiscover = GetAutoDiscover(hubAttribute);
297 | IEnumerable methods;
298 | switch (autoDiscover)
299 | {
300 | case AutoDiscover.None:
301 | case AutoDiscover.Params:
302 | methods = hub.GetMethods(ReflectionUtils.DeclaredPublicInstance).Where(x => x.GetCustomAttribute() != null);
303 | break;
304 | case AutoDiscover.Methods:
305 | case AutoDiscover.MethodsAndParams:
306 | methods = hub.GetMethods(ReflectionUtils.DeclaredPublicInstance);
307 | break;
308 | default:
309 | throw new NotSupportedException($"Auto-discover option '{autoDiscover}' not supported");
310 | }
311 | return methods.Where(x => x.GetCustomAttribute() == null);
312 | }
313 |
314 | private IEnumerable GetMethodParams(MethodInfo method, SignalRHubAttribute hubAttribute, SignalRMethodAttribute methodAttribute)
315 | {
316 | var autoDiscover = GetAutoDiscover(hubAttribute, methodAttribute);
317 | IEnumerable methodParams;
318 | switch (autoDiscover)
319 | {
320 | case AutoDiscover.None:
321 | case AutoDiscover.Methods:
322 | methodParams = method.GetParameters().Where(x => x.GetCustomAttribute() != null);
323 | break;
324 | case AutoDiscover.Params:
325 | case AutoDiscover.MethodsAndParams:
326 | methodParams = method.GetParameters();
327 | break;
328 | default:
329 | throw new NotSupportedException($"Auto-discover option '{autoDiscover}' not supported");
330 | }
331 | return methodParams.Where(x => x.GetCustomAttribute() == null);
332 | }
333 |
334 | private AutoDiscover GetAutoDiscover(SignalRHubAttribute hubAttribute)
335 | {
336 | var autoDiscover = hubAttribute.AutoDiscover;
337 | if (autoDiscover == AutoDiscover.Inherit) autoDiscover = _options.AutoDiscover;
338 | return autoDiscover;
339 | }
340 |
341 | private AutoDiscover GetAutoDiscover(SignalRHubAttribute hubAttribute, SignalRMethodAttribute methodAttribute)
342 | {
343 | var autoDiscover = methodAttribute?.AutoDiscover ?? AutoDiscover.Inherit;
344 | if (autoDiscover == AutoDiscover.Inherit) autoDiscover = hubAttribute.AutoDiscover;
345 | if (autoDiscover == AutoDiscover.Inherit) autoDiscover = _options.AutoDiscover;
346 | return autoDiscover;
347 | }
348 |
349 | private static string GetMethodSummary(SignalRHubAttribute hubAttribute, SignalRMethodAttribute methodAttribute, MemberElement methodXml)
350 | {
351 | var summary = methodAttribute?.Summary;
352 | if (summary != null) return summary;
353 | if (!hubAttribute.XmlCommentsDisabled) summary = methodXml?.Summary?.Text;
354 | return summary;
355 | }
356 |
357 | private static string GetParamDescription(SignalRHubAttribute hubAttribute, SignalRParamAttribute paramAttribute, ParamElement paramXml)
358 | {
359 | var description = paramAttribute?.Description;
360 | if (description != null) return description;
361 | if (!hubAttribute.XmlCommentsDisabled) description = paramXml?.Text;
362 | return description;
363 | }
364 |
365 | private XmlComments GetXmlComments(Type hub)
366 | {
367 | return _xmlComments.FirstOrDefault(x => x.Assembly?.Name?.Text == hub.Assembly.GetName().Name);
368 | }
369 |
370 | private static MemberElement GetHubXml(Type hub, XmlComments xmlComments)
371 | {
372 | return xmlComments?.Members?.FirstOrDefault(x => x.Name == hub.GetXmlCommentsName());
373 | }
374 |
375 | private static MemberElement GetMethodXml(MethodInfo method, XmlComments xmlComments)
376 | {
377 | return xmlComments?.Members?.FirstOrDefault(x => x.Name == method.GetXmlCommentsName());
378 | }
379 |
380 | private static bool TryGetReturnType(Type inType, out Type outType)
381 | {
382 | outType = inType;
383 | if (inType.IsGenericType)
384 | {
385 | if (inType.IsGenericTypeDefinition) return false;
386 | var genericTypeDef = inType.GetGenericTypeDefinition();
387 | if (genericTypeDef == typeof(Task<>)
388 | || genericTypeDef == typeof(ValueTask<>))
389 | {
390 | outType = inType.GetGenericArguments()[0];
391 | return true;
392 | }
393 | }
394 | else
395 | {
396 | if (inType == typeof(void)
397 | || inType == typeof(Task)
398 | || inType == typeof(ValueTask)) return false;
399 | }
400 | return true;
401 | }
402 |
403 | private void LoadXmlComments()
404 | {
405 | var xmlSerializer = new XmlSerializer(typeof(XmlComments));
406 | foreach (var path in _options.PathsToXmlCommentsFiles)
407 | {
408 | using (var streamReader = new StreamReader(path))
409 | {
410 | var xmlComments = (XmlComments)xmlSerializer.Deserialize(streamReader);
411 | _xmlComments.Add(xmlComments);
412 | }
413 | }
414 | }
415 | }
416 | }
417 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/SignalRSwaggerGen.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;netstandard2.1
5 | false
6 | true
7 | Dorin Mocan
8 | Essencebit
9 | This package can be used to generate Swagger documentation for SignalR hubs.
10 | swagger openapi open api swashbuckle swaggergen documentation signalr hub websocket wss rest api web socket http doc generation extension handler handle intercept plugin auto standard net core microsoft web socket schema ui gen extension
11 | https://github.com/Dorin-Mocan/SignalRSwaggerGen
12 | https://github.com/Dorin-Mocan/SignalRSwaggerGen
13 | 3.2.1
14 | Fixed default hub path template
15 | 3.2.1.0
16 | false
17 | 3.2.1.0
18 | LICENSE.md
19 | Logo.png
20 |
21 |
22 |
23 |
24 | C:\Projects\SignalRSwaggerGen\SignalRSwaggerGen\SignalRSwaggerGen\SignalRSwaggerGen.xml
25 |
26 |
27 |
28 |
29 | True
30 |
31 |
32 |
33 | True
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/SignalRSwaggerGen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SignalRSwaggerGen
5 |
6 |
7 |
8 |
9 | Use this attribute to disable Swagger documentation for specific components
10 |
11 |
12 |
13 |
14 | Use this attribute to enable Swagger documentation for hubs
15 |
16 |
17 |
18 | Path of the hub. If path contains "[Hub]", this part will be replaced with the name of the type holding this attribute(hub name).
19 | If not specified, the func from 'SignalRSwaggerGenOptions' will be used to get the path.
20 | A flag indicating what components will have Swagger documentation enabled automatically.
21 | If 'AutoDiscover.Inherit' specified, the value from 'SignalRSwaggerGenOptions' will be used.
22 | An array of names of the Swagger documents the hub will be displayed in.
23 | If null specified, then the value from 'SignalRSwaggerGenOptions' will be used. If empty array specified, then the hub will be displayed in all documents.
24 | The type of the name transformer. The type must inherit from 'SignalRSwaggerGen.Naming.NameTransformer' class, be non-abstract and have public parameterless constructor.
25 | The name transformer will be used to transform the name of the hub and its methods. If null specified, the transformer from 'SignalRSwaggerGenOptions' will be used.
26 | The namespace 'SignalRSwaggerGen.Naming' already contains some predefined name transformers, so check 'em out.
27 | The tag under which the hub will be placed in Swagger doc. If null specified, the summary section of the XML comments of the hub will be used.
28 | If XML comments missing or not enabled, the name of the type holding this attribute will be used.
29 | A flag indicating if XML comments are disabled for the hub
30 | Thrown if
31 | - value not allowed for this attribute
32 | - is abstract or does not inherit from 'SignalRSwaggerGen.Naming.NameTransformer' class or has no public parameterless constructor
33 |
34 |
35 |
36 | Use this attribute to enable Swagger documentation for hub methods
37 |
38 |
39 |
40 | Name of the method which will be invoked on the client side.
41 | If name contains "[Method]", this part will be replaced with the name of the method holding this attribute.
42 | Same as HTTP verb. If 'Operation.Inherit' specified, then the value from 'SignalRSwaggerGenOptions' will be used.
43 | A flag indicating what components will have Swagger documentation enabled automatically.
44 | If 'AutoDiscover.Inherit' specified, the value from the hub will be used. If the hub also has this value specified, then the value from 'SignalRSwaggerGenOptions' will be used.
45 | The text that will appear in summary section of the decorated method in Swagger document.
46 | If null specified and XML comments not disabled, the summary section of the XML comments of the method will be used.
47 | The text that will appear in description section of decorated method in Swagger document
48 | Thrown if
49 | - is null or empty
50 | - value not allowed for this attribute
51 |
52 |
53 |
54 | Use this attribute to enable Swagger documentation for method params
55 |
56 |
57 |
58 | The text that will appear in description section of decorated parameter in Swagger document
59 | Parameter type. If null specified, the type of the parameter holding this attribute will be picked up.
60 |
61 |
62 |
63 | Use this attribute to enable Swagger documentation for request body
64 |
65 |
66 |
67 | The type of the request body
68 | The value that indicates if the request body is required or not
69 | The text that will appear in description section of the corresponding request body in Swagger document
70 | Thrown if is null
71 |
72 |
73 |
74 | Use this attribute to enable Swagger documentation for method return type
75 |
76 |
77 |
78 | Return type. If null specified, the return type of the method will be picked up.
79 | The text that will appear in status code section of the corresponding response in Swagger document
80 | The text that will appear in description section of the corresponding response in Swagger document
81 |
82 |
83 |
84 | A flag indicating what components will have Swagger documentation enabled automatically
85 |
86 |
87 |
88 |
89 | Inherit value from higher level configurations
90 |
91 |
92 |
93 |
94 | None
95 |
96 |
97 |
98 |
99 | Public non-static methods
100 |
101 |
102 |
103 |
104 | Method params
105 |
106 |
107 |
108 |
109 | Public non-static methods and their params
110 |
111 |
112 |
113 |
114 | Same as HTTP verb
115 |
116 |
117 |
118 |
119 | Inherit value from higher level configurations
120 |
121 |
122 |
123 |
124 | Inherit from this class in order to create a name transformer
125 |
126 |
127 |
128 |
129 | Name transformation function
130 |
131 | The name to transform
132 | Transformed name
133 |
134 |
135 |
136 | Transforms the name to camel case
137 |
138 | The name to transform
139 | Camel case name
140 |
141 |
142 |
143 | Transforms the name to lower case
144 |
145 | The name to transform
146 | Lower case name
147 |
148 |
149 |
150 | Transforms the name to upper case
151 |
152 | The name to transform
153 | Upper case name
154 |
155 |
156 |
157 | Options used by SignalRSwaggerGen to generate documentation for SignalR hubs
158 |
159 |
160 |
161 |
162 | The func that will get the hub name and return the path for the hub. The func will be skipped for the hubs that have a not null path specified for them in particular.
163 | If the func not specified explicitly, the default func will return a value based on the template 'Constants.DefaultHubPathTemplate'.
164 | If you decide to set a custom func, make sure the func will return a different path for each hub.
165 |
166 | hubName => $"hubs/are/here/{hubName}"
167 | Thrown if the value is null
168 |
169 |
170 |
171 | A flag indicating what components will have Swagger documentation enabled automatically.
172 | Can be overridden for a specific component by specifying auto-discover value for that component in particular.
173 | If not specified explicitly, the default value is 'Constants.DefaultAutoDiscover'.
174 |
175 | Thrown if the value is 'AutoDiscover.Inherit', since there's no other higher level configuration to inherit from
176 |
177 |
178 |
179 | Same as HTTP verb. Can be overridden for a specific method by specifying the operation for that method in particular.
180 | If not specified explicitly, the default value is 'Constants.DefaultOperation'.
181 |
182 | Thrown if the value is 'Operation.Inherit', since there's no other higher level configuration to inherit from
183 |
184 |
185 |
186 | The name transformer that will be used to transform the name of the hubs and their methods.
187 | Can be overridden for a specific component by specifying a transformer for that component in particular.
188 | If not specified at any level, no transformation will happen. The namespace 'SignalRSwaggerGen.Naming' already contains some predefined name transformers, so check 'em out.
189 |
190 | Thrown if the value is null
191 |
192 |
193 |
194 | Use summary section from hub's XML comments as tag for Swagger doc
195 |
196 |
197 |
198 |
199 | Specify the assembly to be scanned for SignalR hubs. If no assemblies specified explicitly, the entry assembly will be scanned by default.
200 | This method has additive effect. You can use it multiple times to add more assemblies.
201 |
202 | Assembly to be scanned for SignalR hubs
203 | Thrown if is null
204 |
205 |
206 |
207 | Specify assemblies to be scanned for SignalR hubs. If no assemblies specified explicitly, the entry assembly will be scanned by default.
208 | This method has additive effect. You can use it multiple times to add more assemblies.
209 |
210 | Assemblies to be scanned for SignalR hubs
211 | Thrown if or any of its items is null
212 | Thrown if is empty
213 |
214 |
215 |
216 | Specify assemblies to be scanned for SignalR hubs. If no assemblies specified explicitly, the entry assembly will be scanned by default.
217 | This method has additive effect. You can use it multiple times to add more assemblies.
218 |
219 | Assemblies to be scanned for SignalR hubs
220 | Thrown if or any of its items is null
221 | Thrown if is empty
222 |
223 |
224 |
225 | Specify the name of the Swagger document the hubs will be displayed in.
226 | Can be overridden for a specific hub by specifying document names for that hub in particular.
227 | If no document names specified explicitly, then the hubs will be displayed in all documents.
228 | This method has additive effect. You can use it multiple times to add more document names.
229 |
230 | Name of the Swagger document the hubs will be displayed in
231 | Thrown if is null
232 |
233 |
234 |
235 | Specify the list of names of the Swagger documents the hubs will be displayed in.
236 | Can be overridden for a specific hub by specifying document names for that hub in particular.
237 | If no document names specified explicitly, then the hubs will be displayed in all documents.
238 | This method has additive effect. You can use it multiple times to add more document names.
239 |
240 | Names of the Swagger documents the hubs will be displayed in
241 | Thrown if or any of its items is null
242 | Thrown if is empty
243 |
244 |
245 |
246 | Specify the list of names of the Swagger documents the hubs will be displayed in.
247 | Can be overridden for a specific hub by specifying document names for that hub in particular.
248 | If no document names specified explicitly, then the hubs will be displayed in all documents.
249 | This method has additive effect. You can use it multiple times to add more document names.
250 |
251 | Names of the Swagger documents the hubs will be displayed in
252 | Thrown if or any of its items is null
253 | Thrown if is empty
254 |
255 |
256 |
257 | Specify an XML comments file to be used for generating Swagger doc.
258 | This method has additive effect. You can use it multiple times to add more XML comments files.
259 |
260 | Path to the file that contains XML comments
261 | Thrown if is null
262 | Thrown if does not exist
263 |
264 |
265 |
266 | Specify a list of XML comments files to be used for generating Swagger doc.
267 | This method has additive effect. You can use it multiple times to add more XML comments files.
268 |
269 | Paths to the files that contain XML comments
270 | Thrown if or any of its items is null
271 | Thrown if is empty or any of its items does not exist
272 |
273 |
274 |
275 | Specify a list of XML comments files to be used for generating Swagger doc.
276 | This method has additive effect. You can use it multiple times to add more XML comments files.
277 |
278 | Paths to the files that contain XML comments
279 | Thrown if or any of its items is null
280 | Thrown if is empty or any of its items does not exist
281 |
282 |
283 |
284 | Add SignalRSwaggerGen to generate documentation for SignalR hubs
285 |
286 | ...
287 |
288 |
289 |
290 | Add SignalRSwaggerGen to generate documentation for SignalR hubs
291 |
292 | ...
293 | Action for setting up options for SignalRSwaggerGen
294 |
295 |
296 |
297 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/SignalRSwaggerGenOptions.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Enums;
2 | using SignalRSwaggerGen.Naming;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Reflection;
8 |
9 | namespace SignalRSwaggerGen
10 | {
11 | ///
12 | /// Options used by SignalRSwaggerGen to generate documentation for SignalR hubs
13 | ///
14 | public class SignalRSwaggerGenOptions
15 | {
16 | ///
17 | /// The func that will get the hub name and return the path for the hub. The func will be skipped for the hubs that have a not null path specified for them in particular.
18 | /// If the func not specified explicitly, the default func will return a value based on the template 'Constants.DefaultHubPathTemplate'.
19 | /// If you decide to set a custom func, make sure the func will return a different path for each hub.
20 | ///
21 | /// hubName => $"hubs/are/here/{hubName}"
22 | /// Thrown if the value is null
23 | public Func HubPathFunc
24 | {
25 | get => _hubPathFunc;
26 | set
27 | {
28 | _hubPathFunc = value ?? throw new ArgumentNullException(nameof(HubPathFunc));
29 | }
30 | }
31 |
32 | ///
33 | /// A flag indicating what components will have Swagger documentation enabled automatically.
34 | /// Can be overridden for a specific component by specifying auto-discover value for that component in particular.
35 | /// If not specified explicitly, the default value is 'Constants.DefaultAutoDiscover'.
36 | ///
37 | /// Thrown if the value is 'AutoDiscover.Inherit', since there's no other higher level configuration to inherit from
38 | public AutoDiscover AutoDiscover
39 | {
40 | get => _autoDiscover;
41 | set
42 | {
43 | if (value == AutoDiscover.Inherit) throw new ArgumentException($"Auto-discover option '{value}' not allowed, since there's no other higher level configuration to inherit from");
44 | _autoDiscover = value;
45 | }
46 | }
47 |
48 | ///
49 | /// Same as HTTP verb. Can be overridden for a specific method by specifying the operation for that method in particular.
50 | /// If not specified explicitly, the default value is 'Constants.DefaultOperation'.
51 | ///
52 | /// Thrown if the value is 'Operation.Inherit', since there's no other higher level configuration to inherit from
53 | public Operation Operation
54 | {
55 | get => _operation;
56 | set
57 | {
58 | if (value == Operation.Inherit) throw new ArgumentException($"Operation '{value}' not allowed, since there's no other higher level configuration to inherit from");
59 | _operation = value;
60 | }
61 | }
62 |
63 | ///
64 | /// The name transformer that will be used to transform the name of the hubs and their methods.
65 | /// Can be overridden for a specific component by specifying a transformer for that component in particular.
66 | /// If not specified at any level, no transformation will happen. The namespace 'SignalRSwaggerGen.Naming' already contains some predefined name transformers, so check 'em out.
67 | ///
68 | /// Thrown if the value is null
69 | public NameTransformer NameTransformer
70 | {
71 | get => _nameTransformer;
72 | set
73 | {
74 | _nameTransformer = value ?? throw new ArgumentNullException(nameof(NameTransformer));
75 | }
76 | }
77 |
78 | ///
79 | /// Use summary section from hub's XML comments as tag for Swagger doc
80 | ///
81 | public bool UseHubXmlCommentsSummaryAsTag { get; set; }
82 |
83 | ///
84 | /// Specify the assembly to be scanned for SignalR hubs. If no assemblies specified explicitly, the entry assembly will be scanned by default.
85 | /// This method has additive effect. You can use it multiple times to add more assemblies.
86 | ///
87 | /// Assembly to be scanned for SignalR hubs
88 | /// Thrown if is null
89 | public void ScanAssembly(Assembly assembly)
90 | {
91 | if (assembly == null) throw new ArgumentNullException(nameof(assembly));
92 | Assemblies.Add(assembly);
93 | }
94 |
95 | ///
96 | /// Specify assemblies to be scanned for SignalR hubs. If no assemblies specified explicitly, the entry assembly will be scanned by default.
97 | /// This method has additive effect. You can use it multiple times to add more assemblies.
98 | ///
99 | /// Assemblies to be scanned for SignalR hubs
100 | /// Thrown if or any of its items is null
101 | /// Thrown if is empty
102 | public void ScanAssemblies(IEnumerable assemblies)
103 | {
104 | if (assemblies == null) throw new ArgumentNullException(nameof(assemblies));
105 | if (!assemblies.Any()) throw new ArgumentException("Empty", nameof(assemblies));
106 | foreach (var assembly in assemblies)
107 | {
108 | ScanAssembly(assembly);
109 | }
110 | }
111 |
112 | ///
113 | /// Specify assemblies to be scanned for SignalR hubs. If no assemblies specified explicitly, the entry assembly will be scanned by default.
114 | /// This method has additive effect. You can use it multiple times to add more assemblies.
115 | ///
116 | /// Assemblies to be scanned for SignalR hubs
117 | /// Thrown if or any of its items is null
118 | /// Thrown if is empty
119 | public void ScanAssemblies(params Assembly[] assemblies)
120 | {
121 | if (assemblies == null) throw new ArgumentNullException(nameof(assemblies));
122 | if (assemblies.Length == 0) throw new ArgumentException("Empty", nameof(assemblies));
123 | foreach (var assembly in assemblies)
124 | {
125 | ScanAssembly(assembly);
126 | }
127 | }
128 |
129 | ///
130 | /// Specify the name of the Swagger document the hubs will be displayed in.
131 | /// Can be overridden for a specific hub by specifying document names for that hub in particular.
132 | /// If no document names specified explicitly, then the hubs will be displayed in all documents.
133 | /// This method has additive effect. You can use it multiple times to add more document names.
134 | ///
135 | /// Name of the Swagger document the hubs will be displayed in
136 | /// Thrown if is null
137 | public void DisplayInDocument(string documentName)
138 | {
139 | if (documentName == null) throw new ArgumentNullException(nameof(documentName));
140 | DocumentNames.Add(documentName);
141 | }
142 |
143 | ///
144 | /// Specify the list of names of the Swagger documents the hubs will be displayed in.
145 | /// Can be overridden for a specific hub by specifying document names for that hub in particular.
146 | /// If no document names specified explicitly, then the hubs will be displayed in all documents.
147 | /// This method has additive effect. You can use it multiple times to add more document names.
148 | ///
149 | /// Names of the Swagger documents the hubs will be displayed in
150 | /// Thrown if or any of its items is null
151 | /// Thrown if is empty
152 | public void DisplayInDocuments(IEnumerable documentNames)
153 | {
154 | if (documentNames == null) throw new ArgumentNullException(nameof(documentNames));
155 | if (!documentNames.Any()) throw new ArgumentException("Empty", nameof(documentNames));
156 | foreach (var documentName in documentNames)
157 | {
158 | DisplayInDocument(documentName);
159 | }
160 | }
161 |
162 | ///
163 | /// Specify the list of names of the Swagger documents the hubs will be displayed in.
164 | /// Can be overridden for a specific hub by specifying document names for that hub in particular.
165 | /// If no document names specified explicitly, then the hubs will be displayed in all documents.
166 | /// This method has additive effect. You can use it multiple times to add more document names.
167 | ///
168 | /// Names of the Swagger documents the hubs will be displayed in
169 | /// Thrown if or any of its items is null
170 | /// Thrown if is empty
171 | public void DisplayInDocuments(params string[] documentNames)
172 | {
173 | if (documentNames == null) throw new ArgumentNullException(nameof(documentNames));
174 | if (documentNames.Length == 0) throw new ArgumentException("Empty", nameof(documentNames));
175 | foreach (var documentName in documentNames)
176 | {
177 | DisplayInDocument(documentName);
178 | }
179 | }
180 |
181 | ///
182 | /// Specify an XML comments file to be used for generating Swagger doc.
183 | /// This method has additive effect. You can use it multiple times to add more XML comments files.
184 | ///
185 | /// Path to the file that contains XML comments
186 | /// Thrown if is null
187 | /// Thrown if does not exist
188 | public void UseXmlComments(string path)
189 | {
190 | if (path == null) throw new ArgumentNullException(nameof(path));
191 | if (!File.Exists(path)) throw new ArgumentException($"Does not exist: path=[{path}]", nameof(path));
192 | PathsToXmlCommentsFiles.Add(path);
193 | }
194 |
195 | ///
196 | /// Specify a list of XML comments files to be used for generating Swagger doc.
197 | /// This method has additive effect. You can use it multiple times to add more XML comments files.
198 | ///
199 | /// Paths to the files that contain XML comments
200 | /// Thrown if or any of its items is null
201 | /// Thrown if is empty or any of its items does not exist
202 | public void UseXmlComments(IEnumerable paths)
203 | {
204 | if (paths == null) throw new ArgumentNullException(nameof(paths));
205 | if (!paths.Any()) throw new ArgumentException("Empty", nameof(paths));
206 | foreach (var path in paths)
207 | {
208 | UseXmlComments(path);
209 | }
210 | }
211 |
212 | ///
213 | /// Specify a list of XML comments files to be used for generating Swagger doc.
214 | /// This method has additive effect. You can use it multiple times to add more XML comments files.
215 | ///
216 | /// Paths to the files that contain XML comments
217 | /// Thrown if or any of its items is null
218 | /// Thrown if is empty or any of its items does not exist
219 | public void UseXmlComments(params string[] paths)
220 | {
221 | if (paths == null) throw new ArgumentNullException(nameof(paths));
222 | if (paths.Length == 0) throw new ArgumentException("Empty", nameof(paths));
223 | foreach (var path in paths)
224 | {
225 | UseXmlComments(path);
226 | }
227 | }
228 |
229 | internal HashSet Assemblies { get; } = new HashSet();
230 | internal HashSet PathsToXmlCommentsFiles { get; } = new HashSet();
231 | internal HashSet DocumentNames { get; } = new HashSet();
232 |
233 | private Func _hubPathFunc = hubName => Constants.DefaultHubPathTemplate.Replace(Constants.HubNamePlaceholder, hubName);
234 | private AutoDiscover _autoDiscover = Constants.DefaultAutoDiscover;
235 | private Operation _operation = Constants.DefaultOperation;
236 | private NameTransformer _nameTransformer;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/SwaggerGenOptionsExtensions.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen;
2 | using Swashbuckle.AspNetCore.SwaggerGen;
3 | using System;
4 |
5 | namespace Microsoft.Extensions.DependencyInjection
6 | {
7 | public static class SwaggerGenOptionsExtensions
8 | {
9 | ///
10 | /// Add SignalRSwaggerGen to generate documentation for SignalR hubs
11 | ///
12 | /// ...
13 | public static void AddSignalRSwaggerGen(this SwaggerGenOptions swaggerGenOptions)
14 | {
15 | var signalRSwaggerGenOptions = new SignalRSwaggerGenOptions();
16 | swaggerGenOptions.DocumentFilter(signalRSwaggerGenOptions);
17 | }
18 |
19 | ///
20 | /// Add SignalRSwaggerGen to generate documentation for SignalR hubs
21 | ///
22 | /// ...
23 | /// Action for setting up options for SignalRSwaggerGen
24 | public static void AddSignalRSwaggerGen(this SwaggerGenOptions swaggerGenOptions, Action action)
25 | {
26 | if (action == null) throw new ArgumentNullException(nameof(action));
27 | var signalRSwaggerGenOptions = new SignalRSwaggerGenOptions();
28 | action(signalRSwaggerGenOptions);
29 | swaggerGenOptions.DocumentFilter(signalRSwaggerGenOptions);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Utils/Comparison/SignalRReturnAttributeComparer.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Attributes;
2 | using System.Collections.Generic;
3 |
4 | namespace SignalRSwaggerGen.Utils.Comparison
5 | {
6 | internal class SignalRReturnAttributeComparer : IEqualityComparer
7 | {
8 | public bool Equals(SignalRReturnAttribute x, SignalRReturnAttribute y)
9 | {
10 | return x != null
11 | && y != null
12 | && x.StatusCode == y.StatusCode;
13 | }
14 |
15 | public int GetHashCode(SignalRReturnAttribute obj)
16 | {
17 | return obj.StatusCode;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Utils/EnumerableUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace SignalRSwaggerGen.Utils
6 | {
7 | internal static class EnumerableUtils
8 | {
9 | public static IEnumerable TakeLast(this IEnumerable enumerable, int takeCount)
10 | {
11 | if (takeCount < 0) throw new ArgumentException("Less than zero", nameof(takeCount));
12 | if (enumerable == null) return null;
13 | var enumerableCount = enumerable.Count();
14 | var skipCount = enumerableCount <= takeCount ? 0 : enumerableCount - takeCount;
15 | return enumerable.Skip(skipCount).Take(takeCount);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Utils/ReflectionUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace SignalRSwaggerGen.Utils
4 | {
5 | internal class ReflectionUtils
6 | {
7 | public const BindingFlags DeclaredPublicInstance = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Utils/StringUtils.cs:
--------------------------------------------------------------------------------
1 | namespace SignalRSwaggerGen.Utils
2 | {
3 | internal static class StringUtils
4 | {
5 | public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Utils/XmlComments/XmlComments.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Xml.Serialization;
3 |
4 | namespace SignalRSwaggerGen.Utils.XmlComments
5 | {
6 | [XmlRoot("doc")]
7 | public class XmlComments
8 | {
9 | [XmlElement("assembly")]
10 | public AssemblyElement Assembly { get; set; }
11 |
12 | [XmlArray("members")]
13 | [XmlArrayItem("member")]
14 | public List Members { get; set; } = new List();
15 | }
16 |
17 | public class AssemblyElement
18 | {
19 | [XmlElement("name")]
20 | public AssemblyNameElement Name { get; set; }
21 | }
22 |
23 | public class AssemblyNameElement
24 | {
25 | [XmlText]
26 | public string Text { get; set; }
27 | }
28 |
29 | public class MemberElement
30 | {
31 | [XmlAttribute("name")]
32 | public string Name { get; set; }
33 |
34 | [XmlElement("summary")]
35 | public MemberSummaryElement Summary { get; set; }
36 |
37 | [XmlElement("param")]
38 | public List Params { get; set; } = new List();
39 | }
40 |
41 | public class MemberSummaryElement
42 | {
43 | [XmlText]
44 | public string Text { get; set; }
45 | }
46 |
47 | public class ParamElement
48 | {
49 | [XmlAttribute("name")]
50 | public string Name { get; set; }
51 |
52 | [XmlText]
53 | public string Text { get; set; }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/SignalRSwaggerGen/Utils/XmlComments/XmlCommentsUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace SignalRSwaggerGen.Utils.XmlComments
8 | {
9 | internal static class XmlCommentsUtils
10 | {
11 | public static string GetXmlCommentsName(this Type type)
12 | {
13 | if (type == null) throw new ArgumentNullException(nameof(type));
14 |
15 | var result = $"T:{GetTypeName(type)}";
16 |
17 | return result;
18 | }
19 |
20 | public static string GetXmlCommentsName(this MethodInfo method)
21 | {
22 | if (method == null) throw new ArgumentNullException(nameof(method));
23 |
24 | var result = $"M:{GetTypeName(method.DeclaringType)}.{method.Name}";
25 |
26 | if (method.IsGenericMethodDefinition)
27 | {
28 | result += $"``{method.GetGenericArguments().Length}";
29 | }
30 |
31 | var parameters = method.GetParameters();
32 | if (parameters.Length == 0) return result;
33 |
34 | result += "(";
35 | for (int i = 0; i < parameters.Length; i++)
36 | {
37 | if (i > 0) result += ",";
38 | result += GetParamTypeName(parameters[i].ParameterType);
39 | }
40 | result += ")";
41 |
42 | return result;
43 | }
44 |
45 | private static string GetTypeName(Type type)
46 | {
47 | return Regex.Replace(type.FullName ?? $"{type.Namespace}.{type.Name}", @"\[.*\]", string.Empty).Replace('+', '.');
48 | }
49 |
50 | private static string GetParamTypeName(Type paramType)
51 | {
52 | if (paramType.HasElementType) return GetWithElementParamTypeName(paramType);
53 | if (paramType.IsGenericParameter) return GetGenericParameterParamTypeName(paramType);
54 | if (paramType.IsGenericType) return GetGenericParamTypeName(paramType);
55 | return GetSimpleParamTypeName(paramType);
56 | }
57 |
58 | private static string GetWithElementParamTypeName(Type paramType)
59 | {
60 | if (paramType.IsByRef) return GetByRefParamTypeName(paramType);
61 | if (paramType.IsPointer) return GetPointerParamTypeName(paramType);
62 | if (paramType.IsArray) return GetArrayParamTypeName(paramType);
63 | throw new NotSupportedException($"Type [{paramType.AssemblyQualifiedName}] not supported");
64 | }
65 |
66 | private static string GetByRefParamTypeName(Type paramType)
67 | {
68 | return GetParamTypeName(paramType.GetElementType()) + "@";
69 | }
70 |
71 | private static string GetPointerParamTypeName(Type paramType)
72 | {
73 | return GetParamTypeName(paramType.GetElementType()) + "*";
74 | }
75 |
76 | private static string GetArrayParamTypeName(Type paramType)
77 | {
78 | var arrayRank = paramType.GetArrayRank();
79 | var arrayTypeSpecifier = arrayRank == 1
80 | ? "[]"
81 | : $"[{string.Join(",", Enumerable.Range(0, arrayRank).Select(x => "0:"))}]";
82 |
83 | return $"{GetParamTypeName(paramType.GetElementType())}{arrayTypeSpecifier}";
84 | }
85 |
86 | private static string GetGenericParameterParamTypeName(Type paramType)
87 | {
88 | #if NETSTANDARD2_1
89 | if (paramType.IsGenericMethodParameter) return $"``{paramType.GenericParameterPosition}";
90 | if (paramType.IsGenericTypeParameter) return $"`{paramType.GenericParameterPosition}";
91 | throw new NotSupportedException($"Type [{paramType.AssemblyQualifiedName}] not supported");
92 | #else
93 | throw new NotSupportedException("Certain API required for further execution is missing. Target netstandard2.1 to get rid of this exception.");
94 | #endif
95 | }
96 |
97 | private static string GetGenericParamTypeName(Type paramType)
98 | {
99 | var genericArgs = paramType.GetGenericArguments();
100 | if (paramType.IsNested) return GetNestedGenericParamTypeName(paramType, genericArgs);
101 | return $"{(paramType.FullName ?? $"{paramType.Namespace}.{paramType.Name}").Split('`')[0]}{{{string.Join(",", genericArgs.Select(x => GetParamTypeName(x)))}}}";
102 | }
103 |
104 | private static string GetNestedGenericParamTypeName(Type paramType, IEnumerable allGenericArgs)
105 | {
106 | var declaringType = paramType.DeclaringType;
107 | var declaringTypeGenericArgsCount = declaringType.GetGenericArguments().Length;
108 | var declaringTypeName = !declaringType.IsGenericType
109 | ? GetParamTypeName(declaringType)
110 | : GetNestedGenericParamTypeName(declaringType, allGenericArgs.Take(declaringTypeGenericArgsCount));
111 |
112 | var genericArgsCount = paramType.GetGenericArguments().Length - declaringTypeGenericArgsCount;
113 | var genericArgsNames = genericArgsCount > 0
114 | ? $"{{{string.Join(",", allGenericArgs.TakeLast(genericArgsCount).Select(x => GetParamTypeName(x)))}}}"
115 | : "";
116 |
117 | return $"{declaringTypeName}.{paramType.Name.Split('`')[0]}{genericArgsNames}";
118 | }
119 |
120 | private static string GetSimpleParamTypeName(Type paramType)
121 | {
122 | if (paramType.IsNested) return $"{GetParamTypeName(paramType.DeclaringType)}.{paramType.Name}";
123 | return paramType.FullName ?? $"{paramType.Namespace}.{paramType.Name}";
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/TestWebApi/Controllers/TestController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using System.Collections.Generic;
3 |
4 | namespace TestWebApi.Controllers
5 | {
6 | [ApiExplorerSettings(GroupName = "controllers")]
7 | [ApiController]
8 | [Route("[controller]")]
9 | public class TestController : ControllerBase
10 | {
11 | [ProducesResponseType(typeof(int), 200)]
12 | [ProducesResponseType(typeof(string), 400)]
13 | [HttpGet]
14 | public IEnumerable Get()
15 | {
16 | return default;
17 | }
18 |
19 | [HttpPost]
20 | public int Post([FromBody] WeatherForecast body)
21 | {
22 | return body.TemperatureC;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/TestWebApi/Hubs/StronglyTypedTestHub.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.SignalR;
3 | using SignalRSwaggerGen.Attributes;
4 | using SignalRSwaggerGen.Enums;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace TestWebApi.Hubs
9 | {
10 | [SignalRHub(autoDiscover: AutoDiscover.MethodsAndParams, documentNames: new[] { "hubs" })]
11 | public class StronglyTypedTestHub : Hub
12 | {
13 | public async Task TestMethod()
14 | {
15 | await Clients.All.TestMethod(default, default, default, default);
16 | }
17 | }
18 |
19 | [SignalRHub(autoDiscover: AutoDiscover.MethodsAndParams, documentNames: new[] { "hubs" })]
20 | public interface IStronglyTypedTestHub
21 | {
22 | [Authorize]
23 | [return: SignalRReturn]
24 | [SignalRMethod(summary: "method1 summary", description: "method1 description", autoDiscover: AutoDiscover.Params)]
25 | public ValueTask TestMethod(
26 | int agr1,
27 | string arg2,
28 | [SignalRParam(description: "arg3 description")] WeatherForecast arg3,
29 | [SignalRHidden] CancellationToken cancellationToken);
30 |
31 | [return: SignalRReturn(typeof(Task), 200, "Success")]
32 | [return: SignalRReturn(returnType: typeof(ValueTask<>), statusCode: 201, description: "Created")]
33 | [SignalRMethod(summary: "method2 summary", description: "method2 description", autoDiscover: AutoDiscover.Params)]
34 | public void TestMethod2(
35 | [SignalRParam(description: "arg1 description")] int agr1,
36 | [SignalRParam(description: "arg2 description")] string arg2,
37 | WeatherForecast arg3,
38 | [SignalRHidden] CancellationToken cancellationToken);
39 |
40 | [SignalRRequestBody(typeof(WeatherForecast), false, "request body description")]
41 | [return: SignalRHidden]
42 | public Task TestMethod3(
43 | [SignalRParam(paramType: typeof(WeatherForecast))] int agr1,
44 | string arg2,
45 | WeatherForecast arg3,
46 | [SignalRHidden] CancellationToken cancellationToken);
47 |
48 | [return: SignalRHidden]
49 | public Task TestMethod3();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/TestWebApi/Hubs/TestHub.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.SignalR;
3 | using SignalRSwaggerGen.Attributes;
4 | using SignalRSwaggerGen.Enums;
5 | using SignalRSwaggerGen.Naming;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace TestWebApi.Hubs
10 | {
11 | [Authorize]
12 | [SignalRHub(autoDiscover: AutoDiscover.MethodsAndParams, documentNames: new[] { "hubs" }, nameTransformerType: typeof(ToLowerTransformer))]
13 | public class TestHub : Hub
14 | {
15 | [AllowAnonymous]
16 | [return: SignalRReturn]
17 | [SignalRMethod(summary: "method1 summary", description: "method1 description", autoDiscover: AutoDiscover.Params)]
18 | public ValueTask TestMethod(
19 | int agr1,
20 | string arg2,
21 | [SignalRParam(description: "arg3 description")] WeatherForecast arg3,
22 | [SignalRHidden] CancellationToken cancellationToken)
23 | {
24 | return default;
25 | }
26 |
27 | [return: SignalRReturn(typeof(Task), 200, "Success")]
28 | [return: SignalRReturn(returnType: typeof(ValueTask<>), statusCode: 201, description: "Created")]
29 | [SignalRMethod(summary: "method2 summary", description: "method2 description", autoDiscover: AutoDiscover.Params)]
30 | public void TestMethod2(
31 | [SignalRParam(description: "arg1 description")] int agr1,
32 | [SignalRParam(description: "arg2 description")] string arg2,
33 | WeatherForecast arg3,
34 | [SignalRHidden] CancellationToken cancellationToken)
35 | {
36 | return;
37 | }
38 |
39 | [SignalRRequestBody(typeof(WeatherForecast), false, "request body description")]
40 | [return: SignalRHidden]
41 | public Task TestMethod3(
42 | int agr1,
43 | string arg2,
44 | WeatherForecast arg3,
45 | [SignalRHidden] CancellationToken cancellationToken)
46 | {
47 | return default;
48 | }
49 |
50 | [return: SignalRHidden]
51 | public Task TestMethod3()
52 | {
53 | return default;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SignalRSwaggerGen/TestWebApi/Hubs/XmlCommentsHub.cs:
--------------------------------------------------------------------------------
1 | using SignalRSwaggerGen.Attributes;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace TestWebApi.Hubs
6 | {
7 | public class Outer
8 | {
9 | public class Middle
10 | {
11 | public class Inner
12 | {
13 | public class GenericClass { }
14 |
15 | ///
16 | /// xml commented hub
17 | ///
18 | ///
19 | ///
20 | [SignalRHub(documentNames: new[] { "hubs" })]
21 | public class XmlCommentedHub
22 | {
23 | public struct Struct
24 | {
25 | public struct GenericStruct
26 | {
27 | public interface IInterface
28 | {
29 | public delegate int Delegate();
30 | }
31 | }
32 | }
33 |
34 | ///
35 | /// xml commented method1 summary
36 | ///
37 | ///
38 | ///
39 | /// xml commented arg1 description
40 | /// xml commented arg2 description
41 | /// xml commented arg3 description
42 | /// xml commented arg4 description
43 | /// something
44 | public unsafe void Method1(
45 | ref Middle.Inner.GenericClass, TO1, TO2[,]>[,,,][][,,] arg1,
46 | Struct.GenericStruct.IInterface>[,], GenericClass, Inner>>.Delegate