Skip to content

Commit

Permalink
added eventgrid code
Browse files Browse the repository at this point in the history
  • Loading branch information
shribr committed Aug 28, 2024
1 parent 6ccacd1 commit 5253884
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 1 deletion.
59 changes: 59 additions & 0 deletions app/SharedWebComponents/Pages/CallWebAPI.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@page "/call-web-api"
@using System.Text.Json
@using System.Text.Json.Serialization
@inject IHttpClientFactory ClientFactory

<h1>Call web API from a Blazor Server Razor component</h1>

@if (getBranchesError || branches is null)
{
<p>Unable to get branches from GitHub. Please try again later.</p>
}
else
{
<ul>
@foreach (var branch in branches)
{
<li>@branch.Name</li>
}
</ul>
}

@code {
private IEnumerable<GitHubBranch>? branches = [];
private bool getBranchesError;
private bool shouldRender;

protected override bool ShouldRender() => shouldRender;

protected override async Task OnInitializedAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

var client = ClientFactory.CreateClient();

var response = await client.SendAsync(request);

if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
getBranchesError = true;
}

shouldRender = true;
}

public class GitHubBranch
{
[JsonPropertyName("name")]
public string? Name { get; set; }
}
}
182 changes: 182 additions & 0 deletions app/backend/Controllers/UpdatesController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using System.Net;
using System.Text;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Mvc;
using MinimalApi.Hubs;
using MinimalApi.Models;

namespace MinimalApi.Controllers;

[Route("api/[controller]")]
public class UpdatesController : Controller
{
#region Data Members

private bool EventTypeSubcriptionValidation
=> HttpContext.Request.Headers["aeg-event-type"].FirstOrDefault() ==
"SubscriptionValidation";

private bool EventTypeNotification
=> HttpContext.Request.Headers["aeg-event-type"].FirstOrDefault() ==
"Notification";

private readonly IHubContext<GridEventsHub> _hubContext;

#endregion

#region Constructors

public UpdatesController(IHubContext<GridEventsHub> gridEventsHubContext)
{
this._hubContext = gridEventsHubContext;
}

#endregion

#region Public Methods

[HttpOptions]
public Task<IActionResult> OptionsAsync()
{
using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
{
var webhookRequestOrigin = HttpContext.Request.Headers["WebHook-Request-Origin"].FirstOrDefault();
var webhookRequestCallback = HttpContext.Request.Headers["WebHook-Request-Callback"];
var webhookRequestRate = HttpContext.Request.Headers["WebHook-Request-Rate"];
HttpContext.Response.Headers.Append("WebHook-Allowed-Rate", "*");
HttpContext.Response.Headers.Append("WebHook-Allowed-Origin", webhookRequestOrigin);
}

return Task.FromResult<IActionResult>(Ok());
}

[HttpPost]
public async Task<IActionResult> PostAsync()
{
using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
{
var jsonContent = await reader.ReadToEndAsync();

// Check the event type.
// Return the validation code if it's
// a subscription validation request.
if (EventTypeSubcriptionValidation)
{
return await HandleValidationAsync(jsonContent);
}
else if (EventTypeNotification)
{
// Check to see if this is passed in using
// the CloudEvents schema
if (IsCloudEvent(jsonContent))
{
return await HandleCloudEventAsync(jsonContent);
}

return await HandleGridEventsAsync(jsonContent);
}

return new JsonResult(BadRequest());
}
}

#endregion

#region Private Methods

private async Task<JsonResult> HandleValidationAsync(string jsonContent)
{
var gridEvent =
JsonConvert.DeserializeObject<List<GridEvent<Dictionary<string, string>>>>(jsonContent)
?.FirstOrDefault();
if (gridEvent == null)
{
// Handle the case when the list is empty or null.
return Json(BadRequest());
}

await this._hubContext.Clients.All.SendAsync(
"gridupdate",
gridEvent.Id,
gridEvent.EventType,
gridEvent.Subject,
gridEvent.EventTime.ToLongTimeString(),
jsonContent.ToString());

// Retrieve the validation code and echo back.
var validationCode = gridEvent.Data?["validationCode"];
return new JsonResult(new
{
validationResponse = validationCode
});
}

private async Task<IActionResult> HandleGridEventsAsync(string jsonContent)
{
var events = JArray.Parse(jsonContent);
foreach (var e in events)
{
// Invoke a method on the clients for
// an event grid notiification.
var details = JsonConvert.DeserializeObject<GridEvent<dynamic>>(e.ToString());
await this._hubContext.Clients.All.SendAsync(
"gridupdate",
details?.Id,
details?.EventType,
details?.Subject,
details?.EventTime.ToString("T"),
e.ToString());
}

return Ok();
}

private async Task<IActionResult> HandleCloudEventAsync(string jsonContent)
{
var details = JsonConvert.DeserializeObject<CloudEvent<dynamic>>(jsonContent);
var eventData = JObject.Parse(jsonContent);

await this._hubContext.Clients.All.SendAsync(
"gridupdate",
details?.Id,
details?.Type,
details?.Subject,
details?.Time,
eventData.ToString()
);

return Ok();
}

private static bool IsCloudEvent(string jsonContent)
{
// Cloud events are sent one at a time, while Grid events
// are sent in an array. As a result, the JObject.Parse will
// fail for Grid events.
try
{
// Attempt to read one JSON object.
var eventData = JObject.Parse(jsonContent);

// Check for the spec version property.
var version = eventData["specversion"]?.Value<string>();
if (!string.IsNullOrEmpty(version)) return true;
}
catch (Exception e)
{
Console.WriteLine(e);
}

return false;
}

#endregion
}
30 changes: 30 additions & 0 deletions app/backend/Models/CloudEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

using Newtonsoft.Json;

namespace MinimalApi.Models;

// Reference: https://github.com/cloudevents/spec/tree/v1.0-rc1

public class CloudEvent<T> where T : class
{
[JsonProperty("specversion")]
public string? SpecVersion { get; set; }

[JsonProperty("type")]
public string? Type { get; set; }

[JsonProperty("source")]
public string? Source { get; set; }

[JsonProperty("subject")]
public string? Subject { get; set; }

[JsonProperty("id")]
public string? Id { get; set; }

[JsonProperty("time")]
public string? Time { get; set; }

[JsonProperty("data")]
public T? Data { get; set; }
}
10 changes: 10 additions & 0 deletions app/backend/Models/ErrorViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace MinimalApi.Models;

public class ErrorViewModel
{
public string? RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
13 changes: 13 additions & 0 deletions app/backend/Models/GridEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace MinimalApi.Models;

public class GridEvent<T> where T : class
{
public string? Id { get; set; }
public string? EventType { get; set; }
public string? Subject { get; set; }
public DateTime EventTime { get; set; }
public T? Data { get; set; }
public string? Topic { get; set; }
}
18 changes: 17 additions & 1 deletion app/backend/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.AspNetCore.Antiforgery;
using MinimalApi.Hubs;
; // Replace 'YourNamespace' with the actual namespace of the 'GridEventsHub' class

var builder = WebApplication.CreateBuilder(args);

Expand All @@ -11,6 +13,8 @@
builder.Services.AddSwaggerGen();
builder.Services.AddOutputCache();
builder.Services.AddControllersWithViews();
builder.Services.AddControllers();
builder.Services.AddSignalR();
builder.Services.AddRazorPages();
builder.Services.AddCrossOriginResourceSharing();
builder.Services.AddAzureServices();
Expand Down Expand Up @@ -52,7 +56,7 @@
""";
options.InstanceName = "content";


});

// set application telemetry
Expand All @@ -65,6 +69,12 @@
}
}

builder.Services.AddScoped(sp =>
new HttpClient
{
BaseAddress = new Uri(builder.Configuration["FrontendUrl"] ?? "https://localhost:5002")
});

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Expand Down Expand Up @@ -95,6 +105,12 @@
context.Response.Cookies.Append("XSRF-TOKEN", tokens?.RequestToken ?? string.Empty, new CookieOptions() { HttpOnly = false });
return next(context);
});

app.MapHub<GridEventsHub>("/hubs/gridevents");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html");

app.MapApi();
Expand Down
10 changes: 10 additions & 0 deletions app/backend/hubs/GridEventsHub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.SignalR;

namespace MinimalApi.Hubs;

public class GridEventsHub : Hub
{
public GridEventsHub()
{
}
}

0 comments on commit 5253884

Please sign in to comment.