├── .editorconfig
├── .gitignore
├── EventGridDemo.Api
├── EventGridDemo.Api.csproj
├── Program.cs
├── Properties
│ └── launchSettings.json
├── appsettings.Development.json
└── appsettings.json
├── EventGridDemo.AppHost
├── .gitignore
├── EventGridDemo.AppHost.csproj
├── EventGridWebHookResource.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── appsettings.Development.json
├── appsettings.json
├── azure.yaml
├── eventgridwebhook.bicep
├── eventgridwebhookbootstrap.bicep
└── infra
│ ├── api.tmpl.yaml
│ ├── grid
│ └── eventgridwebhook.bicep
│ ├── main.bicep
│ ├── main.parameters.json
│ ├── publisher.tmpl.yaml
│ └── resources.bicep
├── EventGridDemo.Publisher
├── EventGridDemo.Publisher.csproj
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Worker.cs
├── appsettings.Development.json
└── appsettings.json
├── EventGridDemo.ServiceDefaults
├── EventGridDemo.ServiceDefaults.csproj
└── Extensions.cs
├── EventGridDemo.sln
├── EventGridWebHookBootstrap
├── EventGridWebHookBootstrap.csproj
├── Program.cs
├── Properties
│ └── launchSettings.json
├── appsettings.Development.json
└── appsettings.json
└── README.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # AZPROVISION001: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
4 | dotnet_diagnostic.AZPROVISION001.severity = none
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from `dotnet new gitignore`
5 |
6 | # dotenv files
7 | .env
8 |
9 | # User-specific files
10 | *.rsuser
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # User-specific files (MonoDevelop/Xamarin Studio)
17 | *.userprefs
18 |
19 | # Mono auto generated files
20 | mono_crash.*
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | [Ww][Ii][Nn]32/
30 | [Aa][Rr][Mm]/
31 | [Aa][Rr][Mm]64/
32 | bld/
33 | [Bb]in/
34 | [Oo]bj/
35 | [Ll]og/
36 | [Ll]ogs/
37 |
38 | # Visual Studio 2015/2017 cache/options directory
39 | .vs/
40 | # Uncomment if you have tasks that create the project's static files in wwwroot
41 | #wwwroot/
42 |
43 | # Visual Studio 2017 auto generated files
44 | Generated\ Files/
45 |
46 | # MSTest test Results
47 | [Tt]est[Rr]esult*/
48 | [Bb]uild[Ll]og.*
49 |
50 | # NUnit
51 | *.VisualState.xml
52 | TestResult.xml
53 | nunit-*.xml
54 |
55 | # Build Results of an ATL Project
56 | [Dd]ebugPS/
57 | [Rr]eleasePS/
58 | dlldata.c
59 |
60 | # Benchmark Results
61 | BenchmarkDotNet.Artifacts/
62 |
63 | # .NET
64 | project.lock.json
65 | project.fragment.lock.json
66 | artifacts/
67 |
68 | # Tye
69 | .tye/
70 |
71 | # ASP.NET Scaffolding
72 | ScaffoldingReadMe.txt
73 |
74 | # StyleCop
75 | StyleCopReport.xml
76 |
77 | # Files built by Visual Studio
78 | *_i.c
79 | *_p.c
80 | *_h.h
81 | *.ilk
82 | *.meta
83 | *.obj
84 | *.iobj
85 | *.pch
86 | *.pdb
87 | *.ipdb
88 | *.pgc
89 | *.pgd
90 | *.rsp
91 | *.sbr
92 | *.tlb
93 | *.tli
94 | *.tlh
95 | *.tmp
96 | *.tmp_proj
97 | *_wpftmp.csproj
98 | *.log
99 | *.tlog
100 | *.vspscc
101 | *.vssscc
102 | .builds
103 | *.pidb
104 | *.svclog
105 | *.scc
106 |
107 | # Chutzpah Test files
108 | _Chutzpah*
109 |
110 | # Visual C++ cache files
111 | ipch/
112 | *.aps
113 | *.ncb
114 | *.opendb
115 | *.opensdf
116 | *.sdf
117 | *.cachefile
118 | *.VC.db
119 | *.VC.VC.opendb
120 |
121 | # Visual Studio profiler
122 | *.psess
123 | *.vsp
124 | *.vspx
125 | *.sap
126 |
127 | # Visual Studio Trace Files
128 | *.e2e
129 |
130 | # TFS 2012 Local Workspace
131 | $tf/
132 |
133 | # Guidance Automation Toolkit
134 | *.gpState
135 |
136 | # ReSharper is a .NET coding add-in
137 | _ReSharper*/
138 | *.[Rr]e[Ss]harper
139 | *.DotSettings.user
140 |
141 | # TeamCity is a build add-in
142 | _TeamCity*
143 |
144 | # DotCover is a Code Coverage Tool
145 | *.dotCover
146 |
147 | # AxoCover is a Code Coverage Tool
148 | .axoCover/*
149 | !.axoCover/settings.json
150 |
151 | # Coverlet is a free, cross platform Code Coverage Tool
152 | coverage*.json
153 | coverage*.xml
154 | coverage*.info
155 |
156 | # Visual Studio code coverage results
157 | *.coverage
158 | *.coveragexml
159 |
160 | # NCrunch
161 | _NCrunch_*
162 | .*crunch*.local.xml
163 | nCrunchTemp_*
164 |
165 | # MightyMoose
166 | *.mm.*
167 | AutoTest.Net/
168 |
169 | # Web workbench (sass)
170 | .sass-cache/
171 |
172 | # Installshield output folder
173 | [Ee]xpress/
174 |
175 | # DocProject is a documentation generator add-in
176 | DocProject/buildhelp/
177 | DocProject/Help/*.HxT
178 | DocProject/Help/*.HxC
179 | DocProject/Help/*.hhc
180 | DocProject/Help/*.hhk
181 | DocProject/Help/*.hhp
182 | DocProject/Help/Html2
183 | DocProject/Help/html
184 |
185 | # Click-Once directory
186 | publish/
187 |
188 | # Publish Web Output
189 | *.[Pp]ublish.xml
190 | *.azurePubxml
191 | # Note: Comment the next line if you want to checkin your web deploy settings,
192 | # but database connection strings (with potential passwords) will be unencrypted
193 | *.pubxml
194 | *.publishproj
195 |
196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
197 | # checkin your Azure Web App publish settings, but sensitive information contained
198 | # in these scripts will be unencrypted
199 | PublishScripts/
200 |
201 | # NuGet Packages
202 | *.nupkg
203 | # NuGet Symbol Packages
204 | *.snupkg
205 | # The packages folder can be ignored because of Package Restore
206 | **/[Pp]ackages/*
207 | # except build/, which is used as an MSBuild target.
208 | !**/[Pp]ackages/build/
209 | # Uncomment if necessary however generally it will be regenerated when needed
210 | #!**/[Pp]ackages/repositories.config
211 | # NuGet v3's project.json files produces more ignorable files
212 | *.nuget.props
213 | *.nuget.targets
214 |
215 | # Microsoft Azure Build Output
216 | csx/
217 | *.build.csdef
218 |
219 | # Microsoft Azure Emulator
220 | ecf/
221 | rcf/
222 |
223 | # Windows Store app package directories and files
224 | AppPackages/
225 | BundleArtifacts/
226 | Package.StoreAssociation.xml
227 | _pkginfo.txt
228 | *.appx
229 | *.appxbundle
230 | *.appxupload
231 |
232 | # Visual Studio cache files
233 | # files ending in .cache can be ignored
234 | *.[Cc]ache
235 | # but keep track of directories ending in .cache
236 | !?*.[Cc]ache/
237 |
238 | # Others
239 | ClientBin/
240 | ~$*
241 | *~
242 | *.dbmdl
243 | *.dbproj.schemaview
244 | *.jfm
245 | *.pfx
246 | *.publishsettings
247 | orleans.codegen.cs
248 |
249 | # Including strong name files can present a security risk
250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
251 | #*.snk
252 |
253 | # Since there are multiple workflows, uncomment next line to ignore bower_components
254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
255 | #bower_components/
256 |
257 | # RIA/Silverlight projects
258 | Generated_Code/
259 |
260 | # Backup & report files from converting an old project file
261 | # to a newer Visual Studio version. Backup files are not needed,
262 | # because we have git ;-)
263 | _UpgradeReport_Files/
264 | Backup*/
265 | UpgradeLog*.XML
266 | UpgradeLog*.htm
267 | ServiceFabricBackup/
268 | *.rptproj.bak
269 |
270 | # SQL Server files
271 | *.mdf
272 | *.ldf
273 | *.ndf
274 |
275 | # Business Intelligence projects
276 | *.rdl.data
277 | *.bim.layout
278 | *.bim_*.settings
279 | *.rptproj.rsuser
280 | *- [Bb]ackup.rdl
281 | *- [Bb]ackup ([0-9]).rdl
282 | *- [Bb]ackup ([0-9][0-9]).rdl
283 |
284 | # Microsoft Fakes
285 | FakesAssemblies/
286 |
287 | # GhostDoc plugin setting file
288 | *.GhostDoc.xml
289 |
290 | # Node.js Tools for Visual Studio
291 | .ntvs_analysis.dat
292 | node_modules/
293 |
294 | # Visual Studio 6 build log
295 | *.plg
296 |
297 | # Visual Studio 6 workspace options file
298 | *.opt
299 |
300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
301 | *.vbw
302 |
303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
304 | *.vbp
305 |
306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
307 | *.dsw
308 | *.dsp
309 |
310 | # Visual Studio 6 technical files
311 | *.ncb
312 | *.aps
313 |
314 | # Visual Studio LightSwitch build output
315 | **/*.HTMLClient/GeneratedArtifacts
316 | **/*.DesktopClient/GeneratedArtifacts
317 | **/*.DesktopClient/ModelManifest.xml
318 | **/*.Server/GeneratedArtifacts
319 | **/*.Server/ModelManifest.xml
320 | _Pvt_Extensions
321 |
322 | # Paket dependency manager
323 | .paket/paket.exe
324 | paket-files/
325 |
326 | # FAKE - F# Make
327 | .fake/
328 |
329 | # CodeRush personal settings
330 | .cr/personal
331 |
332 | # Python Tools for Visual Studio (PTVS)
333 | __pycache__/
334 | *.pyc
335 |
336 | # Cake - Uncomment if you are using it
337 | # tools/**
338 | # !tools/packages.config
339 |
340 | # Tabs Studio
341 | *.tss
342 |
343 | # Telerik's JustMock configuration file
344 | *.jmconfig
345 |
346 | # BizTalk build output
347 | *.btp.cs
348 | *.btm.cs
349 | *.odx.cs
350 | *.xsd.cs
351 |
352 | # OpenCover UI analysis results
353 | OpenCover/
354 |
355 | # Azure Stream Analytics local run output
356 | ASALocalRun/
357 |
358 | # MSBuild Binary and Structured Log
359 | *.binlog
360 |
361 | # NVidia Nsight GPU debugger configuration file
362 | *.nvuser
363 |
364 | # MFractors (Xamarin productivity tool) working folder
365 | .mfractor/
366 |
367 | # Local History for Visual Studio
368 | .localhistory/
369 |
370 | # Visual Studio History (VSHistory) files
371 | .vshistory/
372 |
373 | # BeatPulse healthcheck temp database
374 | healthchecksdb
375 |
376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
377 | MigrationBackup/
378 |
379 | # Ionide (cross platform F# VS Code tools) working folder
380 | .ionide/
381 |
382 | # Fody - auto-generated XML schema
383 | FodyWeavers.xsd
384 |
385 | # VS Code files for those working on multiple tools
386 | .vscode/*
387 | !.vscode/settings.json
388 | !.vscode/tasks.json
389 | !.vscode/launch.json
390 | !.vscode/extensions.json
391 | *.code-workspace
392 |
393 | # Local History for Visual Studio Code
394 | .history/
395 |
396 | # Windows Installer files from build outputs
397 | *.cab
398 | *.msi
399 | *.msix
400 | *.msm
401 | *.msp
402 |
403 | # JetBrains Rider
404 | *.sln.iml
405 | .idea
406 |
407 | ##
408 | ## Visual studio for Mac
409 | ##
410 |
411 |
412 | # globs
413 | Makefile.in
414 | *.userprefs
415 | *.usertasks
416 | config.make
417 | config.status
418 | aclocal.m4
419 | install-sh
420 | autom4te.cache/
421 | *.tar.gz
422 | tarballs/
423 | test-results/
424 |
425 | # Mac bundle stuff
426 | *.dmg
427 | *.app
428 |
429 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
430 | # General
431 | .DS_Store
432 | .AppleDouble
433 | .LSOverride
434 |
435 | # Icon must end with two \r
436 | Icon
437 |
438 |
439 | # Thumbnails
440 | ._*
441 |
442 | # Files that might appear in the root of a volume
443 | .DocumentRevisions-V100
444 | .fseventsd
445 | .Spotlight-V100
446 | .TemporaryItems
447 | .Trashes
448 | .VolumeIcon.icns
449 | .com.apple.timemachine.donotpresent
450 |
451 | # Directories potentially created on remote AFP share
452 | .AppleDB
453 | .AppleDesktop
454 | Network Trash Folder
455 | Temporary Items
456 | .apdisk
457 |
458 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
459 | # Windows thumbnail cache files
460 | Thumbs.db
461 | ehthumbs.db
462 | ehthumbs_vista.db
463 |
464 | # Dump file
465 | *.stackdump
466 |
467 | # Folder config file
468 | [Dd]esktop.ini
469 |
470 | # Recycle Bin used on file shares
471 | $RECYCLE.BIN/
472 |
473 | # Windows Installer files
474 | *.cab
475 | *.msi
476 | *.msix
477 | *.msm
478 | *.msp
479 |
480 | # Windows shortcuts
481 | *.lnk
482 |
483 | # Vim temporary swap files
484 | *.swp
485 |
--------------------------------------------------------------------------------
/EventGridDemo.Api/EventGridDemo.Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | net8.0
9 | enable
10 | enable
11 |
12 |
13 |
--------------------------------------------------------------------------------
/EventGridDemo.Api/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Nodes;
2 | using Microsoft.AspNetCore.HttpLogging;
3 |
4 | var builder = WebApplication.CreateBuilder(args);
5 |
6 | builder.AddServiceDefaults();
7 |
8 | builder.Services.AddHttpLogging(o =>
9 | {
10 | o.LoggingFields = HttpLoggingFields.All;
11 | });
12 |
13 | var app = builder.Build();
14 |
15 | app.UseHttpLogging();
16 |
17 | app.MapDefaultEndpoints();
18 |
19 | app.MapPost("/hook", (JsonArray gridEvents) =>
20 | {
21 | if (gridEvents.Count == 0)
22 | {
23 | return Results.NoContent();
24 | }
25 |
26 | // Webhook validation
27 | // https://learn.microsoft.com/en-us/azure/event-grid/webhook-event-delivery#validation-details
28 | var firstEvent = gridEvents[0];
29 | var validationCode = firstEvent?["data"]?["validationCode"];
30 |
31 | return Results.Ok(new { validationResponse = validationCode });
32 | });
33 |
34 | app.Run();
35 |
--------------------------------------------------------------------------------
/EventGridDemo.Api/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:4158",
8 | "sslPort": 44359
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "applicationUrl": "http://localhost:5207",
17 | "environmentVariables": {
18 | "ASPNETCORE_ENVIRONMENT": "Development"
19 | }
20 | },
21 | "https": {
22 | "commandName": "Project",
23 | "dotnetRunMessages": true,
24 | "launchBrowser": true,
25 | "applicationUrl": "https://localhost:7043;http://localhost:5207",
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | }
29 | },
30 | "IIS Express": {
31 | "commandName": "IISExpress",
32 | "launchBrowser": true,
33 | "environmentVariables": {
34 | "ASPNETCORE_ENVIRONMENT": "Development"
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/EventGridDemo.Api/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Information"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/EventGridDemo.Api/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Information"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/.gitignore:
--------------------------------------------------------------------------------
1 | .azure
2 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/EventGridDemo.AppHost.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 | true
9 | ba0c27ca-165e-4844-9c73-3802f088a369
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/EventGridWebHookResource.cs:
--------------------------------------------------------------------------------
1 | using Aspire.Hosting.Azure;
2 |
3 | public class EventGridWebHookResource(string name) : AzureBicepResource(name, templateFile: "eventgridwebhook.bicep"), IResourceWithConnectionString
4 | {
5 | public BicepOutputReference Endpoint => new("endpoint", this);
6 |
7 | public ReferenceExpression ConnectionStringExpression =>
8 | ReferenceExpression.Create($"{Endpoint}");
9 | }
10 |
11 | public static class EventGridExtensions
12 | {
13 | public static IResourceBuilder AddEventGridWebHook(this IDistributedApplicationBuilder builder, string name)
14 | {
15 | builder.AddAzureProvisioning();
16 |
17 | var resource = new EventGridWebHookResource(name);
18 | return builder.AddResource(resource)
19 | .WithParameter("topicName", name.ToLowerInvariant())
20 | .WithParameter(AzureBicepResource.KnownParameters.PrincipalId)
21 | .WithParameter(AzureBicepResource.KnownParameters.PrincipalType)
22 | .WithManifestPublishingCallback(resource.WriteToManifest);
23 | }
24 |
25 | public static IResourceBuilder WithWebhookUrl(this IResourceBuilder builder, ReferenceExpression webhookExpression)
26 | {
27 | return builder.WithParameter("webHookEndpoint", () => webhookExpression);
28 | }
29 |
30 | public static IResourceBuilder WithWebhookUrl(this IResourceBuilder builder, ReferenceExpression.ExpressionInterpolatedStringHandler webhookExpression)
31 | {
32 | return builder.WithWebhookUrl(ReferenceExpression.Create(webhookExpression));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/Program.cs:
--------------------------------------------------------------------------------
1 | var builder = DistributedApplication.CreateBuilder(args);
2 |
3 | var api = builder.AddProject("api")
4 | .WithExternalHttpEndpoints();
5 |
6 | var eventGrid = builder.AddEventGridWebHook("grid")
7 | .WithWebhookUrl($"{api.GetEndpoint("https")}/hook");
8 |
9 | builder.AddProject("publisher")
10 | .WithReference(eventGrid);
11 |
12 | builder.Build().Run();
13 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "https": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "launchBrowser": true,
8 | "applicationUrl": "https://localhost:17177;http://localhost:15068",
9 | "environmentVariables": {
10 | "ASPNETCORE_ENVIRONMENT": "Development",
11 | "DOTNET_ENVIRONMENT": "Development",
12 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21151",
13 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22237"
14 | }
15 | },
16 | "http": {
17 | "commandName": "Project",
18 | "dotnetRunMessages": true,
19 | "launchBrowser": true,
20 | "applicationUrl": "http://localhost:15068",
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development",
23 | "DOTNET_ENVIRONMENT": "Development",
24 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19050",
25 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20179"
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning",
6 | "Aspire.Hosting.Dcp": "Warning"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/azure.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
2 |
3 | name: EventGridDemo.AppHost
4 | services:
5 | app:
6 | language: dotnet
7 | project: .\EventGridDemo.AppHost.csproj
8 | host: containerapp
9 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/eventgridwebhook.bicep:
--------------------------------------------------------------------------------
1 | param topicName string
2 | param webHookEndpoint string
3 | param principalId string
4 | param principalType string
5 | param location string = resourceGroup().location
6 |
7 | // The topic name must be unique because it's represented by a DNS entry.
8 | // must be between 3-50 characters and contain only values a-z, A-Z, 0-9, and "-".
9 |
10 | resource topic 'Microsoft.EventGrid/topics@2023-12-15-preview' = {
11 | name: toLower(take('${topicName}${uniqueString(resourceGroup().id)}', 50))
12 | location: location
13 |
14 | resource eventSubscription 'eventSubscriptions' = {
15 | name: 'customSub'
16 | properties: {
17 | destination: {
18 | endpointType: 'WebHook'
19 | properties: {
20 | endpointUrl: webHookEndpoint
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | // https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/integration#eventgrid-data-sender
28 |
29 | resource EventGridRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
30 | name: guid(topic.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7'))
31 | scope: topic
32 | properties: {
33 | principalId: principalId
34 | principalType: principalType
35 | roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7')
36 | }
37 | }
38 |
39 | output endpoint string = topic.properties.endpoint
40 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/eventgridwebhookbootstrap.bicep:
--------------------------------------------------------------------------------
1 | param location string = resourceGroup().location
2 | param containerAppEnvironmentId string
3 | param webhookPath string
4 | param name string
5 |
6 | resource webhookbootstrapApp 'Microsoft.App/containerApps@2024-03-01' = {
7 | name: name
8 | location: location
9 | properties: {
10 | environmentId: containerAppEnvironmentId
11 | configuration: {
12 | ingress: {
13 | external: true
14 | targetPort: 8080
15 | }
16 | }
17 | template: {
18 | containers: [
19 | {
20 | name: name
21 | image: 'davidfowl/eventgridwebhookbootstrap'
22 | args: ['--webhookPath', webhookPath]
23 | }
24 | ]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/infra/api.tmpl.yaml:
--------------------------------------------------------------------------------
1 | location: {{ .Env.AZURE_LOCATION }}
2 | identity:
3 | type: UserAssigned
4 | userAssignedIdentities:
5 | ? "{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}"
6 | : {}
7 | properties:
8 | environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}
9 | configuration:
10 | activeRevisionsMode: single
11 | ingress:
12 | external: true
13 | targetPort: {{ targetPortOrDefault 8080 }}
14 | transport: http
15 | allowInsecure: false
16 | registries:
17 | - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}
18 | identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}
19 | template:
20 | containers:
21 | - image: {{ .Image }}
22 | name: api
23 | env:
24 | - name: AZURE_CLIENT_ID
25 | value: {{ .Env.MANAGED_IDENTITY_CLIENT_ID }}
26 | - name: ASPNETCORE_FORWARDEDHEADERS_ENABLED
27 | value: "true"
28 | - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES
29 | value: "true"
30 | - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES
31 | value: "true"
32 | - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY
33 | value: in_memory
34 | scale:
35 | minReplicas: 1
36 | tags:
37 | azd-service-name: api
38 | aspire-resource-name: api
39 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/infra/grid/eventgridwebhook.bicep:
--------------------------------------------------------------------------------
1 | param topicName string
2 | param webHookEndpoint string
3 | param principalId string
4 | param principalType string
5 | param location string = resourceGroup().location
6 |
7 | // The topic name must be unique because it's represented by a DNS entry.
8 | // must be between 3-50 characters and contain only values a-z, A-Z, 0-9, and "-".
9 |
10 | resource topic 'Microsoft.EventGrid/topics@2023-12-15-preview' = {
11 | name: toLower(take('${topicName}${uniqueString(resourceGroup().id)}', 50))
12 | location: location
13 |
14 | resource eventSubscription 'eventSubscriptions' = {
15 | name: 'customSub'
16 | properties: {
17 | destination: {
18 | endpointType: 'WebHook'
19 | properties: {
20 | endpointUrl: webHookEndpoint
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | // https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/integration#eventgrid-data-sender
28 |
29 | resource EventGridRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
30 | name: guid(topic.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7'))
31 | scope: topic
32 | properties: {
33 | principalId: principalId
34 | principalType: principalType
35 | roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7')
36 | }
37 | }
38 |
39 | output endpoint string = topic.properties.endpoint
40 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/infra/main.bicep:
--------------------------------------------------------------------------------
1 | targetScope = 'subscription'
2 |
3 | @minLength(1)
4 | @maxLength(64)
5 | @description('Name of the environment that can be used as part of naming resource convention, the name of the resource group for your application will use this name, prefixed with rg-')
6 | param environmentName string
7 |
8 | @minLength(1)
9 | @description('The location used for all deployed resources')
10 | param location string
11 |
12 | @description('Id of the user or app to assign application roles')
13 | param principalId string = ''
14 |
15 |
16 | var tags = {
17 | 'azd-env-name': environmentName
18 | }
19 |
20 | resource rg 'Microsoft.Resources/resourceGroups@2022-09-01' = {
21 | name: 'rg-${environmentName}'
22 | location: location
23 | tags: tags
24 | }
25 |
26 | module resources 'resources.bicep' = {
27 | scope: rg
28 | name: 'resources'
29 | params: {
30 | location: location
31 | tags: tags
32 | principalId: principalId
33 | }
34 | }
35 |
36 | module bootstrap '../eventgridwebhookbootstrap.bicep' = {
37 | name: 'webhookbootstrap'
38 | scope: rg
39 | params: {
40 | location: location
41 | containerAppEnvironmentId: resources.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_ID
42 | name: 'api'
43 | webhookPath: 'hook'
44 | }
45 | }
46 |
47 | module grid 'grid/eventgridwebhook.bicep' = {
48 | name: 'grid'
49 | scope: rg
50 | params: {
51 | location: location
52 | principalId: resources.outputs.MANAGED_IDENTITY_PRINCIPAL_ID
53 | principalType: 'ServicePrincipal'
54 | topicName: 'grid'
55 | webHookEndpoint: 'https://api.${resources.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN}/hook'
56 | }
57 | }
58 | output MANAGED_IDENTITY_CLIENT_ID string = resources.outputs.MANAGED_IDENTITY_CLIENT_ID
59 | output MANAGED_IDENTITY_NAME string = resources.outputs.MANAGED_IDENTITY_NAME
60 | output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = resources.outputs.AZURE_LOG_ANALYTICS_WORKSPACE_NAME
61 | output AZURE_CONTAINER_REGISTRY_ENDPOINT string = resources.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
62 | output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = resources.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID
63 | output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = resources.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_ID
64 | output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = resources.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN
65 |
66 | output GRID_ENDPOINT string = grid.outputs.endpoint
67 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/infra/main.parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "principalId": {
6 | "value": "${AZURE_PRINCIPAL_ID}"
7 | },
8 | "environmentName": {
9 | "value": "${AZURE_ENV_NAME}"
10 | },
11 | "location": {
12 | "value": "${AZURE_LOCATION}"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/infra/publisher.tmpl.yaml:
--------------------------------------------------------------------------------
1 | location: {{ .Env.AZURE_LOCATION }}
2 | identity:
3 | type: UserAssigned
4 | userAssignedIdentities:
5 | ? "{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}"
6 | : {}
7 | properties:
8 | environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}
9 | configuration:
10 | activeRevisionsMode: single
11 | registries:
12 | - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}
13 | identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}
14 | secrets:
15 | - name: connectionstrings--grid
16 | value: '{{ .Env.GRID_ENDPOINT }}'
17 | template:
18 | containers:
19 | - image: {{ .Image }}
20 | name: publisher
21 | env:
22 | - name: AZURE_CLIENT_ID
23 | value: {{ .Env.MANAGED_IDENTITY_CLIENT_ID }}
24 | - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES
25 | value: "true"
26 | - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES
27 | value: "true"
28 | - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY
29 | value: in_memory
30 | - name: ConnectionStrings__grid
31 | secretRef: connectionstrings--grid
32 | scale:
33 | minReplicas: 1
34 | tags:
35 | azd-service-name: publisher
36 | aspire-resource-name: publisher
37 |
--------------------------------------------------------------------------------
/EventGridDemo.AppHost/infra/resources.bicep:
--------------------------------------------------------------------------------
1 | @description('The location used for all deployed resources')
2 | param location string = resourceGroup().location
3 | @description('Id of the user or app to assign application roles')
4 | param principalId string = ''
5 |
6 |
7 | @description('Tags that will be applied to all resources')
8 | param tags object = {}
9 |
10 | var resourceToken = uniqueString(resourceGroup().id)
11 |
12 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
13 | name: 'mi-${resourceToken}'
14 | location: location
15 | tags: tags
16 | }
17 |
18 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
19 | name: replace('acr-${resourceToken}', '-', '')
20 | location: location
21 | sku: {
22 | name: 'Basic'
23 | }
24 | properties: {
25 | adminUserEnabled: true
26 | }
27 | tags: tags
28 | }
29 |
30 | resource caeMiRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
31 | name: guid(containerRegistry.id, managedIdentity.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))
32 | scope: containerRegistry
33 | properties: {
34 | principalId: managedIdentity.properties.principalId
35 | principalType: 'ServicePrincipal'
36 | roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
37 | }
38 | }
39 |
40 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
41 | name: 'law-${resourceToken}'
42 | location: location
43 | properties: {
44 | sku: {
45 | name: 'PerGB2018'
46 | }
47 | }
48 | tags: tags
49 | }
50 |
51 | resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2024-02-02-preview' = {
52 | name: 'cae-${resourceToken}'
53 | location: location
54 | properties: {
55 | workloadProfiles: [{
56 | workloadProfileType: 'Consumption'
57 | name: 'consumption'
58 | }]
59 | appLogsConfiguration: {
60 | destination: 'log-analytics'
61 | logAnalyticsConfiguration: {
62 | customerId: logAnalyticsWorkspace.properties.customerId
63 | sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
64 | }
65 | }
66 | }
67 | tags: tags
68 |
69 | resource aspireDashboard 'dotNetComponents' = {
70 | name: 'aspire-dashboard'
71 | properties: {
72 | componentType: 'AspireDashboard'
73 | }
74 | }
75 | }
76 | resource explicitContributorUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
77 | name: guid(containerAppEnvironment.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c'))
78 | scope: containerAppEnvironment
79 | properties: {
80 | principalId: principalId
81 | roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
82 | }
83 | }
84 |
85 | output MANAGED_IDENTITY_CLIENT_ID string = managedIdentity.properties.clientId
86 | output MANAGED_IDENTITY_NAME string = managedIdentity.name
87 | output MANAGED_IDENTITY_PRINCIPAL_ID string = managedIdentity.properties.principalId
88 | output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = logAnalyticsWorkspace.name
89 | output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = logAnalyticsWorkspace.id
90 | output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.properties.loginServer
91 | output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = managedIdentity.id
92 | output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = containerAppEnvironment.id
93 | output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = containerAppEnvironment.properties.defaultDomain
94 |
--------------------------------------------------------------------------------
/EventGridDemo.Publisher/EventGridDemo.Publisher.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | dotnet-EventGrid.Publisher-c23e70ad-09f6-4ad5-bb6e-746850cf60a6
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/EventGridDemo.Publisher/Program.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Messaging.EventGrid;
3 | using EventGrid.Publisher;
4 |
5 | var builder = Host.CreateApplicationBuilder(args);
6 |
7 | builder.AddServiceDefaults();
8 |
9 | builder.Services.AddSingleton(sp =>
10 | {
11 | var endpoint = builder.Configuration.GetConnectionString("grid") ?? throw new InvalidOperationException("EventGrid endpoint not found in configuration.");
12 | return new EventGridPublisherClient(new Uri(endpoint), new DefaultAzureCredential());
13 | });
14 |
15 | builder.Services.AddHostedService();
16 |
17 | var host = builder.Build();
18 | host.Run();
19 |
--------------------------------------------------------------------------------
/EventGridDemo.Publisher/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "EventGrid.Publisher": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "environmentVariables": {
8 | "DOTNET_ENVIRONMENT": "Development"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/EventGridDemo.Publisher/Worker.cs:
--------------------------------------------------------------------------------
1 | using Azure.Messaging.EventGrid;
2 |
3 | namespace EventGrid.Publisher;
4 |
5 | public class Worker(EventGridPublisherClient client, ILogger logger) : BackgroundService
6 | {
7 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
8 | {
9 | while (!stoppingToken.IsCancellationRequested)
10 | {
11 | var payload = new BinaryData(new DateEvent(DateTimeOffset.UtcNow));
12 |
13 | logger.LogInformation("Sending event at {time}", DateTimeOffset.UtcNow);
14 |
15 | // Send the events
16 | await client.SendEventsAsync([
17 | new EventGridEvent("ExampleEventSubject", "Example.EventType", "1.0", payload)
18 | ],
19 | stoppingToken);
20 |
21 | await Task.Delay(5000, stoppingToken);
22 | }
23 | }
24 |
25 | record DateEvent(DateTimeOffset Time);
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/EventGridDemo.Publisher/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/EventGridDemo.Publisher/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/EventGridDemo.ServiceDefaults/EventGridDemo.ServiceDefaults.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/EventGridDemo.ServiceDefaults/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Diagnostics.HealthChecks;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.Diagnostics.HealthChecks;
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.Extensions.ServiceDiscovery;
7 | using OpenTelemetry;
8 | using OpenTelemetry.Metrics;
9 | using OpenTelemetry.Trace;
10 |
11 | namespace Microsoft.Extensions.Hosting;
12 |
13 | public static class Extensions
14 | {
15 | public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
16 | {
17 | builder.ConfigureOpenTelemetry();
18 |
19 | builder.AddDefaultHealthChecks();
20 |
21 | builder.Services.AddServiceDiscovery();
22 |
23 | builder.Services.ConfigureHttpClientDefaults(http =>
24 | {
25 | // Turn on resilience by default
26 | http.AddStandardResilienceHandler();
27 |
28 | // Turn on service discovery by default
29 | http.AddServiceDiscovery();
30 | });
31 |
32 | // Uncomment the following to restrict the allowed schemes for service discovery.
33 | // builder.Services.Configure(options =>
34 | // {
35 | // options.AllowedSchemes = ["https"];
36 | // });
37 |
38 | return builder;
39 | }
40 |
41 | public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
42 | {
43 | builder.Logging.AddOpenTelemetry(logging =>
44 | {
45 | logging.IncludeFormattedMessage = true;
46 | logging.IncludeScopes = true;
47 | });
48 |
49 | builder.Services.AddOpenTelemetry()
50 | .WithMetrics(metrics =>
51 | {
52 | metrics.AddAspNetCoreInstrumentation()
53 | .AddHttpClientInstrumentation()
54 | .AddRuntimeInstrumentation();
55 | })
56 | .WithTracing(tracing =>
57 | {
58 | tracing.AddAspNetCoreInstrumentation()
59 | // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
60 | //.AddGrpcClientInstrumentation()
61 | .AddHttpClientInstrumentation();
62 | });
63 |
64 | builder.AddOpenTelemetryExporters();
65 |
66 | return builder;
67 | }
68 |
69 | private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
70 | {
71 | var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
72 |
73 | if (useOtlpExporter)
74 | {
75 | builder.Services.AddOpenTelemetry().UseOtlpExporter();
76 | }
77 |
78 | // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
79 | // builder.Services.AddOpenTelemetry()
80 | // .WithMetrics(metrics => metrics.AddPrometheusExporter());
81 |
82 | // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
83 | //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
84 | //{
85 | // builder.Services.AddOpenTelemetry()
86 | // .UseAzureMonitor();
87 | //}
88 |
89 | return builder;
90 | }
91 |
92 | public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
93 | {
94 | builder.Services.AddHealthChecks()
95 | // Add a default liveness check to ensure app is responsive
96 | .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
97 |
98 | return builder;
99 | }
100 |
101 | public static WebApplication MapDefaultEndpoints(this WebApplication app)
102 | {
103 | // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
104 | // app.MapPrometheusScrapingEndpoint();
105 |
106 | // Adding health checks endpoints to applications in non-development environments has security implications.
107 | // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
108 | if (app.Environment.IsDevelopment())
109 | {
110 | // All health checks must pass for app to be considered ready to accept traffic after starting
111 | app.MapHealthChecks("/health");
112 |
113 | // Only health checks tagged with the "live" tag must pass for app to be considered alive
114 | app.MapHealthChecks("/alive", new HealthCheckOptions
115 | {
116 | Predicate = r => r.Tags.Contains("live")
117 | });
118 | }
119 |
120 | return app;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/EventGridDemo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.34818.151
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventGridDemo.AppHost", "EventGridDemo.AppHost\EventGridDemo.AppHost.csproj", "{B6C4A62E-BA95-4731-B050-E3744004325C}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventGridDemo.Api", "EventGridDemo.Api\EventGridDemo.Api.csproj", "{AD754E86-1ABC-4F4E-AFCA-6CF764D42A87}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventGridDemo.Publisher", "EventGridDemo.Publisher\EventGridDemo.Publisher.csproj", "{CD9C8044-B76C-438A-8A56-A6B4B81F101C}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventGridDemo.ServiceDefaults", "EventGridDemo.ServiceDefaults\EventGridDemo.ServiceDefaults.csproj", "{28FEE277-3FEE-44B4-8569-813ECB28DE9C}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventGridWebHookBootstrap", "EventGridWebHookBootstrap\EventGridWebHookBootstrap.csproj", "{B8E03849-7A89-4EBC-864E-9A485329732A}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {B6C4A62E-BA95-4731-B050-E3744004325C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {B6C4A62E-BA95-4731-B050-E3744004325C}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {B6C4A62E-BA95-4731-B050-E3744004325C}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {B6C4A62E-BA95-4731-B050-E3744004325C}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {AD754E86-1ABC-4F4E-AFCA-6CF764D42A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {AD754E86-1ABC-4F4E-AFCA-6CF764D42A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {AD754E86-1ABC-4F4E-AFCA-6CF764D42A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {AD754E86-1ABC-4F4E-AFCA-6CF764D42A87}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {CD9C8044-B76C-438A-8A56-A6B4B81F101C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {CD9C8044-B76C-438A-8A56-A6B4B81F101C}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {CD9C8044-B76C-438A-8A56-A6B4B81F101C}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {CD9C8044-B76C-438A-8A56-A6B4B81F101C}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {28FEE277-3FEE-44B4-8569-813ECB28DE9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {28FEE277-3FEE-44B4-8569-813ECB28DE9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {28FEE277-3FEE-44B4-8569-813ECB28DE9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {28FEE277-3FEE-44B4-8569-813ECB28DE9C}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {B8E03849-7A89-4EBC-864E-9A485329732A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {B8E03849-7A89-4EBC-864E-9A485329732A}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {B8E03849-7A89-4EBC-864E-9A485329732A}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {B8E03849-7A89-4EBC-864E-9A485329732A}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {2FD4B962-ACAA-4EAB-AA44-B324DE43A6AA}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/EventGridWebHookBootstrap/EventGridWebHookBootstrap.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/EventGridWebHookBootstrap/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Nodes;
2 | using Microsoft.AspNetCore.HttpLogging;
3 |
4 | var builder = WebApplication.CreateBuilder(args);
5 |
6 | var webhookPath = builder.Configuration["WebhookPath"];
7 |
8 | if (string.IsNullOrEmpty(webhookPath))
9 | {
10 | throw new InvalidOperationException("WebhookPath is required");
11 | }
12 |
13 | builder.Services.AddHttpLogging(o =>
14 | {
15 | o.LoggingFields = HttpLoggingFields.All;
16 | });
17 |
18 | var app = builder.Build();
19 |
20 | app.Logger.LogInformation("Webhook path: {webhookPath}", webhookPath);
21 |
22 | app.UseHttpLogging();
23 |
24 | app.MapPost(webhookPath, (JsonArray gridEvents) =>
25 | {
26 | if (gridEvents.Count == 0)
27 | {
28 | return Results.NoContent();
29 | }
30 |
31 | // Webhook validation
32 | // https://learn.microsoft.com/en-us/azure/event-grid/webhook-event-delivery#validation-details
33 | var firstEvent = gridEvents[0];
34 | var validationCode = firstEvent?["data"]?["validationCode"];
35 |
36 | return Results.Ok(new { validationResponse = validationCode });
37 | });
38 |
39 | app.Run();
40 |
--------------------------------------------------------------------------------
/EventGridWebHookBootstrap/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:6471",
8 | "sslPort": 44382
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "applicationUrl": "http://localhost:5010",
17 | "environmentVariables": {
18 | "ASPNETCORE_ENVIRONMENT": "Development"
19 | }
20 | },
21 | "https": {
22 | "commandName": "Project",
23 | "dotnetRunMessages": true,
24 | "launchBrowser": true,
25 | "applicationUrl": "https://localhost:7094;http://localhost:5010",
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | }
29 | },
30 | "IIS Express": {
31 | "commandName": "IISExpress",
32 | "launchBrowser": true,
33 | "environmentVariables": {
34 | "ASPNETCORE_ENVIRONMENT": "Development"
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/EventGridWebHookBootstrap/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/EventGridWebHookBootstrap/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## EventGrid Webhooks using .NET Aspire
2 |
3 | The code in this repository shows publish and subscribe to events using event grid in .NET Aspire. The code is made up of 2 applications, [EventGridDemo.Publisher](/EventGridDemo.Publisher) and [EventGridDemo.Api](/EventGridDemo.Api). The publisher application publishes events to the event grid topic, and the API application listens for events from the event grid topic.
4 |
5 |
6 | The `/hook` endpoint is used for validating the webhook. When Event Grid creates a new subscription, it sends a validation event to the webhook. The validation event contains a validationCode property. The application reads the validationCode from the first event and returns it as a response.
7 |
8 | The [AppHost](/EventGridDemo.AppHost/) projects creates the eventgrid topic, web hook subscription, sets up the role assignment and orchestrates the deployment of the publisher and api.
9 |
10 | ### Deployment
11 |
12 | Event grid subscriptions require the endpoint to be live. In order to get the application deployed, there must be a 2 step deployment process. This application has uses a [bootstrapper container](https://hub.docker.com/r/davidfowl/eventgridwebhookbootstrap) and
13 | [a bootstrap container app](/EventGridDemo.AppHost/eventgridwebhookbootstrap.bicep) to deploy the application. The bootstrap container is configured in [main.bicep](/EventGridDemo.AppHost/infra/main.bicep#36), which is auto-generated by using `azd infra synth`.
14 |
15 | See https://github.com/Azure/azure-dev/issues/3931 for more details.
16 |
17 | ### Known Issues
18 |
19 | 1. **Local development** - The application is not able to receive events from the Event Grid service when running locally. The application needs to be deployed to a public endpoint to receive events from the Event Grid service. Future efforts will work around this issue by setting up dev tunnels or using ngrok to expose the local endpoint to the public.
20 |
--------------------------------------------------------------------------------