├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
└── exceptionless.webhooks
├── Exceptionless.WebHook.Abstractions
├── Exceptionless.WebHook.Abstractions.csproj
├── ExceptionlessEventModel.cs
├── FileTemplateService.cs
└── IWebHookProvider.cs
├── Exceptionless.WebHook.DingTalk
├── DingTalkWebHookProvider.cs
├── Exceptionless.WebHook.DingTalk.csproj
├── Messages
│ ├── DingTalkRequestMessage.cs
│ ├── DingTalkResponseMessage.cs
│ ├── MessageType.cs
│ └── Messages.cs
├── Services
│ └── MessageService.cs
└── Utilitys
│ └── StringUtilitys.cs
├── Web
├── AppSettings.cs
├── Middleware
│ └── ExceptionlessWebhookMiddleware.cs
├── Program.cs
├── Startup.cs
├── Web.csproj
├── appsettings.json
├── markdownTemplate.md
├── nlog.Development.config
└── nlog.config
└── exceptionless.webhooks.sln
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.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 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # exceptionless-webhooks
2 | 一个 Exceptionless 相关 WebHooks 项目。
3 | ## 目标
4 | DingTalk(钉钉)
5 |
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.Abstractions/Exceptionless.WebHook.Abstractions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.Abstractions/ExceptionlessEventModel.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 |
4 | namespace Exceptionless.WebHook.Abstractions
5 | {
6 | public class ExceptionlessEventModel
7 | {
8 | public string Id { get; set; }
9 | public string Url { get; set; }
10 |
11 | [JsonProperty("occurrence_date")]
12 | public DateTime OccurrenceDate { get; set; }
13 |
14 | public string Type { get; set; }
15 | public string Message { get; set; }
16 |
17 | [JsonProperty("project_id")]
18 | public string ProjectId { get; set; }
19 |
20 | [JsonProperty("project_Name")]
21 | public string ProjectName { get; set; }
22 |
23 | [JsonProperty("organization_id")]
24 | public string OrganizationId { get; set; }
25 |
26 | [JsonProperty("organization_name")]
27 | public string OrganizationName { get; set; }
28 |
29 | [JsonProperty("stack_id")]
30 | public string StackId { get; set; }
31 |
32 | [JsonProperty("stack_url")]
33 | public string StackUrl { get; set; }
34 |
35 | [JsonProperty("stack_title")]
36 | public string StackTitle { get; set; }
37 |
38 | [JsonProperty("stack_tags")]
39 | public string[] StackTags { get; set; }
40 |
41 | [JsonProperty("total_occurrences")]
42 | public int TotalOccurrences { get; set; }
43 |
44 | [JsonProperty("first_occurrence")]
45 | public DateTime FirstOccurrence { get; set; }
46 |
47 | [JsonProperty("last_occurrence")]
48 | public DateTime LastOccurrence { get; set; }
49 |
50 | [JsonProperty("is_new")]
51 | public bool IsNew { get; set; }
52 |
53 | [JsonProperty("is_regression")]
54 | public bool IsRegression { get; set; }
55 |
56 | [JsonProperty("is_critical")]
57 | public bool IsCritical { get; set; }
58 |
59 | [JsonProperty("ip_address")]
60 | public string IpAddress { get; set; }
61 | }
62 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.Abstractions/FileTemplateService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Caching.Memory;
2 | using Microsoft.Extensions.FileProviders;
3 | using Rabbit.Extensions.DependencyInjection;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Reflection;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace Exceptionless.WebHook.Abstractions
12 | {
13 | public class FileTemplateService : ISingletonDependency, IDisposable
14 | {
15 | #region Field
16 |
17 | private readonly IMemoryCache _memoryCache;
18 | private readonly PhysicalFileProvider _physicalFileProvider;
19 |
20 | #endregion Field
21 |
22 | #region Constructor
23 |
24 | public FileTemplateService(IMemoryCache memoryCache)
25 | {
26 | _memoryCache = memoryCache;
27 | _physicalFileProvider = new PhysicalFileProvider(AppContext.BaseDirectory);
28 | }
29 |
30 | #endregion Constructor
31 |
32 | #region Public Method
33 |
34 | public async Task GetContent(string templateFile, object model)
35 | {
36 | var template = await GetTemplate(templateFile);
37 | if (string.IsNullOrEmpty(template))
38 | return template;
39 |
40 | var properties = model.GetType().GetProperties();
41 | var builder = new StringBuilder(template);
42 |
43 | var propertys = new Dictionary();
44 | string GetPropertyValue(PropertyInfo property)
45 | {
46 | if (propertys.TryGetValue(property.Name, out var value))
47 | {
48 | return value;
49 | }
50 |
51 | value = property.GetValue(model)?.ToString();
52 |
53 | propertys[property.Name] = value;
54 |
55 | return value;
56 | }
57 |
58 | foreach (var property in properties)
59 | {
60 | var key = "{" + property.Name + "}";
61 | if (template.Contains(key))
62 | builder.Replace(key, GetPropertyValue(property));
63 | }
64 | return builder.ToString();
65 | }
66 |
67 | #endregion Public Method
68 |
69 | #region Private Method
70 |
71 | private async Task GetTemplate(string templateFile)
72 | {
73 | return await _memoryCache.GetOrCreateAsync(templateFile, async entry =>
74 | {
75 | entry.AddExpirationToken(_physicalFileProvider.Watch(templateFile));
76 | var fileInfo = _physicalFileProvider.GetFileInfo(templateFile);
77 | if (!fileInfo.Exists)
78 | return string.Empty;
79 |
80 | using (var reader = new StreamReader(fileInfo.CreateReadStream()))
81 | {
82 | return await reader.ReadToEndAsync();
83 | }
84 | });
85 | }
86 |
87 | #endregion Private Method
88 |
89 | #region IDisposable
90 |
91 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
92 | public void Dispose()
93 | {
94 | _memoryCache?.Dispose();
95 | _physicalFileProvider?.Dispose();
96 | }
97 |
98 | #endregion IDisposable
99 | }
100 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.Abstractions/IWebHookProvider.cs:
--------------------------------------------------------------------------------
1 | using Rabbit.Extensions.DependencyInjection;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace Exceptionless.WebHook.Abstractions
6 | {
7 | public interface IWebHookProvider : ISingletonDependency
8 | {
9 | string Name { get; }
10 |
11 | Task ProcessAsync(ExceptionlessEventModel model, IDictionary parameters);
12 | }
13 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/DingTalkWebHookProvider.cs:
--------------------------------------------------------------------------------
1 | using Exceptionless.WebHook.Abstractions;
2 | using Exceptionless.WebHook.DingTalk.Messages;
3 | using Exceptionless.WebHook.DingTalk.Services;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Threading.Tasks;
7 |
8 | namespace Exceptionless.WebHook.DingTalk
9 | {
10 | public class DingTalkWebHookProvider : IWebHookProvider
11 | {
12 | private readonly FileTemplateService _fileTemplateService;
13 | private readonly MessageService _messageService;
14 |
15 | public DingTalkWebHookProvider(FileTemplateService fileTemplateService, MessageService messageService)
16 | {
17 | _fileTemplateService = fileTemplateService;
18 | _messageService = messageService;
19 | }
20 |
21 | #region Implementation of IWebHookProvider
22 |
23 | public string Name { get; } = "DingTake";
24 |
25 | public async Task ProcessAsync(ExceptionlessEventModel model, IDictionary parameters)
26 | {
27 | parameters.TryGetValue("accessToken", out var accessToken);
28 | var content = await _fileTemplateService.GetContent("markdownTemplate.md", model);
29 |
30 | await _messageService.SendAsync(
31 | new Uri($"https://oapi.dingtalk.com/robot/send?access_token={accessToken}"),
32 | new DingTalkRequestMessage
33 | {
34 | Data = new MarkdownDingTalkMessage("Exceptionless 有新的事件", content)
35 | });
36 | }
37 |
38 | #endregion Implementation of IWebHookProvider
39 | }
40 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/Exceptionless.WebHook.DingTalk.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/Messages/DingTalkRequestMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Exceptionless.WebHook.DingTalk.Messages
2 | {
3 | public class DingTalkRequestMessage
4 | {
5 | public MessageType Type => Data.Type;
6 | public DingTalkMessageBase Data { get; set; }
7 | }
8 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/Messages/DingTalkResponseMessage.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Exceptionless.WebHook.DingTalk.Messages
4 | {
5 | public class DingTalkResponseMessage
6 | {
7 | [JsonProperty("errmsg")]
8 | public string Message { get; set; }
9 |
10 | [JsonProperty("errcode")]
11 | public int ErrorCode { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/Messages/MessageType.cs:
--------------------------------------------------------------------------------
1 | namespace Exceptionless.WebHook.DingTalk.Messages
2 | {
3 | public enum MessageType
4 | {
5 | Link,
6 | ActionCard,
7 | Markdown
8 | }
9 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/Messages/Messages.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Exceptionless.WebHook.DingTalk.Messages
4 | {
5 | public abstract class DingTalkMessageBase
6 | {
7 | public abstract MessageType Type { get; }
8 |
9 | protected abstract string ToContentJson();
10 | }
11 |
12 | public class MarkdownDingTalkMessage : DingTalkMessageBase
13 | {
14 | public MarkdownDingTalkMessage(string title, string text)
15 | {
16 | Title = title;
17 | Text = text;
18 | }
19 |
20 | public MarkdownDingTalkMessage()
21 | {
22 | }
23 |
24 | [JsonProperty("title")]
25 | public string Title { get; set; }
26 |
27 | [JsonProperty("text")]
28 | public string Text { get; set; }
29 |
30 | #region Overrides of DingTalkMessageBase
31 |
32 | public override MessageType Type { get; } = MessageType.Markdown;
33 |
34 | protected override string ToContentJson()
35 | {
36 | return JsonConvert.SerializeObject(new
37 | {
38 | title = Title,
39 | text = Text
40 | });
41 | }
42 |
43 | #endregion Overrides of DingTalkMessageBase
44 | }
45 |
46 | public class ActionCardDingTalkMessage : DingTalkMessageBase
47 | {
48 | public ActionCardDingTalkMessage(string title, string text, string singleTitle, string singleUrl, bool btnOrientation = false, bool hideAvatar = false)
49 | {
50 | Title = title;
51 | Text = text;
52 | SingleTitle = singleTitle;
53 | SingleUrl = singleUrl;
54 | BtnOrientation = btnOrientation;
55 | HideAvatar = hideAvatar;
56 | }
57 |
58 | public ActionCardDingTalkMessage()
59 | {
60 | }
61 |
62 | [JsonProperty("title")]
63 | public string Title { get; set; }
64 |
65 | [JsonProperty("text")]
66 | public string Text { get; set; }
67 |
68 | [JsonProperty("singleTitle")]
69 | public string SingleTitle { get; set; }
70 |
71 | [JsonProperty("singleURL")]
72 | public string SingleUrl { get; set; }
73 |
74 | [JsonProperty("btnOrientation")]
75 | public bool BtnOrientation { get; set; }
76 |
77 | [JsonProperty("hideAvatar")]
78 | public bool HideAvatar { get; set; }
79 |
80 | #region Overrides of DingTalkMessageBase
81 |
82 | public override MessageType Type { get; } = MessageType.ActionCard;
83 |
84 | protected override string ToContentJson()
85 | {
86 | return JsonConvert.SerializeObject(new
87 | {
88 | title = Title,
89 | text = Text,
90 | singleTitle = SingleTitle,
91 | singleUrl = SingleUrl,
92 | btnOrientation = BtnOrientation ? 1 : 0,
93 | hideAvatar = HideAvatar ? 1 : 0
94 | });
95 | }
96 |
97 | #endregion Overrides of DingTalkMessageBase
98 | }
99 |
100 | public class LinkMessageDingTalkMessage : DingTalkMessageBase
101 | {
102 | public LinkMessageDingTalkMessage(string title, string text, string messageUrl, string picUrl = null)
103 | {
104 | Title = title;
105 | Text = text;
106 | MessageUrl = messageUrl;
107 | PicUrl = picUrl;
108 | }
109 |
110 | public LinkMessageDingTalkMessage()
111 | {
112 | }
113 |
114 | [JsonProperty("title")]
115 | public string Title { get; set; }
116 |
117 | [JsonProperty("text")]
118 | public string Text { get; set; }
119 |
120 | [JsonProperty("messageUrl")]
121 | public string MessageUrl { get; set; }
122 |
123 | [JsonProperty("picUrl")]
124 | public string PicUrl { get; set; }
125 |
126 | #region Overrides of DingTalkMessageBase
127 |
128 | public override MessageType Type { get; } = MessageType.Link;
129 |
130 | protected override string ToContentJson()
131 | {
132 | return JsonConvert.SerializeObject(new
133 | {
134 | title = Title,
135 | text = Text,
136 | messageUrl = MessageUrl,
137 | picUrl = PicUrl
138 | });
139 | }
140 |
141 | #endregion Overrides of DingTalkMessageBase
142 | }
143 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/Services/MessageService.cs:
--------------------------------------------------------------------------------
1 | using Exceptionless.WebHook.DingTalk.Messages;
2 | using Exceptionless.WebHook.DingTalk.Utilitys;
3 | using Microsoft.Extensions.Logging;
4 | using Newtonsoft.Json;
5 | using Rabbit.Extensions.DependencyInjection;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Net.Http;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace Exceptionless.WebHook.DingTalk.Services
13 | {
14 | public class MessageService : ISingletonDependency
15 | {
16 | private readonly ILogger _logger;
17 |
18 | public MessageService(ILogger logger)
19 | {
20 | _logger = logger;
21 | }
22 |
23 | public async Task SendAsync(Uri url, DingTalkRequestMessage message)
24 | {
25 | if (_logger.IsEnabled(LogLevel.Debug))
26 | _logger.LogDebug($"准备向 {url} 发送数据");
27 |
28 | var type = StringUtilitys.ToCamelCase(message.Type.ToString());
29 | var data = new Dictionary
30 | {
31 | {"msgtype", type},
32 | {type, message.Data}
33 | };
34 |
35 | var json = JsonConvert.SerializeObject(data);
36 |
37 | if (_logger.IsEnabled(LogLevel.Debug))
38 | _logger.LogDebug($"send dingtalk json:{json}");
39 |
40 | string responseContent = null;
41 | try
42 | {
43 | using (var client = new HttpClient())
44 | {
45 | var result = await client.PostAsync(url, new StringContent(json, Encoding.UTF8, "application/json"));
46 | responseContent = await result.Content.ReadAsStringAsync();
47 |
48 | if (_logger.IsEnabled(LogLevel.Debug))
49 | _logger.LogDebug(responseContent);
50 |
51 | var response = JsonConvert.DeserializeObject(responseContent);
52 | if (response.ErrorCode > 0)
53 | _logger.LogError($"钉钉返回错误消息:{responseContent}");
54 | }
55 | }
56 | catch (Exception e)
57 | {
58 | _logger.LogError(0, e, $"向钉钉发送消息时失败,钉钉返回的消息:{responseContent},发送的数据:{json}。");
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Exceptionless.WebHook.DingTalk/Utilitys/StringUtilitys.cs:
--------------------------------------------------------------------------------
1 | namespace Exceptionless.WebHook.DingTalk.Utilitys
2 | {
3 | public class StringUtilitys
4 | {
5 | public static string ToCamelCase(string str)
6 | {
7 | if (string.IsNullOrEmpty(str))
8 | return str;
9 |
10 | var first = str.Substring(0, 1);
11 | var last = str.Substring(1);
12 | return first.ToLower() + last;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/AppSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Web
2 | {
3 | public class AppSettings
4 | {
5 | public string[] EnableModules { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/Middleware/ExceptionlessWebhookMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Exceptionless.WebHook.Abstractions;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.Extensions.Logging;
4 | using Newtonsoft.Json;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Threading.Tasks;
10 |
11 | namespace Web.Middleware
12 | {
13 | public class ExceptionlessWebhookMiddleware
14 | {
15 | #region Field
16 |
17 | private readonly RequestDelegate _next;
18 | private readonly IEnumerable _webHookProviders;
19 | private readonly ILogger _logger;
20 |
21 | #endregion Field
22 |
23 | #region Constructor
24 |
25 | public ExceptionlessWebhookMiddleware(RequestDelegate next, IEnumerable webHookProviders, ILogger logger)
26 | {
27 | _next = next;
28 | _webHookProviders = webHookProviders;
29 | _logger = logger;
30 | if (_logger.IsEnabled(LogLevel.Debug))
31 | _logger.LogDebug($"全部处理程序数量:{_webHookProviders.Count()}");
32 | }
33 |
34 | #endregion Constructor
35 |
36 | public async Task Invoke(HttpContext context)
37 | {
38 | var request = context.Request;
39 |
40 | if (request.Method != HttpMethods.Post)
41 | {
42 | await _next(context);
43 | return;
44 | }
45 |
46 | if (_logger.IsEnabled(LogLevel.Debug))
47 | _logger.LogDebug("接收到 Webhook 请求。");
48 |
49 | var content = string.Empty;
50 | try
51 | {
52 | using (var reader = new StreamReader(request.Body))
53 | content = await reader.ReadToEndAsync();
54 |
55 | if (_logger.IsEnabled(LogLevel.Debug))
56 | _logger.LogDebug($"接收到 Webhook 请求:{content}");
57 |
58 | var model = JsonConvert.DeserializeObject(content);
59 |
60 | var providers = GetProviders(request.Query);
61 |
62 | var parameters = new Dictionary(request.Query.ToDictionary(i => i.Key, i => i.Value.ToString()), StringComparer.OrdinalIgnoreCase);
63 | foreach (var provider in providers)
64 | {
65 | try
66 | {
67 | _logger.LogDebug($"使用{provider.Name}处理中");
68 | await provider.ProcessAsync(model, parameters);
69 | }
70 | catch (Exception e)
71 | {
72 | _logger.LogError(0, e, $"处理 {provider.Name} Webhook 时发生了异常,内容:{content}。");
73 | }
74 | }
75 | }
76 | catch (Exception e)
77 | {
78 | _logger.LogError(0, e, $"处理 Webhook 失败,内容:{content}。");
79 | throw;
80 | }
81 | }
82 |
83 | #region Private Method
84 |
85 | private IEnumerable GetProviders(IQueryCollection query)
86 | {
87 | var providers = _webHookProviders;
88 | if (!query.TryGetValue("webhooks", out var webhooks))
89 | return providers;
90 |
91 | var useHooks = webhooks.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
92 | providers = _webHookProviders.Where(
93 | i => useHooks.Any(z => string.Equals(z, i.Name, StringComparison.Ordinal)));
94 | return providers;
95 | }
96 |
97 | #endregion Private Method
98 | }
99 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Logging;
4 | using NLog.Web;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 |
9 | namespace Web
10 | {
11 | public class Program
12 | {
13 | public static void Main(string[] args)
14 | {
15 | BuildWebHost(args).Run();
16 | }
17 |
18 | public static IWebHost BuildWebHost(string[] args) =>
19 | WebHost.CreateDefaultBuilder(args)
20 | .ConfigureLogging((ctx, builder) =>
21 | {
22 | IEnumerable GetNlogConfigFiles()
23 | {
24 | yield return $"nlog.{ctx.HostingEnvironment.EnvironmentName}.config";
25 | yield return "nlog.config";
26 | }
27 |
28 | string GetNlogConfigFile()
29 | {
30 | return GetNlogConfigFiles().FirstOrDefault(f =>
31 | ctx.HostingEnvironment.ContentRootFileProvider.GetFileInfo(f).Exists);
32 | }
33 |
34 | var configFile = GetNlogConfigFile();
35 | if (configFile == null)
36 | throw new Exception("找不到nlog的配置文件");
37 |
38 | builder
39 | .AddConfiguration(ctx.Configuration)
40 | .AddConsole()
41 | .ConfigureNLog(configFile);
42 | })
43 | .UseStartup()
44 | .UseNLog()
45 | .Build();
46 | }
47 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Logging;
6 | using NLog.Extensions.Logging;
7 | using Rabbit.Extensions.DependencyInjection;
8 | using System;
9 | using System.Linq;
10 | using System.Reflection;
11 | using Web.Middleware;
12 |
13 | namespace Web
14 | {
15 | public class Startup
16 | {
17 | public Startup(IConfiguration configuration)
18 | {
19 | Configuration = configuration;
20 | }
21 |
22 | public IConfiguration Configuration { get; }
23 |
24 | // This method gets called by the runtime. Use this method to add services to the container.
25 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
26 | public void ConfigureServices(IServiceCollection services)
27 | {
28 | var appSettings = Configuration.Get();
29 |
30 | var enableModules = new[] { "Exceptionless.WebHook.Abstractions" }.Concat(appSettings.EnableModules)
31 | .ToDictionary(name => name, name => Assembly.Load(new AssemblyName(name)));
32 |
33 | foreach (var item in enableModules.Where(i => i.Value == null))
34 | Console.WriteLine($"无法加载模块:{item.Key}");
35 |
36 | var assemblies = enableModules.Values.ToArray();
37 | services
38 | .AddLogging()
39 | .AddMemoryCache()
40 | .AddInterfaceDependency(assemblies)
41 | .AddMvc();
42 | }
43 |
44 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
45 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
46 | {
47 | loggerFactory
48 | .AddNLog();
49 |
50 | app.UseStatusCodePages();
51 | if (env.IsDevelopment())
52 | {
53 | app.UseDeveloperExceptionPage();
54 | }
55 |
56 | app
57 | .UseMiddleware();
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | PreserveNewest
21 |
22 |
23 | PreserveNewest
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "IncludeScopes": true,
4 | "LogLevel": {
5 | "Default": "Information",
6 | "Microsoft": "Warning"
7 | }
8 | },
9 | "EnableModules": [
10 | "Exceptionless.WebHook.DingTalk"
11 | ]
12 | }
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/markdownTemplate.md:
--------------------------------------------------------------------------------
1 | # {OrganizationName}.{ProjectName} 有新的事件发生
2 | 发生时间:{OccurrenceDate}
3 | 类型:{Type}
4 | 消息:{Message}
5 | 堆标题:{StackTitle}
6 | 第一次发生时间:{FirstOccurrence}
7 | 次数:{TotalOccurrences}
8 | IP:{IpAddress}
9 | [查看事件详情]({Url})
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/nlog.Development.config:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/exceptionless.webhooks/Web/nlog.config:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/exceptionless.webhooks/exceptionless.webhooks.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26430.13
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "Web\Web.csproj", "{0A3A9747-0A0D-47A6-AB91-6DC77670C710}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.WebHook.Abstractions", "Exceptionless.WebHook.Abstractions\Exceptionless.WebHook.Abstractions.csproj", "{37702FD2-77F9-482F-A21E-B88A33AA2D40}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.WebHook.DingTalk", "Exceptionless.WebHook.DingTalk\Exceptionless.WebHook.DingTalk.csproj", "{DDA628F0-EB63-4C34-A87D-C1296F5970DC}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {0A3A9747-0A0D-47A6-AB91-6DC77670C710}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {0A3A9747-0A0D-47A6-AB91-6DC77670C710}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {0A3A9747-0A0D-47A6-AB91-6DC77670C710}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {0A3A9747-0A0D-47A6-AB91-6DC77670C710}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {37702FD2-77F9-482F-A21E-B88A33AA2D40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {37702FD2-77F9-482F-A21E-B88A33AA2D40}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {37702FD2-77F9-482F-A21E-B88A33AA2D40}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {37702FD2-77F9-482F-A21E-B88A33AA2D40}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {DDA628F0-EB63-4C34-A87D-C1296F5970DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {DDA628F0-EB63-4C34-A87D-C1296F5970DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {DDA628F0-EB63-4C34-A87D-C1296F5970DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {DDA628F0-EB63-4C34-A87D-C1296F5970DC}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | EndGlobal
35 |
--------------------------------------------------------------------------------