refactor: flatten Microsoft skills from nested to flat directory structure
Rewrote sync_microsoft_skills.py (v4) to use each SKILL.md's frontmatter 'name' field as the flat directory name under skills/, replacing the nested skills/official/microsoft/<lang>/<category>/<service>/ hierarchy. This fixes CI failures caused by the indexing, validation, and catalog scripts expecting skills/<id>/SKILL.md (depth 1). Changes: - Rewrite scripts/sync_microsoft_skills.py for flat output with collision detection - Update scripts/tests/inspect_microsoft_repo.py for flat name mapping - Update scripts/tests/test_comprehensive_coverage.py for name uniqueness checks - Delete skills/official/ nested directory - Add 129 Microsoft skills as flat directories (e.g. skills/azure-mgmt-botservice-dotnet/) - Move attribution files to docs/ (LICENSE-MICROSOFT, microsoft-skills-attribution.json) - Rebuild skills_index.json, CATALOG.md, README.md (845 total skills)
This commit is contained in:
488
skills/azure-eventgrid-dotnet/SKILL.md
Normal file
488
skills/azure-eventgrid-dotnet/SKILL.md
Normal file
@@ -0,0 +1,488 @@
|
||||
---
|
||||
name: azure-eventgrid-dotnet
|
||||
description: |
|
||||
Azure Event Grid SDK for .NET. Client library for publishing and consuming events with Azure Event Grid. Use for event-driven architectures, pub/sub messaging, CloudEvents, and EventGridEvents. Triggers: "Event Grid", "EventGridPublisherClient", "CloudEvent", "EventGridEvent", "publish events .NET", "event-driven", "pub/sub".
|
||||
package: Azure.Messaging.EventGrid
|
||||
---
|
||||
|
||||
# Azure.Messaging.EventGrid (.NET)
|
||||
|
||||
Client library for publishing events to Azure Event Grid topics, domains, and namespaces.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# For topics and domains (push delivery)
|
||||
dotnet add package Azure.Messaging.EventGrid
|
||||
|
||||
# For namespaces (pull delivery)
|
||||
dotnet add package Azure.Messaging.EventGrid.Namespaces
|
||||
|
||||
# For CloudNative CloudEvents interop
|
||||
dotnet add package Microsoft.Azure.Messaging.EventGrid.CloudNativeCloudEvents
|
||||
```
|
||||
|
||||
**Current Version**: 4.28.0 (stable)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# Topic/Domain endpoint
|
||||
EVENT_GRID_TOPIC_ENDPOINT=https://<topic-name>.<region>.eventgrid.azure.net/api/events
|
||||
EVENT_GRID_TOPIC_KEY=<access-key>
|
||||
|
||||
# Namespace endpoint (for pull delivery)
|
||||
EVENT_GRID_NAMESPACE_ENDPOINT=https://<namespace>.<region>.eventgrid.azure.net
|
||||
EVENT_GRID_TOPIC_NAME=<topic-name>
|
||||
EVENT_GRID_SUBSCRIPTION_NAME=<subscription-name>
|
||||
```
|
||||
|
||||
## Client Hierarchy
|
||||
|
||||
```
|
||||
Push Delivery (Topics/Domains)
|
||||
└── EventGridPublisherClient
|
||||
├── SendEventAsync(EventGridEvent)
|
||||
├── SendEventsAsync(IEnumerable<EventGridEvent>)
|
||||
├── SendEventAsync(CloudEvent)
|
||||
└── SendEventsAsync(IEnumerable<CloudEvent>)
|
||||
|
||||
Pull Delivery (Namespaces)
|
||||
├── EventGridSenderClient
|
||||
│ └── SendAsync(CloudEvent)
|
||||
└── EventGridReceiverClient
|
||||
├── ReceiveAsync()
|
||||
├── AcknowledgeAsync()
|
||||
├── ReleaseAsync()
|
||||
└── RejectAsync()
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### API Key Authentication
|
||||
|
||||
```csharp
|
||||
using Azure;
|
||||
using Azure.Messaging.EventGrid;
|
||||
|
||||
EventGridPublisherClient client = new(
|
||||
new Uri("https://mytopic.eastus-1.eventgrid.azure.net/api/events"),
|
||||
new AzureKeyCredential("<access-key>"));
|
||||
```
|
||||
|
||||
### Microsoft Entra ID (Recommended)
|
||||
|
||||
```csharp
|
||||
using Azure.Identity;
|
||||
using Azure.Messaging.EventGrid;
|
||||
|
||||
EventGridPublisherClient client = new(
|
||||
new Uri("https://mytopic.eastus-1.eventgrid.azure.net/api/events"),
|
||||
new DefaultAzureCredential());
|
||||
```
|
||||
|
||||
### SAS Token Authentication
|
||||
|
||||
```csharp
|
||||
string sasToken = EventGridPublisherClient.BuildSharedAccessSignature(
|
||||
new Uri(topicEndpoint),
|
||||
DateTimeOffset.UtcNow.AddHours(1),
|
||||
new AzureKeyCredential(topicKey));
|
||||
|
||||
var sasCredential = new AzureSasCredential(sasToken);
|
||||
EventGridPublisherClient client = new(
|
||||
new Uri(topicEndpoint),
|
||||
sasCredential);
|
||||
```
|
||||
|
||||
## Publishing Events
|
||||
|
||||
### EventGridEvent Schema
|
||||
|
||||
```csharp
|
||||
EventGridPublisherClient client = new(
|
||||
new Uri(topicEndpoint),
|
||||
new AzureKeyCredential(topicKey));
|
||||
|
||||
// Single event
|
||||
EventGridEvent egEvent = new(
|
||||
subject: "orders/12345",
|
||||
eventType: "Order.Created",
|
||||
dataVersion: "1.0",
|
||||
data: new { OrderId = "12345", Amount = 99.99 });
|
||||
|
||||
await client.SendEventAsync(egEvent);
|
||||
|
||||
// Batch of events
|
||||
List<EventGridEvent> events = new()
|
||||
{
|
||||
new EventGridEvent(
|
||||
subject: "orders/12345",
|
||||
eventType: "Order.Created",
|
||||
dataVersion: "1.0",
|
||||
data: new OrderData { OrderId = "12345", Amount = 99.99 }),
|
||||
new EventGridEvent(
|
||||
subject: "orders/12346",
|
||||
eventType: "Order.Created",
|
||||
dataVersion: "1.0",
|
||||
data: new OrderData { OrderId = "12346", Amount = 149.99 })
|
||||
};
|
||||
|
||||
await client.SendEventsAsync(events);
|
||||
```
|
||||
|
||||
### CloudEvent Schema
|
||||
|
||||
```csharp
|
||||
CloudEvent cloudEvent = new(
|
||||
source: "/orders/system",
|
||||
type: "Order.Created",
|
||||
data: new { OrderId = "12345", Amount = 99.99 });
|
||||
|
||||
cloudEvent.Subject = "orders/12345";
|
||||
cloudEvent.Id = Guid.NewGuid().ToString();
|
||||
cloudEvent.Time = DateTimeOffset.UtcNow;
|
||||
|
||||
await client.SendEventAsync(cloudEvent);
|
||||
|
||||
// Batch of CloudEvents
|
||||
List<CloudEvent> cloudEvents = new()
|
||||
{
|
||||
new CloudEvent("/orders", "Order.Created", new { OrderId = "1" }),
|
||||
new CloudEvent("/orders", "Order.Updated", new { OrderId = "2" })
|
||||
};
|
||||
|
||||
await client.SendEventsAsync(cloudEvents);
|
||||
```
|
||||
|
||||
### Publishing to Event Grid Domain
|
||||
|
||||
```csharp
|
||||
// Events must specify the Topic property for domain routing
|
||||
List<EventGridEvent> events = new()
|
||||
{
|
||||
new EventGridEvent(
|
||||
subject: "orders/12345",
|
||||
eventType: "Order.Created",
|
||||
dataVersion: "1.0",
|
||||
data: new { OrderId = "12345" })
|
||||
{
|
||||
Topic = "orders-topic" // Domain topic name
|
||||
},
|
||||
new EventGridEvent(
|
||||
subject: "inventory/item-1",
|
||||
eventType: "Inventory.Updated",
|
||||
dataVersion: "1.0",
|
||||
data: new { ItemId = "item-1" })
|
||||
{
|
||||
Topic = "inventory-topic"
|
||||
}
|
||||
};
|
||||
|
||||
await client.SendEventsAsync(events);
|
||||
```
|
||||
|
||||
### Custom Serialization
|
||||
|
||||
```csharp
|
||||
using System.Text.Json;
|
||||
|
||||
var serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var customSerializer = new JsonObjectSerializer(serializerOptions);
|
||||
|
||||
EventGridEvent egEvent = new(
|
||||
subject: "orders/12345",
|
||||
eventType: "Order.Created",
|
||||
dataVersion: "1.0",
|
||||
data: customSerializer.Serialize(new OrderData { OrderId = "12345" }));
|
||||
|
||||
await client.SendEventAsync(egEvent);
|
||||
```
|
||||
|
||||
## Pull Delivery (Namespaces)
|
||||
|
||||
### Send Events to Namespace Topic
|
||||
|
||||
```csharp
|
||||
using Azure;
|
||||
using Azure.Messaging;
|
||||
using Azure.Messaging.EventGrid.Namespaces;
|
||||
|
||||
var senderClient = new EventGridSenderClient(
|
||||
new Uri(namespaceEndpoint),
|
||||
topicName,
|
||||
new AzureKeyCredential(topicKey));
|
||||
|
||||
// Send single event
|
||||
CloudEvent cloudEvent = new("employee_source", "Employee.Created",
|
||||
new { Name = "John", Age = 30 });
|
||||
await senderClient.SendAsync(cloudEvent);
|
||||
|
||||
// Send batch
|
||||
await senderClient.SendAsync(new[]
|
||||
{
|
||||
new CloudEvent("source", "type", new { Name = "Alice" }),
|
||||
new CloudEvent("source", "type", new { Name = "Bob" })
|
||||
});
|
||||
```
|
||||
|
||||
### Receive and Process Events
|
||||
|
||||
```csharp
|
||||
var receiverClient = new EventGridReceiverClient(
|
||||
new Uri(namespaceEndpoint),
|
||||
topicName,
|
||||
subscriptionName,
|
||||
new AzureKeyCredential(topicKey));
|
||||
|
||||
// Receive events
|
||||
ReceiveResult result = await receiverClient.ReceiveAsync(maxEvents: 10);
|
||||
|
||||
List<string> lockTokensToAck = new();
|
||||
List<string> lockTokensToRelease = new();
|
||||
|
||||
foreach (ReceiveDetails detail in result.Details)
|
||||
{
|
||||
CloudEvent cloudEvent = detail.Event;
|
||||
string lockToken = detail.BrokerProperties.LockToken;
|
||||
|
||||
try
|
||||
{
|
||||
// Process the event
|
||||
Console.WriteLine($"Event: {cloudEvent.Type}, Data: {cloudEvent.Data}");
|
||||
lockTokensToAck.Add(lockToken);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Release for retry
|
||||
lockTokensToRelease.Add(lockToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Acknowledge successfully processed events
|
||||
if (lockTokensToAck.Any())
|
||||
{
|
||||
await receiverClient.AcknowledgeAsync(lockTokensToAck);
|
||||
}
|
||||
|
||||
// Release events for retry
|
||||
if (lockTokensToRelease.Any())
|
||||
{
|
||||
await receiverClient.ReleaseAsync(lockTokensToRelease);
|
||||
}
|
||||
```
|
||||
|
||||
### Reject Events (Dead Letter)
|
||||
|
||||
```csharp
|
||||
// Reject events that cannot be processed
|
||||
await receiverClient.RejectAsync(new[] { lockToken });
|
||||
```
|
||||
|
||||
## Consuming Events (Azure Functions)
|
||||
|
||||
### EventGridEvent Trigger
|
||||
|
||||
```csharp
|
||||
using Azure.Messaging.EventGrid;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
|
||||
|
||||
public static class EventGridFunction
|
||||
{
|
||||
[FunctionName("ProcessEventGridEvent")]
|
||||
public static void Run(
|
||||
[EventGridTrigger] EventGridEvent eventGridEvent,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation($"Event Type: {eventGridEvent.EventType}");
|
||||
log.LogInformation($"Subject: {eventGridEvent.Subject}");
|
||||
log.LogInformation($"Data: {eventGridEvent.Data}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CloudEvent Trigger
|
||||
|
||||
```csharp
|
||||
using Azure.Messaging;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
|
||||
public class CloudEventFunction
|
||||
{
|
||||
[Function("ProcessCloudEvent")]
|
||||
public void Run(
|
||||
[EventGridTrigger] CloudEvent cloudEvent,
|
||||
FunctionContext context)
|
||||
{
|
||||
var logger = context.GetLogger("ProcessCloudEvent");
|
||||
logger.LogInformation($"Event Type: {cloudEvent.Type}");
|
||||
logger.LogInformation($"Source: {cloudEvent.Source}");
|
||||
logger.LogInformation($"Data: {cloudEvent.Data}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Parsing Events
|
||||
|
||||
### Parse EventGridEvent
|
||||
|
||||
```csharp
|
||||
// From JSON string
|
||||
string json = "..."; // Event Grid webhook payload
|
||||
EventGridEvent[] events = EventGridEvent.ParseMany(BinaryData.FromString(json));
|
||||
|
||||
foreach (EventGridEvent egEvent in events)
|
||||
{
|
||||
if (egEvent.TryGetSystemEventData(out object systemEvent))
|
||||
{
|
||||
// Handle system event
|
||||
switch (systemEvent)
|
||||
{
|
||||
case StorageBlobCreatedEventData blobCreated:
|
||||
Console.WriteLine($"Blob created: {blobCreated.Url}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle custom event
|
||||
var customData = egEvent.Data.ToObjectFromJson<MyCustomData>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Parse CloudEvent
|
||||
|
||||
```csharp
|
||||
CloudEvent[] cloudEvents = CloudEvent.ParseMany(BinaryData.FromString(json));
|
||||
|
||||
foreach (CloudEvent cloudEvent in cloudEvents)
|
||||
{
|
||||
var data = cloudEvent.Data.ToObjectFromJson<MyEventData>();
|
||||
Console.WriteLine($"Type: {cloudEvent.Type}, Data: {data}");
|
||||
}
|
||||
```
|
||||
|
||||
## System Events
|
||||
|
||||
```csharp
|
||||
// Common system event types
|
||||
using Azure.Messaging.EventGrid.SystemEvents;
|
||||
|
||||
// Storage events
|
||||
StorageBlobCreatedEventData blobCreated;
|
||||
StorageBlobDeletedEventData blobDeleted;
|
||||
|
||||
// Resource events
|
||||
ResourceWriteSuccessEventData resourceCreated;
|
||||
ResourceDeleteSuccessEventData resourceDeleted;
|
||||
|
||||
// App Service events
|
||||
WebAppUpdatedEventData webAppUpdated;
|
||||
|
||||
// Container Registry events
|
||||
ContainerRegistryImagePushedEventData imagePushed;
|
||||
|
||||
// IoT Hub events
|
||||
IotHubDeviceCreatedEventData deviceCreated;
|
||||
```
|
||||
|
||||
## Key Types Reference
|
||||
|
||||
| Type | Purpose |
|
||||
|------|---------|
|
||||
| `EventGridPublisherClient` | Publish to topics/domains |
|
||||
| `EventGridSenderClient` | Send to namespace topics |
|
||||
| `EventGridReceiverClient` | Receive from namespace subscriptions |
|
||||
| `EventGridEvent` | Event Grid native schema |
|
||||
| `CloudEvent` | CloudEvents 1.0 schema |
|
||||
| `ReceiveResult` | Pull delivery response |
|
||||
| `ReceiveDetails` | Event with broker properties |
|
||||
| `BrokerProperties` | Lock token, delivery count |
|
||||
|
||||
## Event Schemas Comparison
|
||||
|
||||
| Feature | EventGridEvent | CloudEvent |
|
||||
|---------|----------------|------------|
|
||||
| Standard | Azure-specific | CNCF standard |
|
||||
| Required fields | subject, eventType, dataVersion, data | source, type |
|
||||
| Extensibility | Limited | Extension attributes |
|
||||
| Interoperability | Azure only | Cross-platform |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use CloudEvents** — Prefer CloudEvents for new implementations (industry standard)
|
||||
2. **Batch events** — Send multiple events in one call for efficiency
|
||||
3. **Use Entra ID** — Prefer managed identity over access keys
|
||||
4. **Idempotent handlers** — Events may be delivered more than once
|
||||
5. **Set event TTL** — Configure time-to-live for namespace events
|
||||
6. **Handle partial failures** — Acknowledge/release events individually
|
||||
7. **Use dead-letter** — Configure dead-letter for failed events
|
||||
8. **Validate schemas** — Validate event data before processing
|
||||
|
||||
## Error Handling
|
||||
|
||||
```csharp
|
||||
using Azure;
|
||||
|
||||
try
|
||||
{
|
||||
await client.SendEventAsync(cloudEvent);
|
||||
}
|
||||
catch (RequestFailedException ex) when (ex.Status == 401)
|
||||
{
|
||||
Console.WriteLine("Authentication failed - check credentials");
|
||||
}
|
||||
catch (RequestFailedException ex) when (ex.Status == 403)
|
||||
{
|
||||
Console.WriteLine("Authorization failed - check RBAC permissions");
|
||||
}
|
||||
catch (RequestFailedException ex) when (ex.Status == 413)
|
||||
{
|
||||
Console.WriteLine("Payload too large - max 1MB per event, 1MB total batch");
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
Console.WriteLine($"Event Grid error: {ex.Status} - {ex.Message}");
|
||||
}
|
||||
```
|
||||
|
||||
## Failover Pattern
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
var primaryClient = new EventGridPublisherClient(primaryUri, primaryKey);
|
||||
await primaryClient.SendEventsAsync(events);
|
||||
}
|
||||
catch (RequestFailedException)
|
||||
{
|
||||
// Failover to secondary region
|
||||
var secondaryClient = new EventGridPublisherClient(secondaryUri, secondaryKey);
|
||||
await secondaryClient.SendEventsAsync(events);
|
||||
}
|
||||
```
|
||||
|
||||
## Related SDKs
|
||||
|
||||
| SDK | Purpose | Install |
|
||||
|-----|---------|---------|
|
||||
| `Azure.Messaging.EventGrid` | Topics/Domains (this SDK) | `dotnet add package Azure.Messaging.EventGrid` |
|
||||
| `Azure.Messaging.EventGrid.Namespaces` | Pull delivery | `dotnet add package Azure.Messaging.EventGrid.Namespaces` |
|
||||
| `Azure.Identity` | Authentication | `dotnet add package Azure.Identity` |
|
||||
| `Microsoft.Azure.WebJobs.Extensions.EventGrid` | Azure Functions trigger | `dotnet add package Microsoft.Azure.WebJobs.Extensions.EventGrid` |
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| NuGet Package | https://www.nuget.org/packages/Azure.Messaging.EventGrid |
|
||||
| API Reference | https://learn.microsoft.com/dotnet/api/azure.messaging.eventgrid |
|
||||
| Quickstart | https://learn.microsoft.com/azure/event-grid/custom-event-quickstart |
|
||||
| Pull Delivery | https://learn.microsoft.com/azure/event-grid/pull-delivery-overview |
|
||||
| GitHub Source | https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/eventgrid/Azure.Messaging.EventGrid |
|
||||
Reference in New Issue
Block a user