Browse Source

WIP

pull/937/head
eiximenis 6 years ago
parent
commit
485bb9e4a9
26 changed files with 535 additions and 17 deletions
  1. +1
    -0
      docker-compose.override.yml
  2. +22
    -0
      src/Services/Webhooks/Webhooks.API/IntegrationEvents/OrderStatusChangedToShippedIntegrationEvent.cs
  3. +29
    -0
      src/Services/Webhooks/Webhooks.API/IntegrationEvents/OrderStatusChangedToShippedIntegrationEventHandler.cs
  4. +26
    -0
      src/Services/Webhooks/Webhooks.API/Model/WebhookData.cs
  5. +2
    -1
      src/Services/Webhooks/Webhooks.API/Model/WebhookType.cs
  6. +14
    -0
      src/Services/Webhooks/Webhooks.API/Services/IWebhooksRetriever.cs
  7. +11
    -0
      src/Services/Webhooks/Webhooks.API/Services/IWebhooksSender.cs
  8. +24
    -0
      src/Services/Webhooks/Webhooks.API/Services/WebhooksRetriever.cs
  9. +49
    -0
      src/Services/Webhooks/Webhooks.API/Services/WebhooksSender.cs
  10. +4
    -9
      src/Services/Webhooks/Webhooks.API/Startup.cs
  11. +24
    -0
      src/Web/WebhookClient/Extensions/ISessionExtensions.cs
  12. +51
    -0
      src/Web/WebhookClient/HttpClientAuthorizationDelegatingHandler.cs
  13. +13
    -0
      src/Web/WebhookClient/Models/WebHookReceived.cs
  14. +14
    -0
      src/Web/WebhookClient/Models/WebhookResponse.cs
  15. +15
    -0
      src/Web/WebhookClient/Models/WebhookSubscriptionRequest.cs
  16. +14
    -4
      src/Web/WebhookClient/Pages/Index.cshtml
  17. +7
    -1
      src/Web/WebhookClient/Pages/Index.cshtml.cs
  18. +14
    -0
      src/Web/WebhookClient/Pages/RegisterWebhook.cshtml
  19. +55
    -0
      src/Web/WebhookClient/Pages/RegisterWebhook.cshtml.cs
  20. +28
    -0
      src/Web/WebhookClient/Pages/WebhooksList.cshtml
  21. +28
    -0
      src/Web/WebhookClient/Pages/WebhooksList.cshtml.cs
  22. +13
    -0
      src/Web/WebhookClient/Services/IWebhooksClient.cs
  23. +29
    -0
      src/Web/WebhookClient/Services/WebhooksClient.cs
  24. +17
    -0
      src/Web/WebhookClient/Settings.cs
  25. +30
    -2
      src/Web/WebhookClient/Startup.cs
  26. +1
    -0
      src/Web/WebhookClient/WebhookClient.csproj

+ 1
- 0
docker-compose.override.yml View File

@ -403,5 +403,6 @@ services:
- IdentityUrl=http://10.0.75.1:5105
- CallBackUrl=http://localhost:5114
- WebhooksUrl=http://webhooks.api
- SelfUrl=http://webhooks.client/
ports:
- "5114:80"

+ 22
- 0
src/Services/Webhooks/Webhooks.API/IntegrationEvents/OrderStatusChangedToShippedIntegrationEvent.cs View File

@ -0,0 +1,22 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Webhooks.API.IntegrationEvents
{
public class OrderStatusChangedToShippedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; private set; }
public string OrderStatus { get; private set; }
public string BuyerName { get; private set; }
public OrderStatusChangedToShippedIntegrationEvent(int orderId, string orderStatus, string buyerName)
{
OrderId = orderId;
OrderStatus = orderStatus;
BuyerName = buyerName;
}
}
}

+ 29
- 0
src/Services/Webhooks/Webhooks.API/IntegrationEvents/OrderStatusChangedToShippedIntegrationEventHandler.cs View File

@ -0,0 +1,29 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Webhooks.API.Model;
using Webhooks.API.Services;
namespace Webhooks.API.IntegrationEvents
{
public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToShippedIntegrationEvent>
{
private readonly IWebhooksRetriever _retriever;
private readonly IWebhooksSender _sender;
public OrderStatusChangedToShippedIntegrationEventHandler(IWebhooksRetriever retriever, IWebhooksSender sender )
{
_retriever = retriever;
_sender = sender;
}
public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event)
{
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderShipped);
var whook = new WebhookData(WebhookType.OrderShipped, @event);
await _sender.SendAll(subscriptions, whook);
}
}
}

+ 26
- 0
src/Services/Webhooks/Webhooks.API/Model/WebhookData.cs View File

@ -0,0 +1,26 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Webhooks.API.Model
{
public class WebhookData
{
public DateTime When { get; }
public string Payload { get; }
public string Type { get; }
public WebhookData(WebhookType hookType, object data)
{
When = DateTime.UtcNow;
Type = hookType.ToString();
Payload = JsonConvert.SerializeObject(data);
}
}
}

+ 2
- 1
src/Services/Webhooks/Webhooks.API/Model/WebhookType.cs View File

@ -7,6 +7,7 @@ namespace Webhooks.API.Model
{
public enum WebhookType
{
CatalogItemPriceChange = 1
CatalogItemPriceChange = 1,
OrderShipped = 2
}
}

+ 14
- 0
src/Services/Webhooks/Webhooks.API/Services/IWebhooksRetriever.cs View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Webhooks.API.Model;
namespace Webhooks.API.Services
{
public interface IWebhooksRetriever
{
Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type);
}
}

+ 11
- 0
src/Services/Webhooks/Webhooks.API/Services/IWebhooksSender.cs View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Webhooks.API.Model;
namespace Webhooks.API.Services
{
public interface IWebhooksSender
{
Task SendAll(IEnumerable<WebhookSubscription> receivers, WebhookData data);
}
}

+ 24
- 0
src/Services/Webhooks/Webhooks.API/Services/WebhooksRetriever.cs View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Webhooks.API.Infrastructure;
using Webhooks.API.Model;
namespace Webhooks.API.Services
{
public class WebhooksRetriever : IWebhooksRetriever
{
private readonly WebhooksContext _db;
public WebhooksRetriever(WebhooksContext db)
{
_db = db;
}
public async Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type)
{
var data = await _db.Subscriptions.Where(s => s.Type == type).ToListAsync();
return data;
}
}
}

+ 49
- 0
src/Services/Webhooks/Webhooks.API/Services/WebhooksSender.cs View File

@ -0,0 +1,49 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Webhooks.API.Model;
namespace Webhooks.API.Services
{
public class WebhooksSender : IWebhooksSender
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
public WebhooksSender(IHttpClientFactory httpClientFactory, ILogger<WebhooksSender> logger)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
}
public async Task SendAll(IEnumerable<WebhookSubscription> receivers, WebhookData data)
{
var client = _httpClientFactory.CreateClient();
var json = JsonConvert.SerializeObject(data);
var tasks = receivers.Select(r => OnSendData(r, json, client));
await Task.WhenAll(tasks.ToArray());
}
private Task OnSendData(WebhookSubscription subs, string jsonData, HttpClient client)
{
var request = new HttpRequestMessage()
{
RequestUri = new Uri(subs.DestUrl, UriKind.Absolute),
Method = HttpMethod.Post,
Content = new StringContent(jsonData, Encoding.UTF8, "application/json")
};
if (!string.IsNullOrWhiteSpace(subs.Token))
{
request.Headers.Add("X-eshop-whtoken", subs.Token);
}
_logger.LogDebug($"Sending hook to {subs.DestUrl} of type {subs.Type.ToString()}");
return client.SendAsync(request);
}
}
}

+ 4
- 9
src/Services/Webhooks/Webhooks.API/Startup.cs View File

@ -61,7 +61,9 @@ namespace Webhooks.API
.AddCustomAuthentication(Configuration)
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
.AddTransient<IIdentityService, IdentityService>()
.AddTransient<IGrantUrlTesterService, GrantUrlTesterService>();
.AddTransient<IGrantUrlTesterService, GrantUrlTesterService>()
.AddTransient<IWebhooksRetriever, WebhooksRetriever>()
.AddTransient<IWebhooksSender, WebhooksSender>();
var container = new ContainerBuilder();
container.Populate(services);
@ -125,6 +127,7 @@ namespace Webhooks.API
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToShippedIntegrationEvent, OrderStatusChangedToShippedIntegrationEventHandler>();
}
}
@ -287,19 +290,11 @@ namespace Webhooks.API
public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//register delegating handlers
//services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
//InfinteTimeSpan -> See: https://github.com/aspnet/HttpClientFactory/issues/194
services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan);
//add http client services
services.AddHttpClient("GrantClient")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
//.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
return services;
}


+ 24
- 0
src/Web/WebhookClient/Extensions/ISessionExtensions.cs View File

@ -0,0 +1,24 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Http
{
static class ISessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) :
JsonConvert.DeserializeObject<T>(value);
}
}
}

+ 51
- 0
src/Web/WebhookClient/HttpClientAuthorizationDelegatingHandler.cs View File

@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace WebhookClient
{
public class HttpClientAuthorizationDelegatingHandler
: DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccesor;
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor)
{
_httpContextAccesor = httpContextAccesor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var authorizationHeader = _httpContextAccesor.HttpContext
.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
}
var token = await GetToken();
if (token != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
async Task<string> GetToken()
{
const string ACCESS_TOKEN = "access_token";
return await _httpContextAccesor.HttpContext
.GetTokenAsync(ACCESS_TOKEN);
}
}
}

+ 13
- 0
src/Web/WebhookClient/Models/WebHookReceived.cs View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebhookClient.Models
{
public class WebHookReceived
{
public DateTime When { get; set; }
public string Data { get; set; }
}
}

+ 14
- 0
src/Web/WebhookClient/Models/WebhookResponse.cs View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebhookClient.Models
{
public class WebhookResponse
{
public DateTime Date { get; set; }
public string DestUrl { get; set; }
public string Token { get; set; }
}
}

+ 15
- 0
src/Web/WebhookClient/Models/WebhookSubscriptionRequest.cs View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebhookClient.Models
{
public class WebhookSubscriptionRequest
{
public string Url { get; set; }
public string Token { get; set; }
public string Event { get; set; }
public string GrantUrl { get; set; }
}
}

+ 14
- 4
src/Web/WebhookClient/Pages/Index.cshtml View File

@ -7,13 +7,23 @@
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>eShopOnContainers - Webhook client</p>
<a class="btn-primary btn" href="@Url.Action("SignIn","Account")>Login</a>
<a class="btn-primary btn" href="@Url.Action("SignIn","Account")">Login</a>
<p>Why I need to login? You only need to login <bold>to setup a new webhook</bold>.</p>
</div>
@if (User.Identity.IsAuthenticated)
{
<div class="row">
Your current Webhooks:
</div>
<div class="table">
<h3>Current webhooks invoked</h3>
<table class="table">
@foreach (var webhook in Model.WebHooksReceived)
{
<tr>
<td>@webhook.When</td>
<td><pre>@webhook.Data</pre></td>
</tr>
}
</table>
</div>
}

+ 7
- 1
src/Web/WebhookClient/Pages/Index.cshtml.cs View File

@ -4,14 +4,20 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Http;
using WebhookClient.Models;
namespace WebhookClient.Pages
{
public class IndexModel : PageModel
{
public IEnumerable<WebHookReceived> WebHooksReceived { get; private set; }
public void OnGet()
{
WebHooksReceived = HttpContext.Session.Get<IEnumerable<WebHookReceived>>("webhooks.received") ?? Enumerable.Empty<WebHookReceived>();
}
}
}

+ 14
- 0
src/Web/WebhookClient/Pages/RegisterWebhook.cshtml View File

@ -0,0 +1,14 @@
@page
@model WebhookClient.Pages.RegisterWebhookModel
@{
ViewData["Title"] = "RegisterWebhook";
}
<h3>Register webhook</h3>
<p>This page registers the "OrderShipped" Webhook by sending a POST to webhooks.</p>
<form method="post">
<p>Token: <input type="text" asp-for="Token" /></p>
<input type="submit" value="send" />
</form>

+ 55
- 0
src/Web/WebhookClient/Pages/RegisterWebhook.cshtml.cs View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using WebhookClient.Models;
namespace WebhookClient.Pages
{
public class RegisterWebhookModel : PageModel
{
private readonly Settings _settings;
private readonly IHttpClientFactory _httpClientFactory;
[BindProperty] public string Token { get; set; }
public RegisterWebhookModel(IOptions<Settings> settings, IHttpClientFactory httpClientFactory)
{
_settings = settings.Value;
_httpClientFactory = httpClientFactory;
}
public void OnGet()
{
Token = _settings.Token;
}
public async Task OnPost()
{
var protocol = Request.IsHttps ? "https" : "http";
var selfurl = !string.IsNullOrEmpty(_settings.SelfUrl) ? _settings.SelfUrl : $"{protocol}://{Request.Host}/{Request.PathBase}";
var granturl = $"{selfurl}check";
var url = $"{selfurl}webhook";
var client = _httpClientFactory.CreateClient("GrantClient");
var payload = new WebhookSubscriptionRequest()
{
Event = "OrderShipped",
GrantUrl = granturl,
Url = url,
Token = Token
};
var response = await client.PostAsync<WebhookSubscriptionRequest>(_settings.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter());
RedirectToPage("Index");
}
}
}

+ 28
- 0
src/Web/WebhookClient/Pages/WebhooksList.cshtml View File

@ -0,0 +1,28 @@
@page
@model WebhookClient.Pages.WebhooksListModel
@{
ViewData["Title"] = "WebhooksList";
}
<h1>List of Webhooks registered by user @User.Identity.Name</h1>
<table class="table">
<thead class="thead-light">
<tr>
<th scope="col">Date</th>
<th scope="col">Destination Url</th>
<th scope="col">Validation token</th>
</tr>
</thead>
@foreach (var whr in Model.Webhooks)
{
<tr>
<td>@whr.Date</td>
<td>@whr.DestUrl</td>
<td>@whr.Token</td>
</tr>
}
</table>

+ 28
- 0
src/Web/WebhookClient/Pages/WebhooksList.cshtml.cs View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebhookClient.Models;
using WebhookClient.Services;
namespace WebhookClient.Pages
{
public class WebhooksListModel : PageModel
{
private readonly IWebhooksClient _webhooksClient;
public IEnumerable<WebhookResponse> Webhooks { get; private set; }
public WebhooksListModel(IWebhooksClient webhooksClient)
{
_webhooksClient = webhooksClient;
}
public async Task OnGet()
{
Webhooks = await _webhooksClient.LoadWebhooks();
}
}
}

+ 13
- 0
src/Web/WebhookClient/Services/IWebhooksClient.cs View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebhookClient.Models;
namespace WebhookClient.Services
{
public interface IWebhooksClient
{
Task<IEnumerable<WebhookResponse>> LoadWebhooks();
}
}

+ 29
- 0
src/Web/WebhookClient/Services/WebhooksClient.cs View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WebhookClient.Models;
namespace WebhookClient.Services
{
public class WebhooksClient : IWebhooksClient
{
public async Task<IEnumerable<WebhookResponse>> LoadWebhooks()
{
return new[]{
new WebhookResponse()
{
Date = DateTime.Now,
DestUrl = "http://aaaaa.me",
Token = "3282832as2"
},
new WebhookResponse()
{
Date = DateTime.Now.Subtract(TimeSpan.FromSeconds(392)),
DestUrl = "http://bbbbb.me",
Token = "ds2"
}
};
}
}
}

+ 17
- 0
src/Web/WebhookClient/Settings.cs View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebhookClient
{
public class Settings
{
public string Token { get; set; }
public string IdentityUrl { get; set; }
public string CallBackUrl { get; set; }
public string WebhooksUrl { get; set; }
public string SelfUrl { get; set; }
}
}

+ 30
- 2
src/Web/WebhookClient/Startup.cs View File

@ -9,6 +9,8 @@ using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Net;
using System.Threading;
using WebhookClient.Services;
namespace WebhookClient
{
@ -25,7 +27,14 @@ namespace WebhookClient
public void ConfigureServices(IServiceCollection services)
{
services
.AddSession(opt =>
{
opt.Cookie.Name = ".eShopWebhooks.Session";
})
.AddConfiguration(Configuration)
.AddHttpClientServices(Configuration)
.AddCustomAuthentication(Configuration)
.AddTransient<IWebhooksClient, WebhooksClient>()
.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
@ -42,7 +51,6 @@ namespace WebhookClient
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.Map("/check", capp =>
@ -69,14 +77,20 @@ namespace WebhookClient
}
});
});
app.UseStaticFiles();
app.UseSession();
app.UseMvcWithDefaultRoute();
}
}
static class ServiceExtensions
{
public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure<Settings>(configuration);
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
var identityUrl = configuration.GetValue<string>("IdentityUrl");
@ -107,5 +121,19 @@ namespace WebhookClient
return services;
}
public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan);
//add http client services
services.AddHttpClient("GrantClient")
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
return services;
}
}
}

+ 1
- 0
src/Web/WebhookClient/WebhookClient.csproj View File

@ -12,6 +12,7 @@
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" />
</ItemGroup>
</Project>

Loading…
Cancel
Save