From 711b9e0edf9c7dcd3e7700dafb428fa7a416134a Mon Sep 17 00:00:00 2001
From: cKey <35512826+colinin@users.noreply.github.com>
Date: Tue, 5 Apr 2022 11:05:58 +0800
Subject: [PATCH 1/2] fix: fix case in reference project paths
---
.../LINGYUN.Abp.WebHooks.ClientProxies.csproj | 16 --
.../AbpWebHooksClientProxiesModule.cs | 10 -
.../ClientProxiesWebhookPublisher.cs | 91 ---------
.../LINGYUN.Abp.WebHooks/FodyWeavers.xml | 3 -
.../LINGYUN.Abp.WebHooks/FodyWeavers.xsd | 30 ---
.../LINGYUN.Abp.WebHooks.csproj | 18 --
.../LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs | 54 ------
.../Abp/Webhooks/AbpWebhooksOptions.cs | 35 ----
.../BackgroundWorker/WebhookSenderJob.cs | 131 -------------
.../Abp/Webhooks/DefaultWebhookPublisher.cs | 140 --------------
.../Abp/Webhooks/DefaultWebhookSender.cs | 129 -------------
.../WebhookSubscriptionExtensions.cs | 21 ---
.../Abp/Webhooks/IWebhookDefinitionContext.cs | 16 --
.../Abp/Webhooks/IWebhookDefinitionManager.cs | 37 ----
.../Abp/Webhooks/IWebhookEventStore.cs | 18 --
.../LINGYUN/Abp/Webhooks/IWebhookManager.cs | 22 ---
.../LINGYUN/Abp/Webhooks/IWebhookPublisher.cs | 56 ------
.../Abp/Webhooks/IWebhookSendAttemptStore.cs | 25 ---
.../LINGYUN/Abp/Webhooks/IWebhookSender.cs | 16 --
.../Webhooks/IWebhookSubscriptionManager.cs | 74 --------
.../Webhooks/IWebhookSubscriptionsStore.cs | 83 ---------
.../Abp/Webhooks/NullWebhookEventStore.cs | 24 ---
.../Webhooks/NullWebhookSendAttemptStore.cs | 47 -----
.../Webhooks/NullWebhookSubscriptionsStore.cs | 65 -------
.../LINGYUN/Abp/Webhooks/WebhookDefinition.cs | 67 -------
.../Abp/Webhooks/WebhookDefinitionContext.cs | 55 ------
.../Abp/Webhooks/WebhookDefinitionManager.cs | 139 --------------
.../Abp/Webhooks/WebhookDefinitionProvider.cs | 13 --
.../LINGYUN/Abp/Webhooks/WebhookEvent.cs | 30 ---
.../Abp/Webhooks/WebhookGroupDefinition.cs | 101 ----------
.../LINGYUN/Abp/Webhooks/WebhookHeader.cs | 18 --
.../LINGYUN/Abp/Webhooks/WebhookManager.cs | 80 --------
.../LINGYUN/Abp/Webhooks/WebhookPayload.cs | 35 ----
.../Abp/Webhooks/WebhookSendAttempt.cs | 39 ----
.../LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs | 62 -------
.../Abp/Webhooks/WebhookSubscriptionInfo.cs | 60 ------
.../Webhooks/WebhookSubscriptionManager.cs | 172 ------------------
.../System/AbpStringCryptographyExtensions.cs | 13 --
38 files changed, 2045 deletions(-)
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs
delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj
deleted file mode 100644
index 251dd53d3..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
- netstandard2.0
-
-
-
-
-
-
-
-
-
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs
deleted file mode 100644
index 41e08a4a7..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using LINGYUN.Abp.WebhooksManagement;
-using Volo.Abp.Modularity;
-
-namespace LINGYUN.Abp.Webhooks.ClientProxies;
-
-[DependsOn(typeof(AbpWebhooksModule))]
-[DependsOn(typeof(WebhooksManagementHttpApiClientModule))]
-public class AbpWebHooksClientProxiesModule : AbpModule
-{
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs
deleted file mode 100644
index 9e5ae8b35..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using LINGYUN.Abp.WebhooksManagement;
-using Newtonsoft.Json;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Volo.Abp.DependencyInjection;
-
-namespace LINGYUN.Abp.Webhooks.ClientProxies;
-
-[Dependency(ReplaceServices = true)]
-public class ClientProxiesWebhookPublisher : IWebhookPublisher, ITransientDependency
-{
- protected IWebhookPublishAppService PublishAppService { get; }
-
- public ClientProxiesWebhookPublisher(
- IWebhookPublishAppService publishAppService)
- {
- PublishAppService = publishAppService;
- }
-
- public async virtual Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null)
- {
- var input = new WebhookPublishInput
- {
- WebhookName = webhookName,
- Data = JsonConvert.SerializeObject(data),
- SendExactSameData = sendExactSameData,
- };
- if (headers != null)
- {
- input.Header = new WebhooksHeaderInput
- {
- UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders,
- Headers = headers.Headers
- };
- }
-
- await PublishAsync(input);
- }
-
- public async virtual Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null)
- {
- var input = new WebhookPublishInput
- {
- WebhookName = webhookName,
- Data = JsonConvert.SerializeObject(data),
- SendExactSameData = sendExactSameData,
- TenantIds = new List
- {
- tenantId
- },
- };
- if (headers != null)
- {
- input.Header = new WebhooksHeaderInput
- {
- UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders,
- Headers = headers.Headers
- };
- }
-
- await PublishAsync(input);
- }
-
- public async virtual Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null)
- {
- var input = new WebhookPublishInput
- {
- WebhookName = webhookName,
- Data = JsonConvert.SerializeObject(data),
- SendExactSameData = sendExactSameData,
- TenantIds = tenantIds.ToList(),
- };
- if (headers != null)
- {
- input.Header = new WebhooksHeaderInput
- {
- UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders,
- Headers = headers.Headers
- };
- }
-
- await PublishAsync(input);
- }
-
- protected virtual async Task PublishAsync(WebhookPublishInput input)
- {
- await PublishAppService.PublishAsync(input);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml
deleted file mode 100644
index 17d32672d..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd
deleted file mode 100644
index 11da52550..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
-
-
-
-
- A comma-separated list of error codes that can be safely ignored in assembly verification.
-
-
-
-
- 'false' to turn off automatic generation of the XML Schema file.
-
-
-
-
-
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj
deleted file mode 100644
index 2cd64673f..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
- netstandard2.0
-
-
-
-
-
-
-
-
-
-
-
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs
deleted file mode 100644
index 84798d8d2..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using Volo.Abp.BackgroundJobs;
-using Volo.Abp.Features;
-using Volo.Abp.Guids;
-using Volo.Abp.Http.Client;
-using Volo.Abp.Modularity;
-
-namespace LINGYUN.Abp.Webhooks;
-
-//[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))]
-// 防止未引用实现无法发布到后台作业
-[DependsOn(typeof(AbpBackgroundJobsModule))]
-[DependsOn(typeof(AbpFeaturesModule))]
-[DependsOn(typeof(AbpGuidsModule))]
-[DependsOn(typeof(AbpHttpClientModule))]
-public class AbpWebhooksModule : AbpModule
-{
- internal const string WebhooksClient = "__Abp_Webhooks_HttpClient";
-
- public override void PreConfigureServices(ServiceConfigurationContext context)
- {
- AutoAddDefinitionProviders(context.Services);
- }
-
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- var options = context.Services.ExecutePreConfiguredActions();
-
- context.Services.AddHttpClient(WebhooksClient, client =>
- {
- client.Timeout = options.TimeoutDuration;
- });
- }
-
- private static void AutoAddDefinitionProviders(IServiceCollection services)
- {
- var definitionProviders = new List();
-
- services.OnRegistred(context =>
- {
- if (typeof(WebhookDefinitionProvider).IsAssignableFrom(context.ImplementationType))
- {
- definitionProviders.Add(context.ImplementationType);
- }
- });
-
- services.Configure(options =>
- {
- options.DefinitionProviders.AddIfNotContains(definitionProviders);
- });
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs
deleted file mode 100644
index 072a76bcc..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using Volo.Abp.Collections;
-
-namespace LINGYUN.Abp.Webhooks;
-
-public class AbpWebhooksOptions
-{
- ///
- /// 默认超时时间
- ///
- public TimeSpan TimeoutDuration { get; set; }
- ///
- /// 默认最大发送次数
- ///
- public int MaxSendAttemptCount { get; set; }
- ///
- /// 是否达到最大连续失败次数时自动取消订阅
- ///
- public bool IsAutomaticSubscriptionDeactivationEnabled { get; set; }
- ///
- /// 取消订阅前最大连续失败次数
- ///
- public int MaxConsecutiveFailCountBeforeDeactivateSubscription { get; set; }
-
- public ITypeList DefinitionProviders { get; }
-
- public AbpWebhooksOptions()
- {
- TimeoutDuration = TimeSpan.FromSeconds(60);
- MaxSendAttemptCount = 5;
- MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3;
-
- DefinitionProviders = new TypeList();
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs
deleted file mode 100644
index b411e3a84..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using System;
-using System.Threading.Tasks;
-using Volo.Abp.BackgroundJobs;
-using Volo.Abp.DependencyInjection;
-using Volo.Abp.Uow;
-
-namespace LINGYUN.Abp.Webhooks.BackgroundWorker
-{
- public class WebhookSenderJob : AsyncBackgroundJob, ITransientDependency
- {
- private readonly IUnitOfWorkManager _unitOfWorkManager;
- private readonly IWebhookDefinitionManager _webhookDefinitionManager;
- private readonly IWebhookSubscriptionManager _webhookSubscriptionManager;
- private readonly IWebhookSendAttemptStore _webhookSendAttemptStore;
- private readonly IWebhookSender _webhookSender;
-
- private readonly AbpWebhooksOptions _options;
-
- public WebhookSenderJob(
- IUnitOfWorkManager unitOfWorkManager,
- IWebhookDefinitionManager webhookDefinitionManager,
- IWebhookSubscriptionManager webhookSubscriptionManager,
- IWebhookSendAttemptStore webhookSendAttemptStore,
- IWebhookSender webhookSender,
- IOptions options)
- {
- _unitOfWorkManager = unitOfWorkManager;
- _webhookDefinitionManager = webhookDefinitionManager;
- _webhookSubscriptionManager = webhookSubscriptionManager;
- _webhookSendAttemptStore = webhookSendAttemptStore;
- _webhookSender = webhookSender;
- _options = options.Value;
- }
-
- public override async Task ExecuteAsync(WebhookSenderArgs args)
- {
- var webhookDefinition = _webhookDefinitionManager.Get(args.WebhookName);
-
- if (webhookDefinition.TryOnce)
- {
- try
- {
- await SendWebhook(args, webhookDefinition);
- }
- catch (Exception e)
- {
- Logger.LogWarning("An error occured while sending webhook with try once.", e);
- // ignored
- }
- }
- else
- {
- await SendWebhook(args, webhookDefinition);
- }
- }
-
- private async Task SendWebhook(WebhookSenderArgs args, WebhookDefinition webhookDefinition)
- {
- if (args.WebhookEventId == default)
- {
- return;
- }
-
- if (args.WebhookSubscriptionId == default)
- {
- return;
- }
-
- if (!webhookDefinition.TryOnce)
- {
- var sendAttemptCount = await _webhookSendAttemptStore.GetSendAttemptCountAsync(
- args.TenantId,
- args.WebhookEventId,
- args.WebhookSubscriptionId
- );
-
- if ((webhookDefinition.MaxSendAttemptCount > 0 && sendAttemptCount > webhookDefinition.MaxSendAttemptCount) ||
- sendAttemptCount > _options.MaxSendAttemptCount)
- {
- return;
- }
- }
-
- try
- {
- await _webhookSender.SendWebhookAsync(args);
- }
- catch (Exception)
- {
- // no need to retry to send webhook since subscription disabled
- if (!await TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount(
- args.TenantId,
- args.WebhookSubscriptionId))
- {
- throw; //Throw exception to re-try sending webhook
- }
- }
- }
-
- private async Task TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount(
- Guid? tenantId,
- Guid subscriptionId)
- {
- if (!_options.IsAutomaticSubscriptionDeactivationEnabled)
- {
- return false;
- }
-
- var hasXConsecutiveFail = await _webhookSendAttemptStore
- .HasXConsecutiveFailAsync(
- tenantId,
- subscriptionId,
- _options.MaxConsecutiveFailCountBeforeDeactivateSubscription
- );
-
- if (!hasXConsecutiveFail)
- {
- return false;
- }
-
- using (var uow = _unitOfWorkManager.Begin())
- {
- await _webhookSubscriptionManager.ActivateWebhookSubscriptionAsync(subscriptionId, false);
- await uow.CompleteAsync();
- return true;
- }
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs
deleted file mode 100644
index be46197ae..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using Newtonsoft.Json;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Volo.Abp.BackgroundJobs;
-using Volo.Abp.Guids;
-using Volo.Abp.MultiTenancy;
-using Volo.Abp.DependencyInjection;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class DefaultWebhookPublisher : IWebhookPublisher, ITransientDependency
- {
- public IWebhookEventStore WebhookEventStore { get; set; }
-
- private readonly ICurrentTenant _currentTenant;
- private readonly IBackgroundJobManager _backgroundJobManager;
- private readonly IWebhookSubscriptionManager _webhookSubscriptionManager;
-
- public DefaultWebhookPublisher(
- IWebhookSubscriptionManager webhookSubscriptionManager,
- ICurrentTenant currentTenant,
- IBackgroundJobManager backgroundJobManager)
- {
- _currentTenant = currentTenant;
- _backgroundJobManager = backgroundJobManager;
- _webhookSubscriptionManager = webhookSubscriptionManager;
-
- WebhookEventStore = NullWebhookEventStore.Instance;
- }
-
- #region Async Publish Methods
-
- public virtual async Task PublishAsync(
- string webhookName,
- object data,
- bool sendExactSameData = false,
- WebhookHeader headers = null)
- {
- var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(_currentTenant.Id, webhookName);
- await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers);
- }
-
- public virtual async Task PublishAsync(
- string webhookName,
- object data,
- Guid? tenantId,
- bool sendExactSameData = false,
- WebhookHeader headers = null)
- {
- var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(tenantId, webhookName);
- await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers);
- }
-
- public virtual async Task PublishAsync(
- Guid?[] tenantIds,
- string webhookName,
- object data,
- bool sendExactSameData = false,
- WebhookHeader headers = null)
- {
- var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(tenantIds, webhookName);
- await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers);
- }
-
- protected virtual async Task PublishAsync(
- string webhookName,
- object data,
- List webhookSubscriptions,
- bool sendExactSameData = false,
- WebhookHeader headers = null)
- {
- if (webhookSubscriptions.IsNullOrEmpty())
- {
- return;
- }
-
- var subscriptionsGroupedByTenant = webhookSubscriptions.GroupBy(x => x.TenantId);
-
- foreach (var subscriptionGroupedByTenant in subscriptionsGroupedByTenant)
- {
- var webhookInfo = await SaveAndGetWebhookAsync(subscriptionGroupedByTenant.Key, webhookName, data);
-
- foreach (var webhookSubscription in subscriptionGroupedByTenant)
- {
- var headersToSend = webhookSubscription.Headers;
- if (headers != null)
- {
- if (headers.UseOnlyGivenHeaders)//do not use the headers defined in subscription
- {
- headersToSend = headers.Headers;
- }
- else
- {
- //use the headers defined in subscription. If additional headers has same header, use additional headers value.
- foreach (var additionalHeader in headers.Headers)
- {
- headersToSend[additionalHeader.Key] = additionalHeader.Value;
- }
- }
- }
-
- await _backgroundJobManager.EnqueueAsync(new WebhookSenderArgs
- {
- TenantId = webhookSubscription.TenantId,
- WebhookEventId = webhookInfo.Id,
- Data = webhookInfo.Data,
- WebhookName = webhookInfo.WebhookName,
- WebhookSubscriptionId = webhookSubscription.Id,
- Headers = headersToSend,
- Secret = webhookSubscription.Secret,
- WebhookUri = webhookSubscription.WebhookUri,
- SendExactSameData = sendExactSameData
- });
- }
- }
- }
-
- #endregion
-
- protected virtual async Task SaveAndGetWebhookAsync(
- Guid? tenantId,
- string webhookName,
- object data)
- {
- var webhookInfo = new WebhookEvent
- {
- WebhookName = webhookName,
- Data = JsonConvert.SerializeObject(data),
- TenantId = tenantId
- };
-
- var webhookId = await WebhookEventStore.InsertAndGetIdAsync(webhookInfo);
- webhookInfo.Id = webhookId;
-
- return webhookInfo;
- }
- }
-}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs
deleted file mode 100644
index 19982a68e..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using System;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Volo.Abp.DependencyInjection;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class DefaultWebhookSender : IWebhookSender, ITransientDependency
- {
- public ILogger Logger { protected get; set; }
-
- private readonly IWebhookManager _webhookManager;
- private readonly IHttpClientFactory _httpClientFactory;
-
- private const string FailedRequestDefaultContent = "Webhook Send Request Failed";
-
- public DefaultWebhookSender(
- IWebhookManager webhookManager,
- IHttpClientFactory httpClientFactory)
- {
- _webhookManager = webhookManager;
- _httpClientFactory = httpClientFactory;
-
- Logger = NullLogger.Instance;
- }
-
- public async Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs)
- {
- if (webhookSenderArgs.WebhookEventId == default)
- {
- throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookEventId));
- }
-
- if (webhookSenderArgs.WebhookSubscriptionId == default)
- {
- throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId));
- }
-
- var webhookSendAttemptId = await _webhookManager.InsertAndGetIdWebhookSendAttemptAsync(webhookSenderArgs);
-
- var request = CreateWebhookRequestMessage(webhookSenderArgs);
-
- var serializedBody = await _webhookManager.GetSerializedBodyAsync(webhookSenderArgs);
-
- _webhookManager.SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret);
-
- AddAdditionalHeaders(request, webhookSenderArgs);
-
- var isSucceed = false;
- HttpStatusCode? statusCode = null;
- var content = FailedRequestDefaultContent;
-
- try
- {
- var response = await SendHttpRequest(request);
- isSucceed = response.isSucceed;
- statusCode = response.statusCode;
- content = response.content;
- }
- catch (TaskCanceledException)
- {
- statusCode = HttpStatusCode.RequestTimeout;
- content = "Request Timeout";
- }
- catch (HttpRequestException e)
- {
- content = e.Message;
- }
- catch (Exception e)
- {
- Logger.LogError("An error occured while sending a webhook request", e);
- }
- finally
- {
- await _webhookManager.StoreResponseOnWebhookSendAttemptAsync(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content);
- }
-
- if (!isSucceed)
- {
- throw new Exception($"Webhook sending attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}");
- }
-
- return webhookSendAttemptId;
- }
-
- ///
- /// You can override this to change request message
- ///
- ///
- protected virtual HttpRequestMessage CreateWebhookRequestMessage(WebhookSenderArgs webhookSenderArgs)
- {
- return new HttpRequestMessage(HttpMethod.Post, webhookSenderArgs.WebhookUri);
- }
-
- protected virtual void AddAdditionalHeaders(HttpRequestMessage request, WebhookSenderArgs webhookSenderArgs)
- {
- foreach (var header in webhookSenderArgs.Headers)
- {
- if (request.Headers.TryAddWithoutValidation(header.Key, header.Value))
- {
- continue;
- }
-
- if (request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value))
- {
- continue;
- }
-
- throw new Exception($"Invalid Header. SubscriptionId:{webhookSenderArgs.WebhookSubscriptionId},Header: {header.Key}:{header.Value}");
- }
- }
-
- protected virtual async Task<(bool isSucceed, HttpStatusCode statusCode, string content)> SendHttpRequest(HttpRequestMessage request)
- {
- var client = _httpClientFactory.CreateClient(AbpWebhooksModule.WebhooksClient);
-
- var response = await client.SendAsync(request);
-
- var isSucceed = response.IsSuccessStatusCode;
- var statusCode = response.StatusCode;
- var content = await response.Content.ReadAsStringAsync();
-
- return (isSucceed, statusCode, content);
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs
deleted file mode 100644
index 8cf48f520..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-
-namespace LINGYUN.Abp.Webhooks.Extensions
-{
- public static class WebhookSubscriptionExtensions
- {
- ///
- /// checks if subscribed to given webhook
- ///
- ///
- public static bool IsSubscribed(this WebhookSubscriptionInfo webhookSubscription, string webhookName)
- {
- if (webhookSubscription.Webhooks.IsNullOrEmpty())
- {
- return false;
- }
-
- return webhookSubscription.Webhooks.Contains(webhookName);
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs
deleted file mode 100644
index c170ede3d..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using JetBrains.Annotations;
-using Volo.Abp.Localization;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookDefinitionContext
- {
- WebhookGroupDefinition AddGroup(
- [NotNull] string name,
- ILocalizableString displayName = null);
-
- WebhookGroupDefinition GetGroupOrNull(string name);
-
- void RemoveGroup(string name);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs
deleted file mode 100644
index a284ab693..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookDefinitionManager
- {
- ///
- /// Gets a webhook definition by name.
- /// Returns null if there is no webhook definition with given name.
- ///
- WebhookDefinition GetOrNull(string name);
-
- ///
- /// Gets a webhook definition by name.
- /// Throws exception if there is no webhook definition with given name.
- ///
- WebhookDefinition Get(string name);
-
- ///
- /// Gets all webhook definitions.
- ///
- IReadOnlyList GetAll();
-
- ///
- /// Gets all webhook group definitions.
- ///
- ///
- IReadOnlyList GetGroups();
-
- ///
- /// Checks if given webhook name is available for given tenant.
- ///
- Task IsAvailableAsync(Guid? tenantId, string name);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs
deleted file mode 100644
index 1fb8f5c21..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookEventStore
- {
- ///
- /// Inserts to persistent store
- ///
- Task InsertAndGetIdAsync(WebhookEvent webhookEvent);
-
- ///
- /// Gets Webhook info by id
- ///
- Task GetAsync(Guid? tenantId, Guid id);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs
deleted file mode 100644
index 1e8e5fd0c..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookManager
- {
- Task GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs);
-
- void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret);
-
- Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs);
-
- Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs);
-
- Task StoreResponseOnWebhookSendAttemptAsync(
- Guid webhookSendAttemptId, Guid? tenantId,
- HttpStatusCode? statusCode, string content);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs
deleted file mode 100644
index 3b53fec83..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookPublisher
- {
- ///
- /// Sends webhooks to current tenant subscriptions (). with given data, (Checks permissions)
- ///
- ///
- /// data to send
- ///
- /// True: It sends the exact same data as the parameter to clients.
- ///
- /// False: It sends data in . It is recommended way.
- ///
- ///
- /// Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.
- Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null);
-
- ///
- /// Sends webhooks to given tenant's subscriptions
- ///
- ///
- /// data to send
- ///
- /// Target tenant id
- ///
- ///
- /// True: It sends the exact same data as the parameter to clients.
- ///
- /// False: It sends data in . It is recommended way.
- ///
- ///
- /// Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.
- Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null);
-
- ///
- /// Sends webhooks to given tenant's subscriptions
- ///
- ///
- /// data to send
- ///
- /// Target tenant id(s)
- ///
- ///
- /// True: It sends the exact same data as the parameter to clients.
- ///
- /// False: It sends data in . It is recommended way.
- ///
- ///
- /// Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.
- Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null);
- }
-}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs
deleted file mode 100644
index 2e32bc63f..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookSendAttemptStore
- {
- Task GetAsync(Guid? tenantId, Guid id);
-
- ///
- /// Returns work item count by given web hook id and subscription id, (How many times publisher tried to send web hook)
- ///
- Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId);
-
- ///
- /// Checks is there any successful webhook attempt in last items. Should return true if there are not X number items
- ///
- Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount);
-
- Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, int skipCount);
-
- Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs
deleted file mode 100644
index d30e110e6..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookSender
- {
- ///
- /// Tries to send webhook with given transactionId and stores process in
- /// Should throw exception if fails or response status not succeed
- ///
- /// arguments
- /// Webhook send attempt id
- Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs
deleted file mode 100644
index 956b2dbd3..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public interface IWebhookSubscriptionManager
- {
- ///
- /// Returns subscription for given id.
- ///
- /// Unique identifier of
- Task GetAsync(Guid id);
-
- ///
- /// Returns all subscriptions of tenant
- ///
- ///
- /// Target tenant id.
- ///
- Task> GetAllSubscriptionsAsync(Guid? tenantId);
-
- ///
- /// Returns all subscriptions for given webhook.
- ///
- ///
- ///
- /// Target tenant id.
- ///
- Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName);
-
- ///
- /// Returns all subscriptions of tenant
- ///
- ///
- Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds);
-
- ///
- /// Returns all subscriptions for given webhook.
- ///
- ///
- ///
- /// Target tenant id(s).
- ///
- Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName);
-
- ///
- /// Checks if tenant subscribed for a webhook. (Checks if webhook features are granted)
- ///
- ///
- /// Target tenant id(s).
- ///
- ///
- Task IsSubscribedAsync(Guid? tenantId, string webhookName);
-
- ///
- /// If id is the default(Guid) adds new subscription, else updates current one. (Checks if webhook features are granted)
- ///
- Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription);
-
- ///
- /// Activates/Deactivates given webhook subscription
- ///
- /// unique identifier of
- /// IsActive
- Task ActivateWebhookSubscriptionAsync(Guid id, bool active);
-
- ///
- /// Delete given webhook subscription.
- ///
- /// unique identifier of
- Task DeleteSubscriptionAsync(Guid id);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs
deleted file mode 100644
index 23849ad4e..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- ///
- /// This interface should be implemented by vendors to make webhooks working.
- ///
- public interface IWebhookSubscriptionsStore
- {
- ///
- /// returns subscription
- ///
- /// webhook subscription id
- ///
- Task GetAsync(Guid id);
-
- ///
- /// Saves webhook subscription to a persistent store.
- ///
- /// webhook subscription information
- Task InsertAsync(WebhookSubscriptionInfo webhookSubscription);
-
- ///
- /// Updates webhook subscription to a persistent store.
- ///
- /// webhook subscription information
- Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription);
-
- ///
- /// Deletes subscription if exists
- ///
- /// primary key
- ///
- Task DeleteAsync(Guid id);
-
- ///
- /// Returns all subscriptions of given tenant including deactivated
- ///
- ///
- /// Target tenant id.
- ///
- Task> GetAllSubscriptionsAsync(Guid? tenantId);
-
- ///
- /// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
- ///
- ///
- /// Target tenant id.
- ///
- ///
- ///
- Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName);
-
- ///
- /// Returns all subscriptions of given tenant including deactivated
- ///
- ///
- /// Target tenant id(s).
- ///
- Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds);
-
- ///
- /// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
- ///
- ///
- /// Target tenant id(s).
- ///
- ///
- ///
- Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName);
-
- ///
- /// Checks if tenant subscribed for a webhook
- ///
- ///
- /// Target tenant id(s).
- ///
- /// Name of the webhook
- Task IsSubscribedAsync(Guid? tenantId, string webhookName);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs
deleted file mode 100644
index 069726e67..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- ///
- /// Null pattern implementation of .
- /// It's used if is not implemented by actual persistent store
- ///
- public class NullWebhookEventStore : IWebhookEventStore
- {
- public static NullWebhookEventStore Instance { get; } = new NullWebhookEventStore();
-
- public Task InsertAndGetIdAsync(WebhookEvent webhookEvent)
- {
- return Task.FromResult(default);
- }
-
- public Task GetAsync(Guid? tenantId, Guid id)
- {
- return Task.FromResult(default);
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs
deleted file mode 100644
index 7254b1ae6..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class NullWebhookSendAttemptStore : IWebhookSendAttemptStore
- {
- public static NullWebhookSendAttemptStore Instance = new NullWebhookSendAttemptStore();
-
- public Task InsertAsync(WebhookSendAttempt webhookSendAttempt)
- {
- return Task.CompletedTask;
- }
-
- public Task UpdateAsync(WebhookSendAttempt webhookSendAttempt)
- {
- return Task.CompletedTask;
- }
-
- public Task GetAsync(Guid? tenantId, Guid id)
- {
- return Task.FromResult(default);
- }
-
- public Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId)
- {
- return Task.FromResult(int.MaxValue);
- }
-
- public Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount)
- {
- return default;
- }
-
- public Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount,
- int skipCount)
- {
- return Task.FromResult(ValueTuple.Create(0, new List() as IReadOnlyCollection));
- }
-
- public Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId)
- {
- return Task.FromResult(new List());
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs
deleted file mode 100644
index 4214d1d25..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- ///
- /// Null pattern implementation of .
- /// It's used if is not implemented by actual persistent store
- ///
- public class NullWebhookSubscriptionsStore : IWebhookSubscriptionsStore
- {
- public static NullWebhookSubscriptionsStore Instance { get; } = new NullWebhookSubscriptionsStore();
-
- public Task GetAsync(Guid id)
- {
- return Task.FromResult(default);
- }
-
- public WebhookSubscriptionInfo Get(Guid id)
- {
- return default;
- }
-
- public Task InsertAsync(WebhookSubscriptionInfo webhookSubscription)
- {
- return Task.CompletedTask;
- }
-
- public Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription)
- {
- return Task.CompletedTask;
- }
-
- public Task DeleteAsync(Guid id)
- {
- return Task.CompletedTask;
- }
-
- public Task> GetAllSubscriptionsAsync(Guid? tenantId)
- {
- return Task.FromResult(new List());
- }
-
- public Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName)
- {
- return Task.FromResult(new List());
- }
-
- public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds)
- {
- return Task.FromResult(new List());
- }
-
- public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName)
- {
- return Task.FromResult(new List());
- }
-
- public Task IsSubscribedAsync(Guid? tenantId, string webhookName)
- {
- return Task.FromResult(false);
- }
- }
-}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs
deleted file mode 100644
index 1c1461e8a..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Volo.Abp.Localization;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class WebhookDefinition
- {
- ///
- /// Unique name of the webhook.
- ///
- public string Name { get; }
-
- ///
- /// Tries to send webhook only one time without checking to send attempt count
- ///
- public bool TryOnce { get; set; }
-
- ///
- /// Defined maximum number of sending times
- ///
- public int MaxSendAttemptCount { get; set; }
-
- ///
- /// Display name of the webhook.
- /// Optional.
- ///
- public ILocalizableString DisplayName { get; set; }
-
- ///
- /// Description for the webhook.
- /// Optional.
- ///
- public ILocalizableString Description { get; set; }
-
- public List RequiredFeatures { get; set; }
-
- public WebhookDefinition(string name, ILocalizableString displayName = null, ILocalizableString description = null)
- {
- if (name.IsNullOrWhiteSpace())
- {
- throw new ArgumentNullException(nameof(name), $"{nameof(name)} can not be null, empty or whitespace!");
- }
-
- Name = name.Trim();
- DisplayName = displayName;
- Description = description;
-
- RequiredFeatures = new List();
- }
-
- public WebhookDefinition WithFeature(params string[] features)
- {
- if (!features.IsNullOrEmpty())
- {
- RequiredFeatures.AddRange(features);
- }
-
- return this;
- }
-
- public override string ToString()
- {
- return $"[{nameof(WebhookDefinition)} {Name}]";
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs
deleted file mode 100644
index 5e8987a8b..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using JetBrains.Annotations;
-using System.Collections.Generic;
-using Volo.Abp;
-using Volo.Abp.Localization;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class WebhookDefinitionContext : IWebhookDefinitionContext
- {
- protected Dictionary Groups { get; }
-
- public WebhookDefinitionContext(Dictionary webhooks)
- {
- Groups = webhooks;
- }
-
- public WebhookGroupDefinition AddGroup(
- [NotNull] string name,
- ILocalizableString displayName = null)
- {
- Check.NotNull(name, nameof(name));
-
- if (Groups.ContainsKey(name))
- {
- throw new AbpException($"There is already an existing webhook group with name: {name}");
- }
-
- return Groups[name] = new WebhookGroupDefinition(name, displayName);
- }
-
- public WebhookGroupDefinition GetGroupOrNull([NotNull] string name)
- {
- Check.NotNull(name, nameof(name));
-
- if (!Groups.ContainsKey(name))
- {
- return null;
- }
-
- return Groups[name];
- }
-
- public void RemoveGroup(string name)
- {
- Check.NotNull(name, nameof(name));
-
- if (!Groups.ContainsKey(name))
- {
- throw new AbpException($"Undefined notification webhook group: '{name}'.");
- }
-
- Groups.Remove(name);
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs
deleted file mode 100644
index c0f96f20e..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Threading.Tasks;
-using Volo.Abp;
-using Volo.Abp.DependencyInjection;
-using Volo.Abp.Features;
-using Volo.Abp.MultiTenancy;
-
-namespace LINGYUN.Abp.Webhooks
-{
- internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency
- {
- protected IDictionary WebhookGroupDefinitions => _lazyWebhookGroupDefinitions.Value;
- private readonly Lazy> _lazyWebhookGroupDefinitions;
-
- protected IDictionary WebhookDefinitions => _lazyWebhookDefinitions.Value;
- private readonly Lazy> _lazyWebhookDefinitions;
-
- private readonly IServiceProvider _serviceProvider;
- private readonly AbpWebhooksOptions _options;
-
- public WebhookDefinitionManager(
- IServiceProvider serviceProvider,
- IOptions options)
- {
- _serviceProvider = serviceProvider;
- _options = options.Value;
-
- _lazyWebhookGroupDefinitions = new Lazy>(CreateWebhookGroupDefinitions);
- _lazyWebhookDefinitions = new Lazy>(CreateWebhookDefinitions);
- }
-
- public WebhookDefinition GetOrNull(string name)
- {
- if (!WebhookDefinitions.ContainsKey(name))
- {
- return null;
- }
-
- return WebhookDefinitions[name];
- }
-
- public WebhookDefinition Get(string name)
- {
- if (!WebhookDefinitions.ContainsKey(name))
- {
- throw new KeyNotFoundException($"Webhook definitions does not contain a definition with the key \"{name}\".");
- }
-
- return WebhookDefinitions[name];
- }
-
- public IReadOnlyList GetAll()
- {
- return WebhookDefinitions.Values.ToImmutableList();
- }
-
- public IReadOnlyList GetGroups()
- {
- return WebhookGroupDefinitions.Values.ToImmutableList();
- }
-
- public async Task IsAvailableAsync(Guid? tenantId, string name)
- {
- if (tenantId == null) // host allowed to subscribe all webhooks
- {
- return true;
- }
-
- var webhookDefinition = GetOrNull(name);
-
- if (webhookDefinition == null)
- {
- return false;
- }
-
- if (webhookDefinition.RequiredFeatures?.Any() == false)
- {
- return true;
- }
-
- var currentTenant = _serviceProvider.GetRequiredService();
- var featureChecker = _serviceProvider.GetRequiredService();
- using (currentTenant.Change(tenantId))
- {
- if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray()))
- {
- return false;
- }
- }
-
- return true;
- }
-
- protected virtual Dictionary CreateWebhookDefinitions()
- {
- var definitions = new Dictionary();
-
- foreach (var groupDefinition in WebhookGroupDefinitions.Values)
- {
- foreach (var webhook in groupDefinition.Webhooks)
- {
- if (definitions.ContainsKey(webhook.Name))
- {
- throw new AbpException("Duplicate webhook name: " + webhook.Name);
- }
-
- definitions[webhook.Name] = webhook;
- }
- }
-
- return definitions;
- }
-
- protected virtual Dictionary CreateWebhookGroupDefinitions()
- {
- var definitions = new Dictionary();
-
- using (var scope = _serviceProvider.CreateScope())
- {
- var providers = _options
- .DefinitionProviders
- .Select(p => scope.ServiceProvider.GetRequiredService(p) as WebhookDefinitionProvider)
- .ToList();
-
- foreach (var provider in providers)
- {
- provider.Define(new WebhookDefinitionContext(definitions));
- }
- }
-
- return definitions;
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs
deleted file mode 100644
index ead9ab679..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Volo.Abp.DependencyInjection;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public abstract class WebhookDefinitionProvider : ITransientDependency
- {
- ///
- /// Used to add/manipulate webhook definitions.
- ///
- /// Context,
- public abstract void Define(IWebhookDefinitionContext context);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs
deleted file mode 100644
index bd32fa76d..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-
-namespace LINGYUN.Abp.Webhooks
-{
- ///
- /// Store created web hooks. To see who get that webhook check with and you can get
- ///
- public class WebhookEvent
- {
- public Guid Id { get; set; }
-
- ///
- /// Webhook unique name
- ///
- public string WebhookName { get; set; }
-
- ///
- /// Webhook data as JSON string.
- ///
- public string Data { get; set; }
-
- public DateTime CreationTime { get; set; }
-
- public Guid? TenantId { get; set; }
-
- public bool IsDeleted { get; set; }
-
- public DateTime? DeletionTime { get; set; }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs
deleted file mode 100644
index ae58049ed..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-using JetBrains.Annotations;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using Volo.Abp;
-using Volo.Abp.Localization;
-
-namespace LINGYUN.Abp.Webhooks;
-
-public class WebhookGroupDefinition
-{
- [NotNull]
- public string Name { get; set; }
- public Dictionary Properties { get; }
-
- private ILocalizableString _displayName;
- public ILocalizableString DisplayName
- {
- get {
- return _displayName;
- }
- set {
- _displayName = value;
- }
- }
-
- public IReadOnlyList Webhooks => _webhooks.ToImmutableList();
- private readonly List _webhooks;
- public object this[string name] {
- get => Properties.GetOrDefault(name);
- set => Properties[name] = value;
- }
-
- protected internal WebhookGroupDefinition(
- string name,
- ILocalizableString displayName = null)
- {
- Name = name;
- DisplayName = displayName ?? new FixedLocalizableString(Name);
-
- Properties = new Dictionary();
- _webhooks = new List();
- }
-
- public virtual WebhookDefinition AddWebhook(
- string name,
- ILocalizableString displayName = null,
- ILocalizableString description = null)
- {
- if (Webhooks.Any(hook => hook.Name.Equals(name)))
- {
- throw new AbpException($"There is already an existing webhook with name: {name} in group {Name}");
- }
-
- var webhook = new WebhookDefinition(
- name,
- displayName,
- description
- );
-
- _webhooks.Add(webhook);
-
- return webhook;
- }
-
- public virtual void AddWebhooks(params WebhookDefinition[] webhooks)
- {
- foreach (var webhook in webhooks)
- {
- if (Webhooks.Any(hook => hook.Name.Equals(webhook.Name)))
- {
- throw new AbpException($"There is already an existing webhook with name: {webhook.Name} in group {Name}");
- }
- }
-
- _webhooks.AddRange(webhooks);
- }
-
-
-
- [CanBeNull]
- public WebhookDefinition GetWebhookOrNull([NotNull] string name)
- {
- Check.NotNull(name, nameof(name));
-
- foreach (var webhook in Webhooks)
- {
- if (webhook.Name == name)
- {
- return webhook;
- }
- }
-
- return null;
- }
-
- public override string ToString()
- {
- return $"[{nameof(WebhookGroupDefinition)} {Name}]";
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs
deleted file mode 100644
index 91442552f..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Collections.Generic;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class WebhookHeader
- {
- ///
- /// If true, webhook will only contain given headers. If false given headers will be added to predefined headers in subscription.
- /// Default is false
- ///
- public bool UseOnlyGivenHeaders { get; set; }
-
- ///
- /// That headers will be sent with the webhook.
- ///
- public IDictionary Headers { get; set; }
- }
-}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs
deleted file mode 100644
index 854795786..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using Newtonsoft.Json;
-using System;
-using System.Globalization;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public abstract class WebhookManager : IWebhookManager
- {
- private const string SignatureHeaderKey = "sha256";
- private const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}";
- private const string SignatureHeaderName = "abp-webhook-signature";
- protected IWebhookSendAttemptStore WebhookSendAttemptStore { get; }
-
- protected WebhookManager(
- IWebhookSendAttemptStore webhookSendAttemptStore)
- {
- WebhookSendAttemptStore = webhookSendAttemptStore;
- }
-
- public virtual async Task GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs)
- {
- var data = JsonConvert.SerializeObject(webhookSenderArgs.Data);
-
- var attemptNumber = await WebhookSendAttemptStore.GetSendAttemptCountAsync(
- webhookSenderArgs.TenantId,
- webhookSenderArgs.WebhookEventId,
- webhookSenderArgs.WebhookSubscriptionId);
-
- return new WebhookPayload(
- webhookSenderArgs.WebhookEventId.ToString(),
- webhookSenderArgs.WebhookName,
- attemptNumber)
- {
- Data = data
- };
- }
-
- public virtual void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret)
- {
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
-
- if (string.IsNullOrWhiteSpace(serializedBody))
- {
- throw new ArgumentNullException(nameof(serializedBody));
- }
-
- request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json");
-
- var secretBytes = Encoding.UTF8.GetBytes(secret);
- var headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, serializedBody.Sha256(secretBytes));
-
- request.Headers.Add(SignatureHeaderName, headerValue);
- }
-
- public virtual async Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs)
- {
- if (webhookSenderArgs.SendExactSameData)
- {
- return webhookSenderArgs.Data;
- }
-
- var payload = await GetWebhookPayloadAsync(webhookSenderArgs);
-
- var serializedBody = JsonConvert.SerializeObject(payload);
-
- return serializedBody;
- }
-
- public abstract Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs);
-
- public abstract Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content);
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs
deleted file mode 100644
index ff287cbd0..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class WebhookPayload
- {
- public string Id { get; set; }
-
- public string WebhookEvent { get; set; }
-
- public int Attempt { get; set; }
-
- public dynamic Data { get; set; }
-
- public DateTime CreationTimeUtc { get; set; }
-
- public WebhookPayload(string id, string webhookEvent, int attempt)
- {
- if (id.IsNullOrWhiteSpace())
- {
- throw new ArgumentNullException(nameof(id));
- }
-
- if (webhookEvent.IsNullOrWhiteSpace())
- {
- throw new ArgumentNullException(nameof(webhookEvent));
- }
-
- Id = id;
- WebhookEvent = webhookEvent;
- Attempt = attempt;
- CreationTimeUtc = DateTime.UtcNow;
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs
deleted file mode 100644
index 8189f8f72..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Net;
-
-namespace LINGYUN.Abp.Webhooks
-{
- ///
- /// Table for store webhook work items. Each item stores web hook send attempt of to subscribed tenants
- ///
- public class WebhookSendAttempt
- {
- public Guid Id { get; set; }
-
- ///
- /// foreign id
- ///
- public Guid WebhookEventId { get; set; }
-
- ///
- /// foreign id
- ///
- public Guid WebhookSubscriptionId { get; set; }
-
- ///
- /// Webhook response content that webhook endpoint send back
- ///
- public string Response { get; set; }
-
- ///
- /// Webhook response status code that webhook endpoint send back
- ///
- public HttpStatusCode? ResponseStatusCode { get; set; }
-
- public DateTime CreationTime { get; set; }
-
- public DateTime? LastModificationTime { get; set; }
-
- public Guid? TenantId { get; set; }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs
deleted file mode 100644
index cb30acbf9..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class WebhookSenderArgs
- {
- public Guid? TenantId { get; set; }
-
- //Webhook information
-
- ///
- /// foreign id
- ///
- public Guid WebhookEventId { get; set; }
-
- ///
- /// Webhook unique name
- ///
- public string WebhookName { get; set; }
-
- ///
- /// Webhook data as JSON string.
- ///
- public string Data { get; set; }
-
- //Subscription information
-
- ///
- /// foreign id
- ///
- public Guid WebhookSubscriptionId { get; set; }
-
- ///
- /// Subscription webhook endpoint
- ///
- public string WebhookUri { get; set; }
-
- ///
- /// Webhook secret
- ///
- public string Secret { get; set; }
-
- ///
- /// Gets a set of additional HTTP headers.That headers will be sent with the webhook.
- ///
- public IDictionary Headers { get; set; }
-
- ///
- /// True: It sends the exact same data as the parameter to clients.
- ///
- /// False: It sends data in . It is recommended way.
- ///
- ///
- public bool SendExactSameData { get; set; }
-
- public WebhookSenderArgs()
- {
- Headers = new Dictionary();
- }
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs
deleted file mode 100644
index 9e4e3bf2a..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class WebhookSubscriptionInfo
- {
- public Guid Id { get; set; }
- ///
- /// Subscribed Tenant's id .
- ///
- public Guid? TenantId { get; set; }
-
- ///
- /// Subscription webhook endpoint
- ///
- public string WebhookUri { get; set; }
-
- ///
- /// Webhook secret
- ///
- public string Secret { get; set; }
-
- ///
- /// Is subscription active
- ///
- public bool IsActive { get; set; }
-
- ///
- /// Subscribed webhook definitions unique names.It contains webhook definitions list as json
- ///
- /// Do not change it manually.
- /// Use ,
- /// ,
- /// and
- /// to change it.
- ///
- ///
- public List Webhooks { get; set; }
-
- ///
- /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. It contains webhook header dictionary as json
- ///
- /// Do not change it manually.
- /// Use ,
- /// ,
- /// ,
- /// to change it.
- ///
- ///
- public IDictionary Headers { get; set; }
-
- public WebhookSubscriptionInfo()
- {
- IsActive = true;
- Headers = new Dictionary();
- Webhooks = new List();
- }
- }
-}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs
deleted file mode 100644
index fe1a9c697..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Volo.Abp.Authorization;
-using Volo.Abp.DependencyInjection;
-using Volo.Abp.Guids;
-using Volo.Abp.Uow;
-
-namespace LINGYUN.Abp.Webhooks
-{
- public class WebhookSubscriptionManager : IWebhookSubscriptionManager, ITransientDependency
- {
- public IWebhookSubscriptionsStore WebhookSubscriptionsStore { get; set; }
-
- private readonly IGuidGenerator _guidGenerator;
- private readonly IUnitOfWorkManager _unitOfWorkManager;
- private readonly IWebhookDefinitionManager _webhookDefinitionManager;
-
- private const string WebhookSubscriptionSecretPrefix = "whs_";
-
- public WebhookSubscriptionManager(
- IGuidGenerator guidGenerator,
- IUnitOfWorkManager unitOfWorkManager,
- IWebhookDefinitionManager webhookDefinitionManager)
- {
- _guidGenerator = guidGenerator;
- _unitOfWorkManager = unitOfWorkManager;
- _webhookDefinitionManager = webhookDefinitionManager;
-
- WebhookSubscriptionsStore = NullWebhookSubscriptionsStore.Instance;
- }
-
- public virtual async Task GetAsync(Guid id)
- {
- return await WebhookSubscriptionsStore.GetAsync(id);
- }
-
- public virtual async Task> GetAllSubscriptionsAsync(Guid? tenantId)
- {
- return await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId);
- }
-
- public virtual async Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName)
- {
- if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
- {
- return new List();
- }
-
- return (await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId, webhookName)).ToList();
- }
-
- public virtual async Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds)
- {
- return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(tenantIds)).ToList();
- }
-
- public virtual async Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName)
- {
- var featureGrantedTenants = new List();
- foreach (var tenantId in tenantIds)
- {
- if (await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
- {
- featureGrantedTenants.Add(tenantId);
- }
- }
-
- return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(featureGrantedTenants.ToArray(), webhookName)).ToList();
- }
-
- public virtual async Task IsSubscribedAsync(Guid? tenantId, string webhookName)
- {
- if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
- {
- return false;
- }
-
- return await WebhookSubscriptionsStore.IsSubscribedAsync(tenantId, webhookName);
- }
-
- public virtual async Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription)
- {
- using (var uow = _unitOfWorkManager.Begin())
- {
- await CheckIfPermissionsGrantedAsync(webhookSubscription);
-
- if (webhookSubscription.Id == default)
- {
- webhookSubscription.Id = _guidGenerator.Create();
- webhookSubscription.Secret = WebhookSubscriptionSecretPrefix + Guid.NewGuid().ToString("N");
- await WebhookSubscriptionsStore.InsertAsync(webhookSubscription);
- }
- else
- {
- await WebhookSubscriptionsStore.UpdateAsync(webhookSubscription);
- }
-
- await uow.SaveChangesAsync();
- }
- }
-
- public virtual async Task ActivateWebhookSubscriptionAsync(Guid id, bool active)
- {
- using (var uow = _unitOfWorkManager.Begin())
- {
- var webhookSubscription = await WebhookSubscriptionsStore.GetAsync(id);
- webhookSubscription.IsActive = active;
-
- await uow.SaveChangesAsync();
- }
- }
-
- public virtual async Task DeleteSubscriptionAsync(Guid id)
- {
- using (var uow = _unitOfWorkManager.Begin())
- {
- await WebhookSubscriptionsStore.DeleteAsync(id);
-
- await uow.SaveChangesAsync();
- }
- }
-
- public virtual async Task AddWebhookAsync(WebhookSubscriptionInfo subscription, string webhookName)
- {
- using (var uow = _unitOfWorkManager.Begin())
- {
- await CheckPermissionsAsync(subscription.TenantId, webhookName);
- webhookName = webhookName.Trim();
- if (webhookName.IsNullOrWhiteSpace())
- {
- throw new ArgumentNullException(nameof(webhookName), $"{nameof(webhookName)} can not be null, empty or whitespace!");
- }
-
- if (!subscription.Webhooks.Contains(webhookName))
- {
- subscription.Webhooks.Add(webhookName);
-
- await WebhookSubscriptionsStore.UpdateAsync(subscription);
- }
-
- await uow.SaveChangesAsync();
- }
- }
-
- #region PermissionCheck
-
- protected virtual async Task CheckIfPermissionsGrantedAsync(WebhookSubscriptionInfo webhookSubscription)
- {
- if (webhookSubscription.Webhooks.IsNullOrEmpty())
- {
- return;
- }
-
- foreach (var webhookDefinition in webhookSubscription.Webhooks)
- {
- await CheckPermissionsAsync(webhookSubscription.TenantId, webhookDefinition);
- }
- }
-
- protected virtual async Task CheckPermissionsAsync(Guid? tenantId, string webhookName)
- {
- if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
- {
- throw new AbpAuthorizationException($"Tenant \"{tenantId}\" must have necessary feature(s) to use webhook \"{webhookName}\"");
- }
- }
-
- #endregion
- }
-}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs
deleted file mode 100644
index f4202f6e2..000000000
--- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Security.Cryptography;
-
-namespace System;
-
-internal static class AbpStringCryptographyExtensions
-{
- public static string Sha256(this string planText, byte[] salt)
- {
- var data = planText.GetBytes();
- using var hmacsha256 = new HMACSHA256(salt);
- return BitConverter.ToString(hmacsha256.ComputeHash(data));
- }
-}
From ce4d8221b00010f4984dfdbeca094a70ae9a5665 Mon Sep 17 00:00:00 2001
From: cKey <35512826+colinin@users.noreply.github.com>
Date: Tue, 5 Apr 2022 11:06:49 +0800
Subject: [PATCH 2/2] fix: fix case in reference project paths
---
.../LINGYUN.Abp.Webhooks.ClientProxies.csproj | 16 ++
.../AbpWebhooksClientProxiesModule.cs | 10 +
.../ClientProxiesWebhookPublisher.cs | 91 +++++++++
.../LINGYUN.Abp.Webhooks/FodyWeavers.xml | 3 +
.../LINGYUN.Abp.Webhooks/FodyWeavers.xsd | 30 +++
.../LINGYUN.Abp.Webhooks.csproj | 18 ++
.../LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs | 54 ++++++
.../Abp/Webhooks/AbpWebhooksOptions.cs | 35 ++++
.../BackgroundWorker/WebhookSenderJob.cs | 131 +++++++++++++
.../Abp/Webhooks/DefaultWebhookPublisher.cs | 140 ++++++++++++++
.../Abp/Webhooks/DefaultWebhookSender.cs | 129 +++++++++++++
.../WebhookSubscriptionExtensions.cs | 21 +++
.../Abp/Webhooks/IWebhookDefinitionContext.cs | 16 ++
.../Abp/Webhooks/IWebhookDefinitionManager.cs | 37 ++++
.../Abp/Webhooks/IWebhookEventStore.cs | 18 ++
.../LINGYUN/Abp/Webhooks/IWebhookManager.cs | 22 +++
.../LINGYUN/Abp/Webhooks/IWebhookPublisher.cs | 56 ++++++
.../Abp/Webhooks/IWebhookSendAttemptStore.cs | 25 +++
.../LINGYUN/Abp/Webhooks/IWebhookSender.cs | 16 ++
.../Webhooks/IWebhookSubscriptionManager.cs | 74 ++++++++
.../Webhooks/IWebhookSubscriptionsStore.cs | 83 +++++++++
.../Abp/Webhooks/NullWebhookEventStore.cs | 24 +++
.../Webhooks/NullWebhookSendAttemptStore.cs | 47 +++++
.../Webhooks/NullWebhookSubscriptionsStore.cs | 65 +++++++
.../LINGYUN/Abp/Webhooks/WebhookDefinition.cs | 67 +++++++
.../Abp/Webhooks/WebhookDefinitionContext.cs | 55 ++++++
.../Abp/Webhooks/WebhookDefinitionManager.cs | 139 ++++++++++++++
.../Abp/Webhooks/WebhookDefinitionProvider.cs | 13 ++
.../LINGYUN/Abp/Webhooks/WebhookEvent.cs | 30 +++
.../Abp/Webhooks/WebhookGroupDefinition.cs | 101 ++++++++++
.../LINGYUN/Abp/Webhooks/WebhookHeader.cs | 18 ++
.../LINGYUN/Abp/Webhooks/WebhookManager.cs | 80 ++++++++
.../LINGYUN/Abp/Webhooks/WebhookPayload.cs | 35 ++++
.../Abp/Webhooks/WebhookSendAttempt.cs | 39 ++++
.../LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs | 62 +++++++
.../Abp/Webhooks/WebhookSubscriptionInfo.cs | 60 ++++++
.../Webhooks/WebhookSubscriptionManager.cs | 172 ++++++++++++++++++
.../System/AbpStringCryptographyExtensions.cs | 13 ++
38 files changed, 2045 insertions(+)
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs
create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj
new file mode 100644
index 000000000..251dd53d3
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs
new file mode 100644
index 000000000..41e08a4a7
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs
@@ -0,0 +1,10 @@
+using LINGYUN.Abp.WebhooksManagement;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.Webhooks.ClientProxies;
+
+[DependsOn(typeof(AbpWebhooksModule))]
+[DependsOn(typeof(WebhooksManagementHttpApiClientModule))]
+public class AbpWebHooksClientProxiesModule : AbpModule
+{
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs
new file mode 100644
index 000000000..9e5ae8b35
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs
@@ -0,0 +1,91 @@
+using LINGYUN.Abp.WebhooksManagement;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.Webhooks.ClientProxies;
+
+[Dependency(ReplaceServices = true)]
+public class ClientProxiesWebhookPublisher : IWebhookPublisher, ITransientDependency
+{
+ protected IWebhookPublishAppService PublishAppService { get; }
+
+ public ClientProxiesWebhookPublisher(
+ IWebhookPublishAppService publishAppService)
+ {
+ PublishAppService = publishAppService;
+ }
+
+ public async virtual Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null)
+ {
+ var input = new WebhookPublishInput
+ {
+ WebhookName = webhookName,
+ Data = JsonConvert.SerializeObject(data),
+ SendExactSameData = sendExactSameData,
+ };
+ if (headers != null)
+ {
+ input.Header = new WebhooksHeaderInput
+ {
+ UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders,
+ Headers = headers.Headers
+ };
+ }
+
+ await PublishAsync(input);
+ }
+
+ public async virtual Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null)
+ {
+ var input = new WebhookPublishInput
+ {
+ WebhookName = webhookName,
+ Data = JsonConvert.SerializeObject(data),
+ SendExactSameData = sendExactSameData,
+ TenantIds = new List
+ {
+ tenantId
+ },
+ };
+ if (headers != null)
+ {
+ input.Header = new WebhooksHeaderInput
+ {
+ UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders,
+ Headers = headers.Headers
+ };
+ }
+
+ await PublishAsync(input);
+ }
+
+ public async virtual Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null)
+ {
+ var input = new WebhookPublishInput
+ {
+ WebhookName = webhookName,
+ Data = JsonConvert.SerializeObject(data),
+ SendExactSameData = sendExactSameData,
+ TenantIds = tenantIds.ToList(),
+ };
+ if (headers != null)
+ {
+ input.Header = new WebhooksHeaderInput
+ {
+ UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders,
+ Headers = headers.Headers
+ };
+ }
+
+ await PublishAsync(input);
+ }
+
+ protected virtual async Task PublishAsync(WebhookPublishInput input)
+ {
+ await PublishAppService.PublishAsync(input);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml
new file mode 100644
index 000000000..17d32672d
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd
new file mode 100644
index 000000000..11da52550
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj
new file mode 100644
index 000000000..2cd64673f
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs
new file mode 100644
index 000000000..84798d8d2
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs
@@ -0,0 +1,54 @@
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using Volo.Abp.BackgroundJobs;
+using Volo.Abp.Features;
+using Volo.Abp.Guids;
+using Volo.Abp.Http.Client;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.Webhooks;
+
+//[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))]
+// 防止未引用实现无法发布到后台作业
+[DependsOn(typeof(AbpBackgroundJobsModule))]
+[DependsOn(typeof(AbpFeaturesModule))]
+[DependsOn(typeof(AbpGuidsModule))]
+[DependsOn(typeof(AbpHttpClientModule))]
+public class AbpWebhooksModule : AbpModule
+{
+ internal const string WebhooksClient = "__Abp_Webhooks_HttpClient";
+
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ AutoAddDefinitionProviders(context.Services);
+ }
+
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var options = context.Services.ExecutePreConfiguredActions();
+
+ context.Services.AddHttpClient(WebhooksClient, client =>
+ {
+ client.Timeout = options.TimeoutDuration;
+ });
+ }
+
+ private static void AutoAddDefinitionProviders(IServiceCollection services)
+ {
+ var definitionProviders = new List();
+
+ services.OnRegistred(context =>
+ {
+ if (typeof(WebhookDefinitionProvider).IsAssignableFrom(context.ImplementationType))
+ {
+ definitionProviders.Add(context.ImplementationType);
+ }
+ });
+
+ services.Configure(options =>
+ {
+ options.DefinitionProviders.AddIfNotContains(definitionProviders);
+ });
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs
new file mode 100644
index 000000000..072a76bcc
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs
@@ -0,0 +1,35 @@
+using System;
+using Volo.Abp.Collections;
+
+namespace LINGYUN.Abp.Webhooks;
+
+public class AbpWebhooksOptions
+{
+ ///
+ /// 默认超时时间
+ ///
+ public TimeSpan TimeoutDuration { get; set; }
+ ///
+ /// 默认最大发送次数
+ ///
+ public int MaxSendAttemptCount { get; set; }
+ ///
+ /// 是否达到最大连续失败次数时自动取消订阅
+ ///
+ public bool IsAutomaticSubscriptionDeactivationEnabled { get; set; }
+ ///
+ /// 取消订阅前最大连续失败次数
+ ///
+ public int MaxConsecutiveFailCountBeforeDeactivateSubscription { get; set; }
+
+ public ITypeList DefinitionProviders { get; }
+
+ public AbpWebhooksOptions()
+ {
+ TimeoutDuration = TimeSpan.FromSeconds(60);
+ MaxSendAttemptCount = 5;
+ MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3;
+
+ DefinitionProviders = new TypeList();
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs
new file mode 100644
index 000000000..b411e3a84
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs
@@ -0,0 +1,131 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.BackgroundJobs;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Uow;
+
+namespace LINGYUN.Abp.Webhooks.BackgroundWorker
+{
+ public class WebhookSenderJob : AsyncBackgroundJob, ITransientDependency
+ {
+ private readonly IUnitOfWorkManager _unitOfWorkManager;
+ private readonly IWebhookDefinitionManager _webhookDefinitionManager;
+ private readonly IWebhookSubscriptionManager _webhookSubscriptionManager;
+ private readonly IWebhookSendAttemptStore _webhookSendAttemptStore;
+ private readonly IWebhookSender _webhookSender;
+
+ private readonly AbpWebhooksOptions _options;
+
+ public WebhookSenderJob(
+ IUnitOfWorkManager unitOfWorkManager,
+ IWebhookDefinitionManager webhookDefinitionManager,
+ IWebhookSubscriptionManager webhookSubscriptionManager,
+ IWebhookSendAttemptStore webhookSendAttemptStore,
+ IWebhookSender webhookSender,
+ IOptions options)
+ {
+ _unitOfWorkManager = unitOfWorkManager;
+ _webhookDefinitionManager = webhookDefinitionManager;
+ _webhookSubscriptionManager = webhookSubscriptionManager;
+ _webhookSendAttemptStore = webhookSendAttemptStore;
+ _webhookSender = webhookSender;
+ _options = options.Value;
+ }
+
+ public override async Task ExecuteAsync(WebhookSenderArgs args)
+ {
+ var webhookDefinition = _webhookDefinitionManager.Get(args.WebhookName);
+
+ if (webhookDefinition.TryOnce)
+ {
+ try
+ {
+ await SendWebhook(args, webhookDefinition);
+ }
+ catch (Exception e)
+ {
+ Logger.LogWarning("An error occured while sending webhook with try once.", e);
+ // ignored
+ }
+ }
+ else
+ {
+ await SendWebhook(args, webhookDefinition);
+ }
+ }
+
+ private async Task SendWebhook(WebhookSenderArgs args, WebhookDefinition webhookDefinition)
+ {
+ if (args.WebhookEventId == default)
+ {
+ return;
+ }
+
+ if (args.WebhookSubscriptionId == default)
+ {
+ return;
+ }
+
+ if (!webhookDefinition.TryOnce)
+ {
+ var sendAttemptCount = await _webhookSendAttemptStore.GetSendAttemptCountAsync(
+ args.TenantId,
+ args.WebhookEventId,
+ args.WebhookSubscriptionId
+ );
+
+ if ((webhookDefinition.MaxSendAttemptCount > 0 && sendAttemptCount > webhookDefinition.MaxSendAttemptCount) ||
+ sendAttemptCount > _options.MaxSendAttemptCount)
+ {
+ return;
+ }
+ }
+
+ try
+ {
+ await _webhookSender.SendWebhookAsync(args);
+ }
+ catch (Exception)
+ {
+ // no need to retry to send webhook since subscription disabled
+ if (!await TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount(
+ args.TenantId,
+ args.WebhookSubscriptionId))
+ {
+ throw; //Throw exception to re-try sending webhook
+ }
+ }
+ }
+
+ private async Task TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount(
+ Guid? tenantId,
+ Guid subscriptionId)
+ {
+ if (!_options.IsAutomaticSubscriptionDeactivationEnabled)
+ {
+ return false;
+ }
+
+ var hasXConsecutiveFail = await _webhookSendAttemptStore
+ .HasXConsecutiveFailAsync(
+ tenantId,
+ subscriptionId,
+ _options.MaxConsecutiveFailCountBeforeDeactivateSubscription
+ );
+
+ if (!hasXConsecutiveFail)
+ {
+ return false;
+ }
+
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ await _webhookSubscriptionManager.ActivateWebhookSubscriptionAsync(subscriptionId, false);
+ await uow.CompleteAsync();
+ return true;
+ }
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs
new file mode 100644
index 000000000..be46197ae
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs
@@ -0,0 +1,140 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.BackgroundJobs;
+using Volo.Abp.Guids;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class DefaultWebhookPublisher : IWebhookPublisher, ITransientDependency
+ {
+ public IWebhookEventStore WebhookEventStore { get; set; }
+
+ private readonly ICurrentTenant _currentTenant;
+ private readonly IBackgroundJobManager _backgroundJobManager;
+ private readonly IWebhookSubscriptionManager _webhookSubscriptionManager;
+
+ public DefaultWebhookPublisher(
+ IWebhookSubscriptionManager webhookSubscriptionManager,
+ ICurrentTenant currentTenant,
+ IBackgroundJobManager backgroundJobManager)
+ {
+ _currentTenant = currentTenant;
+ _backgroundJobManager = backgroundJobManager;
+ _webhookSubscriptionManager = webhookSubscriptionManager;
+
+ WebhookEventStore = NullWebhookEventStore.Instance;
+ }
+
+ #region Async Publish Methods
+
+ public virtual async Task PublishAsync(
+ string webhookName,
+ object data,
+ bool sendExactSameData = false,
+ WebhookHeader headers = null)
+ {
+ var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(_currentTenant.Id, webhookName);
+ await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers);
+ }
+
+ public virtual async Task PublishAsync(
+ string webhookName,
+ object data,
+ Guid? tenantId,
+ bool sendExactSameData = false,
+ WebhookHeader headers = null)
+ {
+ var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(tenantId, webhookName);
+ await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers);
+ }
+
+ public virtual async Task PublishAsync(
+ Guid?[] tenantIds,
+ string webhookName,
+ object data,
+ bool sendExactSameData = false,
+ WebhookHeader headers = null)
+ {
+ var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(tenantIds, webhookName);
+ await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers);
+ }
+
+ protected virtual async Task PublishAsync(
+ string webhookName,
+ object data,
+ List webhookSubscriptions,
+ bool sendExactSameData = false,
+ WebhookHeader headers = null)
+ {
+ if (webhookSubscriptions.IsNullOrEmpty())
+ {
+ return;
+ }
+
+ var subscriptionsGroupedByTenant = webhookSubscriptions.GroupBy(x => x.TenantId);
+
+ foreach (var subscriptionGroupedByTenant in subscriptionsGroupedByTenant)
+ {
+ var webhookInfo = await SaveAndGetWebhookAsync(subscriptionGroupedByTenant.Key, webhookName, data);
+
+ foreach (var webhookSubscription in subscriptionGroupedByTenant)
+ {
+ var headersToSend = webhookSubscription.Headers;
+ if (headers != null)
+ {
+ if (headers.UseOnlyGivenHeaders)//do not use the headers defined in subscription
+ {
+ headersToSend = headers.Headers;
+ }
+ else
+ {
+ //use the headers defined in subscription. If additional headers has same header, use additional headers value.
+ foreach (var additionalHeader in headers.Headers)
+ {
+ headersToSend[additionalHeader.Key] = additionalHeader.Value;
+ }
+ }
+ }
+
+ await _backgroundJobManager.EnqueueAsync(new WebhookSenderArgs
+ {
+ TenantId = webhookSubscription.TenantId,
+ WebhookEventId = webhookInfo.Id,
+ Data = webhookInfo.Data,
+ WebhookName = webhookInfo.WebhookName,
+ WebhookSubscriptionId = webhookSubscription.Id,
+ Headers = headersToSend,
+ Secret = webhookSubscription.Secret,
+ WebhookUri = webhookSubscription.WebhookUri,
+ SendExactSameData = sendExactSameData
+ });
+ }
+ }
+ }
+
+ #endregion
+
+ protected virtual async Task SaveAndGetWebhookAsync(
+ Guid? tenantId,
+ string webhookName,
+ object data)
+ {
+ var webhookInfo = new WebhookEvent
+ {
+ WebhookName = webhookName,
+ Data = JsonConvert.SerializeObject(data),
+ TenantId = tenantId
+ };
+
+ var webhookId = await WebhookEventStore.InsertAndGetIdAsync(webhookInfo);
+ webhookInfo.Id = webhookId;
+
+ return webhookInfo;
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs
new file mode 100644
index 000000000..19982a68e
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs
@@ -0,0 +1,129 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class DefaultWebhookSender : IWebhookSender, ITransientDependency
+ {
+ public ILogger Logger { protected get; set; }
+
+ private readonly IWebhookManager _webhookManager;
+ private readonly IHttpClientFactory _httpClientFactory;
+
+ private const string FailedRequestDefaultContent = "Webhook Send Request Failed";
+
+ public DefaultWebhookSender(
+ IWebhookManager webhookManager,
+ IHttpClientFactory httpClientFactory)
+ {
+ _webhookManager = webhookManager;
+ _httpClientFactory = httpClientFactory;
+
+ Logger = NullLogger.Instance;
+ }
+
+ public async Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs)
+ {
+ if (webhookSenderArgs.WebhookEventId == default)
+ {
+ throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookEventId));
+ }
+
+ if (webhookSenderArgs.WebhookSubscriptionId == default)
+ {
+ throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId));
+ }
+
+ var webhookSendAttemptId = await _webhookManager.InsertAndGetIdWebhookSendAttemptAsync(webhookSenderArgs);
+
+ var request = CreateWebhookRequestMessage(webhookSenderArgs);
+
+ var serializedBody = await _webhookManager.GetSerializedBodyAsync(webhookSenderArgs);
+
+ _webhookManager.SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret);
+
+ AddAdditionalHeaders(request, webhookSenderArgs);
+
+ var isSucceed = false;
+ HttpStatusCode? statusCode = null;
+ var content = FailedRequestDefaultContent;
+
+ try
+ {
+ var response = await SendHttpRequest(request);
+ isSucceed = response.isSucceed;
+ statusCode = response.statusCode;
+ content = response.content;
+ }
+ catch (TaskCanceledException)
+ {
+ statusCode = HttpStatusCode.RequestTimeout;
+ content = "Request Timeout";
+ }
+ catch (HttpRequestException e)
+ {
+ content = e.Message;
+ }
+ catch (Exception e)
+ {
+ Logger.LogError("An error occured while sending a webhook request", e);
+ }
+ finally
+ {
+ await _webhookManager.StoreResponseOnWebhookSendAttemptAsync(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content);
+ }
+
+ if (!isSucceed)
+ {
+ throw new Exception($"Webhook sending attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}");
+ }
+
+ return webhookSendAttemptId;
+ }
+
+ ///
+ /// You can override this to change request message
+ ///
+ ///
+ protected virtual HttpRequestMessage CreateWebhookRequestMessage(WebhookSenderArgs webhookSenderArgs)
+ {
+ return new HttpRequestMessage(HttpMethod.Post, webhookSenderArgs.WebhookUri);
+ }
+
+ protected virtual void AddAdditionalHeaders(HttpRequestMessage request, WebhookSenderArgs webhookSenderArgs)
+ {
+ foreach (var header in webhookSenderArgs.Headers)
+ {
+ if (request.Headers.TryAddWithoutValidation(header.Key, header.Value))
+ {
+ continue;
+ }
+
+ if (request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value))
+ {
+ continue;
+ }
+
+ throw new Exception($"Invalid Header. SubscriptionId:{webhookSenderArgs.WebhookSubscriptionId},Header: {header.Key}:{header.Value}");
+ }
+ }
+
+ protected virtual async Task<(bool isSucceed, HttpStatusCode statusCode, string content)> SendHttpRequest(HttpRequestMessage request)
+ {
+ var client = _httpClientFactory.CreateClient(AbpWebhooksModule.WebhooksClient);
+
+ var response = await client.SendAsync(request);
+
+ var isSucceed = response.IsSuccessStatusCode;
+ var statusCode = response.StatusCode;
+ var content = await response.Content.ReadAsStringAsync();
+
+ return (isSucceed, statusCode, content);
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs
new file mode 100644
index 000000000..8cf48f520
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.Webhooks.Extensions
+{
+ public static class WebhookSubscriptionExtensions
+ {
+ ///
+ /// checks if subscribed to given webhook
+ ///
+ ///
+ public static bool IsSubscribed(this WebhookSubscriptionInfo webhookSubscription, string webhookName)
+ {
+ if (webhookSubscription.Webhooks.IsNullOrEmpty())
+ {
+ return false;
+ }
+
+ return webhookSubscription.Webhooks.Contains(webhookName);
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs
new file mode 100644
index 000000000..c170ede3d
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs
@@ -0,0 +1,16 @@
+using JetBrains.Annotations;
+using Volo.Abp.Localization;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookDefinitionContext
+ {
+ WebhookGroupDefinition AddGroup(
+ [NotNull] string name,
+ ILocalizableString displayName = null);
+
+ WebhookGroupDefinition GetGroupOrNull(string name);
+
+ void RemoveGroup(string name);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs
new file mode 100644
index 000000000..a284ab693
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookDefinitionManager
+ {
+ ///
+ /// Gets a webhook definition by name.
+ /// Returns null if there is no webhook definition with given name.
+ ///
+ WebhookDefinition GetOrNull(string name);
+
+ ///
+ /// Gets a webhook definition by name.
+ /// Throws exception if there is no webhook definition with given name.
+ ///
+ WebhookDefinition Get(string name);
+
+ ///
+ /// Gets all webhook definitions.
+ ///
+ IReadOnlyList GetAll();
+
+ ///
+ /// Gets all webhook group definitions.
+ ///
+ ///
+ IReadOnlyList GetGroups();
+
+ ///
+ /// Checks if given webhook name is available for given tenant.
+ ///
+ Task IsAvailableAsync(Guid? tenantId, string name);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs
new file mode 100644
index 000000000..1fb8f5c21
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookEventStore
+ {
+ ///
+ /// Inserts to persistent store
+ ///
+ Task InsertAndGetIdAsync(WebhookEvent webhookEvent);
+
+ ///
+ /// Gets Webhook info by id
+ ///
+ Task GetAsync(Guid? tenantId, Guid id);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs
new file mode 100644
index 000000000..1e8e5fd0c
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookManager
+ {
+ Task GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs);
+
+ void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret);
+
+ Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs);
+
+ Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs);
+
+ Task StoreResponseOnWebhookSendAttemptAsync(
+ Guid webhookSendAttemptId, Guid? tenantId,
+ HttpStatusCode? statusCode, string content);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs
new file mode 100644
index 000000000..3b53fec83
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookPublisher
+ {
+ ///
+ /// Sends webhooks to current tenant subscriptions (). with given data, (Checks permissions)
+ ///
+ ///
+ /// data to send
+ ///
+ /// True: It sends the exact same data as the parameter to clients.
+ ///
+ /// False: It sends data in . It is recommended way.
+ ///
+ ///
+ /// Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.
+ Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null);
+
+ ///
+ /// Sends webhooks to given tenant's subscriptions
+ ///
+ ///
+ /// data to send
+ ///
+ /// Target tenant id
+ ///
+ ///
+ /// True: It sends the exact same data as the parameter to clients.
+ ///
+ /// False: It sends data in . It is recommended way.
+ ///
+ ///
+ /// Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.
+ Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null);
+
+ ///
+ /// Sends webhooks to given tenant's subscriptions
+ ///
+ ///
+ /// data to send
+ ///
+ /// Target tenant id(s)
+ ///
+ ///
+ /// True: It sends the exact same data as the parameter to clients.
+ ///
+ /// False: It sends data in . It is recommended way.
+ ///
+ ///
+ /// Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.
+ Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null);
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs
new file mode 100644
index 000000000..2e32bc63f
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookSendAttemptStore
+ {
+ Task GetAsync(Guid? tenantId, Guid id);
+
+ ///
+ /// Returns work item count by given web hook id and subscription id, (How many times publisher tried to send web hook)
+ ///
+ Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId);
+
+ ///
+ /// Checks is there any successful webhook attempt in last items. Should return true if there are not X number items
+ ///
+ Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount);
+
+ Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, int skipCount);
+
+ Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs
new file mode 100644
index 000000000..d30e110e6
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookSender
+ {
+ ///
+ /// Tries to send webhook with given transactionId and stores process in
+ /// Should throw exception if fails or response status not succeed
+ ///
+ /// arguments
+ /// Webhook send attempt id
+ Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs
new file mode 100644
index 000000000..956b2dbd3
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public interface IWebhookSubscriptionManager
+ {
+ ///
+ /// Returns subscription for given id.
+ ///
+ /// Unique identifier of
+ Task GetAsync(Guid id);
+
+ ///
+ /// Returns all subscriptions of tenant
+ ///
+ ///
+ /// Target tenant id.
+ ///
+ Task> GetAllSubscriptionsAsync(Guid? tenantId);
+
+ ///
+ /// Returns all subscriptions for given webhook.
+ ///
+ ///
+ ///
+ /// Target tenant id.
+ ///
+ Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName);
+
+ ///
+ /// Returns all subscriptions of tenant
+ ///
+ ///
+ Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds);
+
+ ///
+ /// Returns all subscriptions for given webhook.
+ ///
+ ///
+ ///
+ /// Target tenant id(s).
+ ///
+ Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName);
+
+ ///
+ /// Checks if tenant subscribed for a webhook. (Checks if webhook features are granted)
+ ///
+ ///
+ /// Target tenant id(s).
+ ///
+ ///
+ Task IsSubscribedAsync(Guid? tenantId, string webhookName);
+
+ ///
+ /// If id is the default(Guid) adds new subscription, else updates current one. (Checks if webhook features are granted)
+ ///
+ Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription);
+
+ ///
+ /// Activates/Deactivates given webhook subscription
+ ///
+ /// unique identifier of
+ /// IsActive
+ Task ActivateWebhookSubscriptionAsync(Guid id, bool active);
+
+ ///
+ /// Delete given webhook subscription.
+ ///
+ /// unique identifier of
+ Task DeleteSubscriptionAsync(Guid id);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs
new file mode 100644
index 000000000..23849ad4e
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ ///
+ /// This interface should be implemented by vendors to make webhooks working.
+ ///
+ public interface IWebhookSubscriptionsStore
+ {
+ ///
+ /// returns subscription
+ ///
+ /// webhook subscription id
+ ///
+ Task GetAsync(Guid id);
+
+ ///
+ /// Saves webhook subscription to a persistent store.
+ ///
+ /// webhook subscription information
+ Task InsertAsync(WebhookSubscriptionInfo webhookSubscription);
+
+ ///
+ /// Updates webhook subscription to a persistent store.
+ ///
+ /// webhook subscription information
+ Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription);
+
+ ///
+ /// Deletes subscription if exists
+ ///
+ /// primary key
+ ///
+ Task DeleteAsync(Guid id);
+
+ ///
+ /// Returns all subscriptions of given tenant including deactivated
+ ///
+ ///
+ /// Target tenant id.
+ ///
+ Task> GetAllSubscriptionsAsync(Guid? tenantId);
+
+ ///
+ /// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
+ ///
+ ///
+ /// Target tenant id.
+ ///
+ ///
+ ///
+ Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName);
+
+ ///
+ /// Returns all subscriptions of given tenant including deactivated
+ ///
+ ///
+ /// Target tenant id(s).
+ ///
+ Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds);
+
+ ///
+ /// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
+ ///
+ ///
+ /// Target tenant id(s).
+ ///
+ ///
+ ///
+ Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName);
+
+ ///
+ /// Checks if tenant subscribed for a webhook
+ ///
+ ///
+ /// Target tenant id(s).
+ ///
+ /// Name of the webhook
+ Task IsSubscribedAsync(Guid? tenantId, string webhookName);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs
new file mode 100644
index 000000000..069726e67
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ ///
+ /// Null pattern implementation of .
+ /// It's used if is not implemented by actual persistent store
+ ///
+ public class NullWebhookEventStore : IWebhookEventStore
+ {
+ public static NullWebhookEventStore Instance { get; } = new NullWebhookEventStore();
+
+ public Task InsertAndGetIdAsync(WebhookEvent webhookEvent)
+ {
+ return Task.FromResult(default);
+ }
+
+ public Task GetAsync(Guid? tenantId, Guid id)
+ {
+ return Task.FromResult(default);
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs
new file mode 100644
index 000000000..7254b1ae6
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class NullWebhookSendAttemptStore : IWebhookSendAttemptStore
+ {
+ public static NullWebhookSendAttemptStore Instance = new NullWebhookSendAttemptStore();
+
+ public Task InsertAsync(WebhookSendAttempt webhookSendAttempt)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task UpdateAsync(WebhookSendAttempt webhookSendAttempt)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task GetAsync(Guid? tenantId, Guid id)
+ {
+ return Task.FromResult(default);
+ }
+
+ public Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId)
+ {
+ return Task.FromResult(int.MaxValue);
+ }
+
+ public Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount)
+ {
+ return default;
+ }
+
+ public Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount,
+ int skipCount)
+ {
+ return Task.FromResult(ValueTuple.Create(0, new List() as IReadOnlyCollection));
+ }
+
+ public Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId)
+ {
+ return Task.FromResult(new List());
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs
new file mode 100644
index 000000000..4214d1d25
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ ///
+ /// Null pattern implementation of .
+ /// It's used if is not implemented by actual persistent store
+ ///
+ public class NullWebhookSubscriptionsStore : IWebhookSubscriptionsStore
+ {
+ public static NullWebhookSubscriptionsStore Instance { get; } = new NullWebhookSubscriptionsStore();
+
+ public Task GetAsync(Guid id)
+ {
+ return Task.FromResult(default);
+ }
+
+ public WebhookSubscriptionInfo Get(Guid id)
+ {
+ return default;
+ }
+
+ public Task InsertAsync(WebhookSubscriptionInfo webhookSubscription)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task DeleteAsync(Guid id)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task> GetAllSubscriptionsAsync(Guid? tenantId)
+ {
+ return Task.FromResult(new List());
+ }
+
+ public Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName)
+ {
+ return Task.FromResult(new List());
+ }
+
+ public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds)
+ {
+ return Task.FromResult(new List());
+ }
+
+ public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName)
+ {
+ return Task.FromResult(new List());
+ }
+
+ public Task IsSubscribedAsync(Guid? tenantId, string webhookName)
+ {
+ return Task.FromResult(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs
new file mode 100644
index 000000000..1c1461e8a
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using Volo.Abp.Localization;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class WebhookDefinition
+ {
+ ///
+ /// Unique name of the webhook.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Tries to send webhook only one time without checking to send attempt count
+ ///
+ public bool TryOnce { get; set; }
+
+ ///
+ /// Defined maximum number of sending times
+ ///
+ public int MaxSendAttemptCount { get; set; }
+
+ ///
+ /// Display name of the webhook.
+ /// Optional.
+ ///
+ public ILocalizableString DisplayName { get; set; }
+
+ ///
+ /// Description for the webhook.
+ /// Optional.
+ ///
+ public ILocalizableString Description { get; set; }
+
+ public List RequiredFeatures { get; set; }
+
+ public WebhookDefinition(string name, ILocalizableString displayName = null, ILocalizableString description = null)
+ {
+ if (name.IsNullOrWhiteSpace())
+ {
+ throw new ArgumentNullException(nameof(name), $"{nameof(name)} can not be null, empty or whitespace!");
+ }
+
+ Name = name.Trim();
+ DisplayName = displayName;
+ Description = description;
+
+ RequiredFeatures = new List();
+ }
+
+ public WebhookDefinition WithFeature(params string[] features)
+ {
+ if (!features.IsNullOrEmpty())
+ {
+ RequiredFeatures.AddRange(features);
+ }
+
+ return this;
+ }
+
+ public override string ToString()
+ {
+ return $"[{nameof(WebhookDefinition)} {Name}]";
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs
new file mode 100644
index 000000000..5e8987a8b
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs
@@ -0,0 +1,55 @@
+using JetBrains.Annotations;
+using System.Collections.Generic;
+using Volo.Abp;
+using Volo.Abp.Localization;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class WebhookDefinitionContext : IWebhookDefinitionContext
+ {
+ protected Dictionary Groups { get; }
+
+ public WebhookDefinitionContext(Dictionary webhooks)
+ {
+ Groups = webhooks;
+ }
+
+ public WebhookGroupDefinition AddGroup(
+ [NotNull] string name,
+ ILocalizableString displayName = null)
+ {
+ Check.NotNull(name, nameof(name));
+
+ if (Groups.ContainsKey(name))
+ {
+ throw new AbpException($"There is already an existing webhook group with name: {name}");
+ }
+
+ return Groups[name] = new WebhookGroupDefinition(name, displayName);
+ }
+
+ public WebhookGroupDefinition GetGroupOrNull([NotNull] string name)
+ {
+ Check.NotNull(name, nameof(name));
+
+ if (!Groups.ContainsKey(name))
+ {
+ return null;
+ }
+
+ return Groups[name];
+ }
+
+ public void RemoveGroup(string name)
+ {
+ Check.NotNull(name, nameof(name));
+
+ if (!Groups.ContainsKey(name))
+ {
+ throw new AbpException($"Undefined notification webhook group: '{name}'.");
+ }
+
+ Groups.Remove(name);
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs
new file mode 100644
index 000000000..c0f96f20e
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs
@@ -0,0 +1,139 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+using Volo.Abp.MultiTenancy;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency
+ {
+ protected IDictionary WebhookGroupDefinitions => _lazyWebhookGroupDefinitions.Value;
+ private readonly Lazy> _lazyWebhookGroupDefinitions;
+
+ protected IDictionary WebhookDefinitions => _lazyWebhookDefinitions.Value;
+ private readonly Lazy> _lazyWebhookDefinitions;
+
+ private readonly IServiceProvider _serviceProvider;
+ private readonly AbpWebhooksOptions _options;
+
+ public WebhookDefinitionManager(
+ IServiceProvider serviceProvider,
+ IOptions options)
+ {
+ _serviceProvider = serviceProvider;
+ _options = options.Value;
+
+ _lazyWebhookGroupDefinitions = new Lazy>(CreateWebhookGroupDefinitions);
+ _lazyWebhookDefinitions = new Lazy>(CreateWebhookDefinitions);
+ }
+
+ public WebhookDefinition GetOrNull(string name)
+ {
+ if (!WebhookDefinitions.ContainsKey(name))
+ {
+ return null;
+ }
+
+ return WebhookDefinitions[name];
+ }
+
+ public WebhookDefinition Get(string name)
+ {
+ if (!WebhookDefinitions.ContainsKey(name))
+ {
+ throw new KeyNotFoundException($"Webhook definitions does not contain a definition with the key \"{name}\".");
+ }
+
+ return WebhookDefinitions[name];
+ }
+
+ public IReadOnlyList GetAll()
+ {
+ return WebhookDefinitions.Values.ToImmutableList();
+ }
+
+ public IReadOnlyList GetGroups()
+ {
+ return WebhookGroupDefinitions.Values.ToImmutableList();
+ }
+
+ public async Task IsAvailableAsync(Guid? tenantId, string name)
+ {
+ if (tenantId == null) // host allowed to subscribe all webhooks
+ {
+ return true;
+ }
+
+ var webhookDefinition = GetOrNull(name);
+
+ if (webhookDefinition == null)
+ {
+ return false;
+ }
+
+ if (webhookDefinition.RequiredFeatures?.Any() == false)
+ {
+ return true;
+ }
+
+ var currentTenant = _serviceProvider.GetRequiredService();
+ var featureChecker = _serviceProvider.GetRequiredService();
+ using (currentTenant.Change(tenantId))
+ {
+ if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray()))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected virtual Dictionary CreateWebhookDefinitions()
+ {
+ var definitions = new Dictionary();
+
+ foreach (var groupDefinition in WebhookGroupDefinitions.Values)
+ {
+ foreach (var webhook in groupDefinition.Webhooks)
+ {
+ if (definitions.ContainsKey(webhook.Name))
+ {
+ throw new AbpException("Duplicate webhook name: " + webhook.Name);
+ }
+
+ definitions[webhook.Name] = webhook;
+ }
+ }
+
+ return definitions;
+ }
+
+ protected virtual Dictionary CreateWebhookGroupDefinitions()
+ {
+ var definitions = new Dictionary();
+
+ using (var scope = _serviceProvider.CreateScope())
+ {
+ var providers = _options
+ .DefinitionProviders
+ .Select(p => scope.ServiceProvider.GetRequiredService(p) as WebhookDefinitionProvider)
+ .ToList();
+
+ foreach (var provider in providers)
+ {
+ provider.Define(new WebhookDefinitionContext(definitions));
+ }
+ }
+
+ return definitions;
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs
new file mode 100644
index 000000000..ead9ab679
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public abstract class WebhookDefinitionProvider : ITransientDependency
+ {
+ ///
+ /// Used to add/manipulate webhook definitions.
+ ///
+ /// Context,
+ public abstract void Define(IWebhookDefinitionContext context);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs
new file mode 100644
index 000000000..bd32fa76d
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ ///
+ /// Store created web hooks. To see who get that webhook check with and you can get
+ ///
+ public class WebhookEvent
+ {
+ public Guid Id { get; set; }
+
+ ///
+ /// Webhook unique name
+ ///
+ public string WebhookName { get; set; }
+
+ ///
+ /// Webhook data as JSON string.
+ ///
+ public string Data { get; set; }
+
+ public DateTime CreationTime { get; set; }
+
+ public Guid? TenantId { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public DateTime? DeletionTime { get; set; }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs
new file mode 100644
index 000000000..ae58049ed
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs
@@ -0,0 +1,101 @@
+using JetBrains.Annotations;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Volo.Abp;
+using Volo.Abp.Localization;
+
+namespace LINGYUN.Abp.Webhooks;
+
+public class WebhookGroupDefinition
+{
+ [NotNull]
+ public string Name { get; set; }
+ public Dictionary Properties { get; }
+
+ private ILocalizableString _displayName;
+ public ILocalizableString DisplayName
+ {
+ get {
+ return _displayName;
+ }
+ set {
+ _displayName = value;
+ }
+ }
+
+ public IReadOnlyList Webhooks => _webhooks.ToImmutableList();
+ private readonly List _webhooks;
+ public object this[string name] {
+ get => Properties.GetOrDefault(name);
+ set => Properties[name] = value;
+ }
+
+ protected internal WebhookGroupDefinition(
+ string name,
+ ILocalizableString displayName = null)
+ {
+ Name = name;
+ DisplayName = displayName ?? new FixedLocalizableString(Name);
+
+ Properties = new Dictionary();
+ _webhooks = new List();
+ }
+
+ public virtual WebhookDefinition AddWebhook(
+ string name,
+ ILocalizableString displayName = null,
+ ILocalizableString description = null)
+ {
+ if (Webhooks.Any(hook => hook.Name.Equals(name)))
+ {
+ throw new AbpException($"There is already an existing webhook with name: {name} in group {Name}");
+ }
+
+ var webhook = new WebhookDefinition(
+ name,
+ displayName,
+ description
+ );
+
+ _webhooks.Add(webhook);
+
+ return webhook;
+ }
+
+ public virtual void AddWebhooks(params WebhookDefinition[] webhooks)
+ {
+ foreach (var webhook in webhooks)
+ {
+ if (Webhooks.Any(hook => hook.Name.Equals(webhook.Name)))
+ {
+ throw new AbpException($"There is already an existing webhook with name: {webhook.Name} in group {Name}");
+ }
+ }
+
+ _webhooks.AddRange(webhooks);
+ }
+
+
+
+ [CanBeNull]
+ public WebhookDefinition GetWebhookOrNull([NotNull] string name)
+ {
+ Check.NotNull(name, nameof(name));
+
+ foreach (var webhook in Webhooks)
+ {
+ if (webhook.Name == name)
+ {
+ return webhook;
+ }
+ }
+
+ return null;
+ }
+
+ public override string ToString()
+ {
+ return $"[{nameof(WebhookGroupDefinition)} {Name}]";
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs
new file mode 100644
index 000000000..91442552f
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class WebhookHeader
+ {
+ ///
+ /// If true, webhook will only contain given headers. If false given headers will be added to predefined headers in subscription.
+ /// Default is false
+ ///
+ public bool UseOnlyGivenHeaders { get; set; }
+
+ ///
+ /// That headers will be sent with the webhook.
+ ///
+ public IDictionary Headers { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs
new file mode 100644
index 000000000..854795786
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs
@@ -0,0 +1,80 @@
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public abstract class WebhookManager : IWebhookManager
+ {
+ private const string SignatureHeaderKey = "sha256";
+ private const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}";
+ private const string SignatureHeaderName = "abp-webhook-signature";
+ protected IWebhookSendAttemptStore WebhookSendAttemptStore { get; }
+
+ protected WebhookManager(
+ IWebhookSendAttemptStore webhookSendAttemptStore)
+ {
+ WebhookSendAttemptStore = webhookSendAttemptStore;
+ }
+
+ public virtual async Task GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs)
+ {
+ var data = JsonConvert.SerializeObject(webhookSenderArgs.Data);
+
+ var attemptNumber = await WebhookSendAttemptStore.GetSendAttemptCountAsync(
+ webhookSenderArgs.TenantId,
+ webhookSenderArgs.WebhookEventId,
+ webhookSenderArgs.WebhookSubscriptionId);
+
+ return new WebhookPayload(
+ webhookSenderArgs.WebhookEventId.ToString(),
+ webhookSenderArgs.WebhookName,
+ attemptNumber)
+ {
+ Data = data
+ };
+ }
+
+ public virtual void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ if (string.IsNullOrWhiteSpace(serializedBody))
+ {
+ throw new ArgumentNullException(nameof(serializedBody));
+ }
+
+ request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json");
+
+ var secretBytes = Encoding.UTF8.GetBytes(secret);
+ var headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, serializedBody.Sha256(secretBytes));
+
+ request.Headers.Add(SignatureHeaderName, headerValue);
+ }
+
+ public virtual async Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs)
+ {
+ if (webhookSenderArgs.SendExactSameData)
+ {
+ return webhookSenderArgs.Data;
+ }
+
+ var payload = await GetWebhookPayloadAsync(webhookSenderArgs);
+
+ var serializedBody = JsonConvert.SerializeObject(payload);
+
+ return serializedBody;
+ }
+
+ public abstract Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs);
+
+ public abstract Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content);
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs
new file mode 100644
index 000000000..ff287cbd0
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class WebhookPayload
+ {
+ public string Id { get; set; }
+
+ public string WebhookEvent { get; set; }
+
+ public int Attempt { get; set; }
+
+ public dynamic Data { get; set; }
+
+ public DateTime CreationTimeUtc { get; set; }
+
+ public WebhookPayload(string id, string webhookEvent, int attempt)
+ {
+ if (id.IsNullOrWhiteSpace())
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+
+ if (webhookEvent.IsNullOrWhiteSpace())
+ {
+ throw new ArgumentNullException(nameof(webhookEvent));
+ }
+
+ Id = id;
+ WebhookEvent = webhookEvent;
+ Attempt = attempt;
+ CreationTimeUtc = DateTime.UtcNow;
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs
new file mode 100644
index 000000000..8189f8f72
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Net;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ ///
+ /// Table for store webhook work items. Each item stores web hook send attempt of to subscribed tenants
+ ///
+ public class WebhookSendAttempt
+ {
+ public Guid Id { get; set; }
+
+ ///
+ /// foreign id
+ ///
+ public Guid WebhookEventId { get; set; }
+
+ ///
+ /// foreign id
+ ///
+ public Guid WebhookSubscriptionId { get; set; }
+
+ ///
+ /// Webhook response content that webhook endpoint send back
+ ///
+ public string Response { get; set; }
+
+ ///
+ /// Webhook response status code that webhook endpoint send back
+ ///
+ public HttpStatusCode? ResponseStatusCode { get; set; }
+
+ public DateTime CreationTime { get; set; }
+
+ public DateTime? LastModificationTime { get; set; }
+
+ public Guid? TenantId { get; set; }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs
new file mode 100644
index 000000000..cb30acbf9
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class WebhookSenderArgs
+ {
+ public Guid? TenantId { get; set; }
+
+ //Webhook information
+
+ ///
+ /// foreign id
+ ///
+ public Guid WebhookEventId { get; set; }
+
+ ///
+ /// Webhook unique name
+ ///
+ public string WebhookName { get; set; }
+
+ ///
+ /// Webhook data as JSON string.
+ ///
+ public string Data { get; set; }
+
+ //Subscription information
+
+ ///
+ /// foreign id
+ ///
+ public Guid WebhookSubscriptionId { get; set; }
+
+ ///
+ /// Subscription webhook endpoint
+ ///
+ public string WebhookUri { get; set; }
+
+ ///
+ /// Webhook secret
+ ///
+ public string Secret { get; set; }
+
+ ///
+ /// Gets a set of additional HTTP headers.That headers will be sent with the webhook.
+ ///
+ public IDictionary Headers { get; set; }
+
+ ///
+ /// True: It sends the exact same data as the parameter to clients.
+ ///
+ /// False: It sends data in . It is recommended way.
+ ///
+ ///
+ public bool SendExactSameData { get; set; }
+
+ public WebhookSenderArgs()
+ {
+ Headers = new Dictionary();
+ }
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs
new file mode 100644
index 000000000..9e4e3bf2a
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class WebhookSubscriptionInfo
+ {
+ public Guid Id { get; set; }
+ ///
+ /// Subscribed Tenant's id .
+ ///
+ public Guid? TenantId { get; set; }
+
+ ///
+ /// Subscription webhook endpoint
+ ///
+ public string WebhookUri { get; set; }
+
+ ///
+ /// Webhook secret
+ ///
+ public string Secret { get; set; }
+
+ ///
+ /// Is subscription active
+ ///
+ public bool IsActive { get; set; }
+
+ ///
+ /// Subscribed webhook definitions unique names.It contains webhook definitions list as json
+ ///
+ /// Do not change it manually.
+ /// Use ,
+ /// ,
+ /// and
+ /// to change it.
+ ///
+ ///
+ public List Webhooks { get; set; }
+
+ ///
+ /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. It contains webhook header dictionary as json
+ ///
+ /// Do not change it manually.
+ /// Use ,
+ /// ,
+ /// ,
+ /// to change it.
+ ///
+ ///
+ public IDictionary Headers { get; set; }
+
+ public WebhookSubscriptionInfo()
+ {
+ IsActive = true;
+ Headers = new Dictionary();
+ Webhooks = new List();
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs
new file mode 100644
index 000000000..fe1a9c697
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Authorization;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Guids;
+using Volo.Abp.Uow;
+
+namespace LINGYUN.Abp.Webhooks
+{
+ public class WebhookSubscriptionManager : IWebhookSubscriptionManager, ITransientDependency
+ {
+ public IWebhookSubscriptionsStore WebhookSubscriptionsStore { get; set; }
+
+ private readonly IGuidGenerator _guidGenerator;
+ private readonly IUnitOfWorkManager _unitOfWorkManager;
+ private readonly IWebhookDefinitionManager _webhookDefinitionManager;
+
+ private const string WebhookSubscriptionSecretPrefix = "whs_";
+
+ public WebhookSubscriptionManager(
+ IGuidGenerator guidGenerator,
+ IUnitOfWorkManager unitOfWorkManager,
+ IWebhookDefinitionManager webhookDefinitionManager)
+ {
+ _guidGenerator = guidGenerator;
+ _unitOfWorkManager = unitOfWorkManager;
+ _webhookDefinitionManager = webhookDefinitionManager;
+
+ WebhookSubscriptionsStore = NullWebhookSubscriptionsStore.Instance;
+ }
+
+ public virtual async Task GetAsync(Guid id)
+ {
+ return await WebhookSubscriptionsStore.GetAsync(id);
+ }
+
+ public virtual async Task> GetAllSubscriptionsAsync(Guid? tenantId)
+ {
+ return await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId);
+ }
+
+ public virtual async Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName)
+ {
+ if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
+ {
+ return new List();
+ }
+
+ return (await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId, webhookName)).ToList();
+ }
+
+ public virtual async Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds)
+ {
+ return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(tenantIds)).ToList();
+ }
+
+ public virtual async Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName)
+ {
+ var featureGrantedTenants = new List();
+ foreach (var tenantId in tenantIds)
+ {
+ if (await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
+ {
+ featureGrantedTenants.Add(tenantId);
+ }
+ }
+
+ return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(featureGrantedTenants.ToArray(), webhookName)).ToList();
+ }
+
+ public virtual async Task IsSubscribedAsync(Guid? tenantId, string webhookName)
+ {
+ if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
+ {
+ return false;
+ }
+
+ return await WebhookSubscriptionsStore.IsSubscribedAsync(tenantId, webhookName);
+ }
+
+ public virtual async Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription)
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ await CheckIfPermissionsGrantedAsync(webhookSubscription);
+
+ if (webhookSubscription.Id == default)
+ {
+ webhookSubscription.Id = _guidGenerator.Create();
+ webhookSubscription.Secret = WebhookSubscriptionSecretPrefix + Guid.NewGuid().ToString("N");
+ await WebhookSubscriptionsStore.InsertAsync(webhookSubscription);
+ }
+ else
+ {
+ await WebhookSubscriptionsStore.UpdateAsync(webhookSubscription);
+ }
+
+ await uow.SaveChangesAsync();
+ }
+ }
+
+ public virtual async Task ActivateWebhookSubscriptionAsync(Guid id, bool active)
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ var webhookSubscription = await WebhookSubscriptionsStore.GetAsync(id);
+ webhookSubscription.IsActive = active;
+
+ await uow.SaveChangesAsync();
+ }
+ }
+
+ public virtual async Task DeleteSubscriptionAsync(Guid id)
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ await WebhookSubscriptionsStore.DeleteAsync(id);
+
+ await uow.SaveChangesAsync();
+ }
+ }
+
+ public virtual async Task AddWebhookAsync(WebhookSubscriptionInfo subscription, string webhookName)
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ await CheckPermissionsAsync(subscription.TenantId, webhookName);
+ webhookName = webhookName.Trim();
+ if (webhookName.IsNullOrWhiteSpace())
+ {
+ throw new ArgumentNullException(nameof(webhookName), $"{nameof(webhookName)} can not be null, empty or whitespace!");
+ }
+
+ if (!subscription.Webhooks.Contains(webhookName))
+ {
+ subscription.Webhooks.Add(webhookName);
+
+ await WebhookSubscriptionsStore.UpdateAsync(subscription);
+ }
+
+ await uow.SaveChangesAsync();
+ }
+ }
+
+ #region PermissionCheck
+
+ protected virtual async Task CheckIfPermissionsGrantedAsync(WebhookSubscriptionInfo webhookSubscription)
+ {
+ if (webhookSubscription.Webhooks.IsNullOrEmpty())
+ {
+ return;
+ }
+
+ foreach (var webhookDefinition in webhookSubscription.Webhooks)
+ {
+ await CheckPermissionsAsync(webhookSubscription.TenantId, webhookDefinition);
+ }
+ }
+
+ protected virtual async Task CheckPermissionsAsync(Guid? tenantId, string webhookName)
+ {
+ if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName))
+ {
+ throw new AbpAuthorizationException($"Tenant \"{tenantId}\" must have necessary feature(s) to use webhook \"{webhookName}\"");
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs
new file mode 100644
index 000000000..f4202f6e2
--- /dev/null
+++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs
@@ -0,0 +1,13 @@
+using System.Security.Cryptography;
+
+namespace System;
+
+internal static class AbpStringCryptographyExtensions
+{
+ public static string Sha256(this string planText, byte[] salt)
+ {
+ var data = planText.GetBytes();
+ using var hmacsha256 = new HMACSHA256(salt);
+ return BitConverter.ToString(hmacsha256.ComputeHash(data));
+ }
+}