{"id":18603049,"url":"https://github.com/devlooped/dotnet-eventgrid","last_synced_at":"2025-04-10T19:31:27.215Z","repository":{"id":36981085,"uuid":"277653377","full_name":"devlooped/dotnet-eventgrid","owner":"devlooped","description":"Azure Function app and accompanying dotnet global tool to monitor Azure EventGrid streams in real-time.","archived":false,"fork":false,"pushed_at":"2025-03-18T00:06:52.000Z","size":6117,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T03:34:06.631Z","etag":null,"topics":["dotnet","dotnet-tool"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devlooped.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":".github/FUNDING.yml","license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"devlooped"}},"created_at":"2020-07-06T21:33:27.000Z","updated_at":"2025-02-18T08:29:41.000Z","dependencies_parsed_at":"2024-04-18T04:39:05.613Z","dependency_job_id":"45f0b52e-0315-43bb-828c-0658d05669f2","html_url":"https://github.com/devlooped/dotnet-eventgrid","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-eventgrid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-eventgrid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-eventgrid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2Fdotnet-eventgrid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devlooped","download_url":"https://codeload.github.com/devlooped/dotnet-eventgrid/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248281400,"owners_count":21077423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","dotnet-tool"],"created_at":"2024-11-07T02:13:22.103Z","updated_at":"2025-04-10T19:31:22.199Z","avatar_url":"https://github.com/devlooped.png","language":"C#","funding_links":["https://github.com/sponsors/devlooped","https://github.com/sponsors"],"categories":[],"sub_categories":[],"readme":"![Icon](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/icon-32.png) dotnet-eventgrid\n============\n\n[![Version](https://img.shields.io/nuget/v/dotnet-eventgrid.svg?color=royalblue)](https://www.nuget.org/packages/dotnet-eventgrid)\n[![Downloads](https://img.shields.io/nuget/dt/dotnet-eventgrid.svg?color=darkmagenta)](https://www.nuget.org/packages/dotnet-eventgrid)\n[![License](https://img.shields.io/github/license/devlooped/dotnet-eventgrid.svg?color=blue)](https://github.com/devlooped/dotnet-eventgrid/blob/main/LICENSE)\n\nAn Azure Function app with an EventGrid-trigger function that forwards events \nto an Azure SignalR service, and an accompanying `dotnet` global tool to \nconnect to it and receive the streaming events in real-time.\n\n![EventGrid tool in action](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/eventgrid.gif)\n\n## Why\n\nI find the [Azure EventGrid Viewer](https://github.com/Azure-Samples/azure-event-grid-viewer) \nquite lacking and stagnating, it's [just a sample after all](https://docs.microsoft.com/en-us/samples/azure-samples/azure-event-grid-viewer/azure-event-grid-viewer/).\nAlso, I'm much more into [dotnet global tools](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools) \nthan web pages, having created a bunch of others like [dotnet-vs](https://github.com/devlooped/dotnet-vs), \n[guit](https://github.com/devlooped/guit), [dotnet-eventgrid](https://github.com/devlooped/dotnet-eventgrid) and \n[dotnet-config](https://github.com/devlooped/dotnet-config) ¯\\_(ツ)_/¯\n\n## Install\n\nNow you can install the dotnet tool that connects to your cloud infrastructure:\n\n```\ndotnet tool install -g dotnet-eventgrid\n```\n\nUpdate:\n\n```\ndotnet tool update -g dotnet-eventgrid\n```\n\n\u003c!-- #tool --\u003e\n\nThis tool provides a real-time view of Azure EventGrid events, by connecting to an Azure SignalR \nservice through an Azure Function app. The function app is triggered by EventGrid events and forwards \nthem to the SignalR service, which the tool connects to and receives the events in real-time.\n\n## Usage\n\n```\nUsage: eventgrid [url] -[property]* +[property[=minimatch]]*\n      +all                    Render all properties\n      -property               Exclude a property\n      +property[=minimatch]   Include a property, optionally filtering\n                              with the given the minimatch expression.\n      jq=expression           When rendering event data containing JSON,\n                              apply the given JQ expression. Learn more at\n                              https://stedolan.github.io/jq/\n\nExamples:\n- Include all event properties, for topic ending in 'System'\n      eventgrid https://mygrid.com +all +topic=**/System\n\n- Exclude data property and filter for specific event types\n      eventgrid https://mygrid.com -data +eventType=Login\n\n- Filter using synthetized path property '{domain}/{topic}/{subject}/{eventType}'\n      eventgrid https://mygrid.com +path=MyApp/**/Login\n\n- Filter using synthetized path property for a specific event and user (subject)\n      eventgrid https://mygrid.com +path=MyApp/*/1bQUI/Login\n\n- Render sponsorship action, sponsorable login and sponsor login from GH webhook JSON event data:\n      eventgrid https://mygrid.com jq=\"{ action: .action, sponsorable: .sponsorship.sponsorable.login, sponsor: .sponsorship.sponsor.login }\"\n```\n\n*eventgrid* also supports [.netconfig](https://dotnetconfig.org) for configuring \narguments:\n\n```gitconfig\n[eventgrid]\n    url = https://events.mygrid.com\n\n    # filters that events must pass to be rendered\n    filter = path=MyApp/**/Login\n    filter = eventType=*System*\n\n    # properties to include in the event rendering\n    include = EventType\n    include = Subject\n\n    # properties to exclude from event rendering\n    exclude = data\n\n    # apply jq when rendering JSON data payloads\n    jq = \"{ action: .action, sponsor: .sponsorship.sponsor.login }\"\n```\n\nThe `url` is the address of your deployed function app, which can optionally \nhave an `?key=[access-key]` query string with the same value specified in the \nFunction App configuration settings with the name `AccessKey`. If present, it \nwill be used as a shared secret to authorize the SignalR stream connection. \nIt's passed as the `X-Authorization` custom header and checked by the function \nduring SignalR connection negotiation.\n\nKeep in mind that the built-in EventGrid format for `topic` is rather unwieldy: \n`/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.EventGrid/domains/{domainName}/topics/{topicName}`. \nFor this reason, we also provide a synthetized `path` property with the much \nsimpler format `{domain}/{topic}/{subject}/{eventType}`, which makes filtering \nwith the [minimatch](https://github.com/isaacs/minimatch) format much more \nconvenient.\n\n\u003c!-- #tool --\u003e\n\nIf you already know how to deploy an Azure SignalR service, you can safely \nskip the following section.\n\n## Deploy\n\nThe dotnet global tool `eventgrid` connects to a SignalR service that broadcasts events with a \nspecific format (basically, just JSON-serialized [EventGridEvent](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.eventgrid.models.eventgridevent?view=azure-dotnet) \nobjects). In order to receive those, we need to connect an EventGrid subscription (thorugh an \nAzure function) to SignalR. Since the resources, cost and privacy issues involved are non-trivial, \nwe don't provide a ready-made service you can just connect your EventGrid events to. \n\nInstructions to deploy the cloud pieces on your own Azure subscription:\n\n1. The first step to getting your own event grid events routed to the tool is to \n   set up a [Azure SignalR service](https://portal.azure.com/#create/Microsoft.SignalRGalleryPackage) if \n   you don't have one already. There is a [free tier](https://azure.microsoft.com/en-us/pricing/details/signalr-service/) \n   that allows 20 simulaneous connections and up to 20k messages per day.\n   Once created, open the Settings \u003e Keys pane and copy the `Connection String`. \n   Make sure you pick `Serverless` for the service mode.\n\n    \u003e ![SignalR Connection String](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/signalr.png)\n\n3. Next comes the [Function App](https://portal.azure.com/#create/Microsoft.FunctionApp). Create \n   an empty one, using .NET Core 3.1. The simplest way to deploy the code to it is to select the \n   `Deployment Center` pane, select `GitHub` for source control (point it to your fork of this repo) \n   and `App Service build service` for the build provider.\n\n    \u003e ![GitHub source control](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/github.png)\n\n    \u003e ![App Service build service](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/kudu.png)\n\n4. Now we need to configure a couple application settings in the function app:\n   * `AzureSignalRConnectionString`: set it to the value copied in step 2.\n   * Optionally, create an `AccessKey` value with an arbitrary string to use as a shared \n     secret to authorize connections from the client. You will need to append that key to \n     the url passed to the `eventgrid` tool, like `eventgrid https://myfunc.azurewebsites.net/?key=...`\n\n    \u003e ![Function App configuration](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/configuration.png)\n\n5. The final step is to start sending events to the function app just created. \n   Go to all the relevant EventGrid services you have (or [create a new one](https://portal.azure.com/#create/Microsoft.EventGridDomain)) \n   and set up the subscriptions to push as much or as little as you need to visualize \n   on the tool. Keep in mind that the tool can also do filtering on the client side, \n   so that you don't need to constantly update the subscriptions. During development, \n   it can be convenient to just create a single global subscription with no filters \n   and just filter on the client. Beware of the SignalR service limits for the tier \n   you have selected, though.\n\n   You just need to create a new Event Subscription and select the `Azure Function` \n   endpoint type, and point it to the deployed function app from step 3.\n\n    \u003e ![New Event Subscription](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/eventgrid.png)\n\n   The function will be named `publish` once you select the right subscription, \n   resource group and function app\n\n    \u003e ![Subscription Endpoint](https://raw.githubusercontent.com/devlooped/dotnet-eventgrid/main/img/subscription.png)\n\n\n### Local Development\n\nWhen running the function app locally, use [dotnet user-secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-7.0\u0026tabs=windows#set-a-secret) \nto set the `AzureSignalRConnectionString` and (optional) `AccessKey` values.\n\n## Testing events\n\nPushing test events to EventGrid is quite simple. Provided you have a package \nreference to `Microsoft.Azure.EventGrid`, you can use the following snippet \nof C# (for example in the most excelent [LINQPad](https://www.linqpad.net/) tool, \nor in a simple top-level C# 9 program) to push some events:\n\n```csharp\nvar domain = \"YOUR_EVENT_GRID_DOMAIN_ENDPOINT_HOSTNAME\";                // From the Overview pane\nvar credentials = new TopicCredentials(\"YOUR_EVENT_GRID_ACCESS_KEY\");   // From Access keys pane\n\nvar events = new List\u003cEventGridEvent\u003e\n{\n    new EventGridEvent(\n        id: Guid.NewGuid().ToString(\"n\"), \n        subject: \"1bQUI\", \n        data: JsonConvert.SerializeObject(new { FirstName = \"Daniel\", LastName = \"Cazzulino\" }), \n        eventType: \"Login\", \n        eventTime: DateTime.UtcNow, \n        dataVersion: \"1.0\", \n        topic: \"Devlooped.MyApp\"),\n    new EventGridEvent(\n        id: Guid.NewGuid().ToString(\"n\"), \n        subject: \"1XKDw\", \n        data: JsonConvert.SerializeObject(new { FirstName = \"Pablo\", LastName = \"Galiano\" }), \n        eventType: \"LoginFailed\", \n        eventTime: DateTime.UtcNow, \n        dataVersion: \"1.0\", \n        topic: \"Devlooped.MyApp\"),\n    // ...\n};\n\nusing (var client = new EventGridClient(credentials))\n{\n    foreach (var e in events)\n    {\n        await client.PublishEventsAsync(domain, new List\u003cEventGridEvent\u003e { e });\n        Thread.Sleep(1000);\n    }\n}\n```\n\nThe above was pretty much what we used to create the animated gif at the top.\n\n\u003c!-- include https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n# Sponsors \n\n\u003c!-- sponsors.md --\u003e\n[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png \"Clarius Org\")](https://github.com/clarius)\n[![Kirill Osenkov](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KirillOsenkov.png \"Kirill Osenkov\")](https://github.com/KirillOsenkov)\n[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png \"MFB Technologies, Inc.\")](https://github.com/MFB-Technologies-Inc)\n[![Stephen Shaw](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/decriptor.png \"Stephen Shaw\")](https://github.com/decriptor)\n[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png \"Torutek\")](https://github.com/torutek-gh)\n[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png \"DRIVE.NET, Inc.\")](https://github.com/drivenet)\n[![Ashley Medway](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/AshleyMedway.png \"Ashley Medway\")](https://github.com/AshleyMedway)\n[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png \"Keith Pickford\")](https://github.com/Keflon)\n[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png \"Thomas Bolon\")](https://github.com/tbolon)\n[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png \"Kori Francis\")](https://github.com/kfrancis)\n[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png \"Toni Wenzel\")](https://github.com/twenzel)\n[![Giorgi Dalakishvili](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Giorgi.png \"Giorgi Dalakishvili\")](https://github.com/Giorgi)\n[![Mike James](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MikeCodesDotNET.png \"Mike James\")](https://github.com/MikeCodesDotNET)\n[![Uno Platform](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/unoplatform.png \"Uno Platform\")](https://github.com/unoplatform)\n[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png \"Dan Siegel\")](https://github.com/dansiegel)\n[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png \"Reuben Swartz\")](https://github.com/rbnswartz)\n[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png \"Jacob Foshee\")](https://github.com/jfoshee)\n[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png \"\")](https://github.com/Mrxx99)\n[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png \"Eric Johnson\")](https://github.com/eajhnsn1)\n[![Certify The Web](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/certifytheweb.png \"Certify The Web\")](https://github.com/certifytheweb)\n[![Ix Technologies B.V.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/IxTechnologies.png \"Ix Technologies B.V.\")](https://github.com/IxTechnologies)\n[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png \"David JENNI\")](https://github.com/davidjenni)\n[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png \"Jonathan \")](https://github.com/Jonathan-Hickey)\n[![Oleg Kyrylchuk](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/okyrylchuk.png \"Oleg Kyrylchuk\")](https://github.com/okyrylchuk)\n[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png \"Charley Wu\")](https://github.com/akunzai)\n[![Jakob Tikjøb Andersen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jakobt.png \"Jakob Tikjøb Andersen\")](https://github.com/jakobt)\n[![Seann Alexander](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/seanalexander.png \"Seann Alexander\")](https://github.com/seanalexander)\n[![Tino Hager](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tinohager.png \"Tino Hager\")](https://github.com/tinohager)\n[![Mark Seemann](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ploeh.png \"Mark Seemann\")](https://github.com/ploeh)\n[![Angelo Belchior](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/angelobelchior.png \"Angelo Belchior\")](https://github.com/angelobelchior)\n[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png \"Ken Bonny\")](https://github.com/KenBonny)\n[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png \"Simon Cropp\")](https://github.com/SimonCropp)\n[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png \"agileworks-eu\")](https://github.com/agileworks-eu)\n[![sorahex](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png \"sorahex\")](https://github.com/sorahex)\n[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png \"Zheyu Shen\")](https://github.com/arsdragonfly)\n[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png \"Vezel\")](https://github.com/vezel-dev)\n[![Michael Staib](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/michaelstaib.png \"Michael Staib\")](https://github.com/michaelstaib)\n\n\n\u003c!-- sponsors.md --\u003e\n\n[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png \"Sponsor this project\")](https://github.com/sponsors/devlooped)\n\u0026nbsp;\n\n[Learn more about GitHub Sponsors](https://github.com/sponsors)\n\n\u003c!-- https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fdotnet-eventgrid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevlooped%2Fdotnet-eventgrid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fdotnet-eventgrid/lists"}