Browse Source

Webhooks API: WIP - Initial commit

pull/937/head
eiximenis 6 years ago
parent
commit
8677b8d240
44 changed files with 1414 additions and 6 deletions
  1. +17
    -0
      docker-compose.override.yml
  2. +15
    -0
      docker-compose.yml
  3. +105
    -0
      eShopOnContainers-ServicesAndWebApps.sln
  4. +1
    -1
      src/Services/Catalog/Catalog.API/Startup.cs
  5. +25
    -5
      src/Services/Identity/Identity.API/Configuration/Config.cs
  6. +1
    -0
      src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs
  7. +35
    -0
      src/Services/Webhooks/Webhooks.API/Controller/WebhookSubscriptionRequest.cs
  8. +115
    -0
      src/Services/Webhooks/Webhooks.API/Controller/WebhooksController.cs
  9. +19
    -0
      src/Services/Webhooks/Webhooks.API/Dockerfile
  10. +11
    -0
      src/Services/Webhooks/Webhooks.API/Exceptions/WebhooksDomainException.cs
  11. +13
    -0
      src/Services/Webhooks/Webhooks.API/Infrastructure/ActionResult/InternalServerErrorObjectResult.cs
  12. +72
    -0
      src/Services/Webhooks/Webhooks.API/Infrastructure/HttpGlobalExceptionFilter.cs
  13. +18
    -0
      src/Services/Webhooks/Webhooks.API/Infrastructure/WebhooksContext.cs
  14. +18
    -0
      src/Services/Webhooks/Webhooks.API/Model/WebhookSubscription.cs
  15. +12
    -0
      src/Services/Webhooks/Webhooks.API/Model/WebhookType.cs
  16. +27
    -0
      src/Services/Webhooks/Webhooks.API/Program.cs
  17. +32
    -0
      src/Services/Webhooks/Webhooks.API/Properties/launchSettings.json
  18. +27
    -0
      src/Services/Webhooks/Webhooks.API/Services/GrantUrlTesterService.cs
  19. +12
    -0
      src/Services/Webhooks/Webhooks.API/Services/IGrantUrlTesterService.cs
  20. +7
    -0
      src/Services/Webhooks/Webhooks.API/Services/IIdentityService.cs
  21. +21
    -0
      src/Services/Webhooks/Webhooks.API/Services/IdentityService.cs
  22. +333
    -0
      src/Services/Webhooks/Webhooks.API/Startup.cs
  23. +31
    -0
      src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj
  24. +9
    -0
      src/Services/Webhooks/Webhooks.API/appsettings.Development.json
  25. +8
    -0
      src/Services/Webhooks/Webhooks.API/appsettings.json
  26. +20
    -0
      src/Web/WebhookClient/Dockerfile
  27. +12
    -0
      src/Web/WebhookClient/HeaderNames.cs
  28. +26
    -0
      src/Web/WebhookClient/Pages/Error.cshtml
  29. +23
    -0
      src/Web/WebhookClient/Pages/Error.cshtml.cs
  30. +10
    -0
      src/Web/WebhookClient/Pages/Index.cshtml
  31. +17
    -0
      src/Web/WebhookClient/Pages/Index.cshtml.cs
  32. +8
    -0
      src/Web/WebhookClient/Pages/Privacy.cshtml
  33. +16
    -0
      src/Web/WebhookClient/Pages/Privacy.cshtml.cs
  34. +25
    -0
      src/Web/WebhookClient/Pages/Shared/_CookieConsentPartial.cshtml
  35. +77
    -0
      src/Web/WebhookClient/Pages/Shared/_Layout.cshtml
  36. +18
    -0
      src/Web/WebhookClient/Pages/Shared/_ValidationScriptsPartial.cshtml
  37. +3
    -0
      src/Web/WebhookClient/Pages/_ViewImports.cshtml
  38. +3
    -0
      src/Web/WebhookClient/Pages/_ViewStart.cshtml
  39. +24
    -0
      src/Web/WebhookClient/Program.cs
  40. +32
    -0
      src/Web/WebhookClient/Properties/launchSettings.json
  41. +81
    -0
      src/Web/WebhookClient/Startup.cs
  42. +17
    -0
      src/Web/WebhookClient/WebhookClient.csproj
  43. +10
    -0
      src/Web/WebhookClient/appsettings.Development.json
  44. +8
    -0
      src/Web/WebhookClient/appsettings.json

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

@ -41,6 +41,7 @@ services:
- OrderingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 - OrderingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
- MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120 - MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120
- WebShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5121 - WebShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5121
- WebhooksApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5113
- UseCustomizationData=True - UseCustomizationData=True
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
- OrchestratorType=${ORCHESTRATOR_TYPE} - OrchestratorType=${ORCHESTRATOR_TYPE}
@ -182,6 +183,15 @@ services:
ports: ports:
- "5109:80" # Important: In a production environment your should remove the external port (5109) kept here for microservice debugging purposes. - "5109:80" # Important: In a production environment your should remove the external port (5109) kept here for microservice debugging purposes.
# The API Gateway redirects and access through the internal port (80). # The API Gateway redirects and access through the internal port (80).
webhooks.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_WEBHOOKS_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.WebhooksDb;User Id=sa;Password=Pass@word}
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
ports:
- "5113:80" # Important: In a production environment your should remove the external port (5109) kept here for microservice debugging purposes.
# The API Gateway redirects and access through the internal port (80).
mobileshoppingapigw: mobileshoppingapigw:
environment: environment:
@ -380,3 +390,10 @@ services:
ports: ports:
- "5100:80" - "5100:80"
webhooks.client:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- Token=6168DB8D-DC58-4094-AF24-483278923590
ports:
- "5114:80"

+ 15
- 0
docker-compose.yml View File

@ -86,6 +86,14 @@ services:
- nosql.data - nosql.data
- rabbitmq - rabbitmq
webhooks.api:
image: eshop/webhooks.api:${TAG:-latest}
build:
context: .
dockerfile: src/Services/Webhooks/Webhooks.API/Dockerfile
depends_on:
- sql.data
mobileshoppingapigw: mobileshoppingapigw:
image: eshop/ocelotapigw:${TAG:-latest} image: eshop/ocelotapigw:${TAG:-latest}
build: build:
@ -217,3 +225,10 @@ services:
- webshoppingapigw - webshoppingapigw
- webmarketingapigw - webmarketingapigw
webhooks.client:
image: eshop/webhooks.client:${TAG:-latest}
build:
context: .
dockerfile: src/Web/WebhookClient/Dockerfile
depends_on:
- webhooks.api

+ 105
- 0
eShopOnContainers-ServicesAndWebApps.sln View File

@ -140,6 +140,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payment", "Payment", "{C61C
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.API", "src\Services\Payment\Payment.API\Payment.API.csproj", "{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.API", "src\Services\Payment\Payment.API\Payment.API.csproj", "{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webhooks", "Webhooks", "{E0AA11C4-2873-461D-8F82-53392530FB7A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webhooks.API", "src\Services\Webhooks\Webhooks.API\Webhooks.API.csproj", "{84E2016E-0435-44C6-8020-3D288AA38B2C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebhookClient", "src\Web\WebhookClient\WebhookClient.csproj", "{766D7E92-6AF0-476C-ADD5-282BF4D8C576}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1642,6 +1648,102 @@ Global
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x64.Build.0 = Release|Any CPU {0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x64.Build.0 = Release|Any CPU
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x86.ActiveCfg = Release|Any CPU {0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x86.ActiveCfg = Release|Any CPU
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x86.Build.0 = Release|Any CPU {0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x86.Build.0 = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|ARM.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhone.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x64.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x64.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x86.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x86.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|ARM.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|ARM.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhone.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x64.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x64.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x86.ActiveCfg = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x86.Build.0 = Debug|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|Any CPU.Build.0 = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|ARM.ActiveCfg = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|ARM.Build.0 = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhone.ActiveCfg = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhone.Build.0 = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x64.ActiveCfg = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x64.Build.0 = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x86.ActiveCfg = Release|Any CPU
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x86.Build.0 = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|ARM.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhone.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x64.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x64.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x86.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x86.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|Any CPU.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|ARM.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|ARM.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhone.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x64.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x64.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x86.ActiveCfg = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x86.Build.0 = Debug|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|Any CPU.ActiveCfg = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|Any CPU.Build.0 = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|ARM.ActiveCfg = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|ARM.Build.0 = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhone.ActiveCfg = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhone.Build.0 = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x64.ActiveCfg = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x64.Build.0 = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x86.ActiveCfg = Release|Any CPU
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1703,6 +1805,9 @@ Global
{120CABB3-0FEA-4B40-B4B5-2D3041798C80} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B} {120CABB3-0FEA-4B40-B4B5-2D3041798C80} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B}
{C61C5CFE-4876-4A46-A96E-5BBF596A984A} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {C61C5CFE-4876-4A46-A96E-5BBF596A984A} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84} = {C61C5CFE-4876-4A46-A96E-5BBF596A984A} {0AB40131-8AD7-436F-9C6B-EDA59CFA3A84} = {C61C5CFE-4876-4A46-A96E-5BBF596A984A}
{E0AA11C4-2873-461D-8F82-53392530FB7A} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{84E2016E-0435-44C6-8020-3D288AA38B2C} = {E0AA11C4-2873-461D-8F82-53392530FB7A}
{766D7E92-6AF0-476C-ADD5-282BF4D8C576} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}


+ 1
- 1
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -274,7 +274,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>( services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c)); sp => (DbConnection c) => new IntegrationEventLogService(c));
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
// services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
if (configuration.GetValue<bool>("AzureServiceBusEnabled")) if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
{ {


+ 25
- 5
src/Services/Identity/Identity.API/Configuration/Config.cs View File

@ -17,7 +17,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
new ApiResource("locations", "Locations Service"), new ApiResource("locations", "Locations Service"),
new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"), new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"),
new ApiResource("webshoppingagg", "Web Shopping Aggregator"), new ApiResource("webshoppingagg", "Web Shopping Aggregator"),
new ApiResource("orders.signalrhub", "Ordering Signalr Hub")
new ApiResource("orders.signalrhub", "Ordering Signalr Hub"),
new ApiResource("webhooks", "Webhooks registration Service"),
}; };
} }
@ -57,7 +58,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
"locations", "locations",
"marketing", "marketing",
"webshoppingagg", "webshoppingagg",
"orders.signalrhub"
"orders.signalrhub",
"webhooks"
}, },
}, },
new Client new Client
@ -84,7 +86,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
"basket", "basket",
"locations", "locations",
"marketing", "marketing",
"mobileshoppingagg"
"mobileshoppingagg",
"webhooks"
}, },
//Allow requesting refresh tokens for long lived API access //Allow requesting refresh tokens for long lived API access
AllowOfflineAccess = true, AllowOfflineAccess = true,
@ -122,7 +125,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
"locations", "locations",
"marketing", "marketing",
"webshoppingagg", "webshoppingagg",
"orders.signalrhub"
"orders.signalrhub",
"webhooks"
}, },
AccessTokenLifetime = 60*60*2, // 2 hours AccessTokenLifetime = 60*60*2, // 2 hours
IdentityTokenLifetime= 60*60*2 // 2 hours IdentityTokenLifetime= 60*60*2 // 2 hours
@ -157,7 +161,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
"basket", "basket",
"locations", "locations",
"marketing", "marketing",
"webshoppingagg"
"webshoppingagg",
"webhooks"
}, },
}, },
new Client new Client
@ -249,6 +254,21 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
{ {
"webshoppingagg" "webshoppingagg"
} }
},
new Client
{
ClientId = "webhooksswaggerui",
ClientName = "WebHooks Service Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/" },
AllowedScopes =
{
"webhooks"
}
} }
}; };
} }


+ 1
- 0
src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs View File

@ -28,6 +28,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
clientUrls.Add("OrderingApi", configuration.GetValue<string>("OrderingApiClient")); clientUrls.Add("OrderingApi", configuration.GetValue<string>("OrderingApiClient"));
clientUrls.Add("MobileShoppingAgg", configuration.GetValue<string>("MobileShoppingAggClient")); clientUrls.Add("MobileShoppingAgg", configuration.GetValue<string>("MobileShoppingAggClient"));
clientUrls.Add("WebShoppingAgg", configuration.GetValue<string>("WebShoppingAggClient")); clientUrls.Add("WebShoppingAgg", configuration.GetValue<string>("WebShoppingAggClient"));
clientUrls.Add("WebhooksApi", configuration.GetValue<string>("WebhooksApiClient"));
if (!context.Clients.Any()) if (!context.Clients.Any())
{ {


+ 35
- 0
src/Services/Webhooks/Webhooks.API/Controller/WebhookSubscriptionRequest.cs View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Webhooks.API.Model;
namespace Webhooks.API.Controller
{
public class WebhookSubscriptionRequest : IValidatableObject
{
public string Url { get; set; }
public string Token { get; set; }
public string Event { get; set; }
public string GrantUrl { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!Uri.IsWellFormedUriString(GrantUrl, UriKind.Absolute))
{
yield return new ValidationResult("GrantUrl is not valid", new[] { nameof(GrantUrl) });
}
if (!Uri.IsWellFormedUriString(Url, UriKind.Absolute))
{
yield return new ValidationResult("Url is not valid", new[] { nameof(Url) });
}
var isOk = Enum.TryParse<WebhookType>(Event, ignoreCase: true, result: out WebhookType whtype);
if (!isOk)
{
yield return new ValidationResult($"{Event} is invalid event name", new[] { nameof(Event) });
}
}
}
}

+ 115
- 0
src/Services/Webhooks/Webhooks.API/Controller/WebhooksController.cs View File

@ -0,0 +1,115 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Webhooks.API.Infrastructure;
using Webhooks.API.Model;
using Webhooks.API.Services;
namespace Webhooks.API.Controller
{
[Route("api/v1/[controller]")]
[ApiController]
public class WebhooksController : ControllerBase
{
private readonly WebhooksContext _dbContext;
private readonly IIdentityService _identityService;
private readonly IGrantUrlTesterService _grantUrlTester;
public WebhooksController(WebhooksContext dbContext, IIdentityService identityService, IGrantUrlTesterService grantUrlTester)
{
_dbContext = dbContext;
_identityService = identityService;
_grantUrlTester = grantUrlTester;
}
[Authorize]
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<WebhookSubscription>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> ListByUser()
{
var userId = _identityService.GetUserIdentity();
var data = await _dbContext.Subscriptions.Where(s => s.UserId == userId).ToListAsync();
return Ok(data);
}
[Authorize]
[HttpGet("{id:int}", Name = "Get")]
[ProducesResponseType(typeof(WebhookSubscription), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetByUserAndId(int id)
{
var userId = _identityService.GetUserIdentity();
var subscription = await _dbContext.Subscriptions.SingleOrDefaultAsync(s => s.Id == id && s.UserId == userId);
if (subscription != null)
{
return Ok(subscription);
}
return NotFound($"Subscriptions {id} not found");
}
[Authorize]
[HttpPost]
[ProducesResponseType((int)HttpStatusCode.Created)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(418)]
public async Task<IActionResult> SubscribeWebhook(WebhookSubscriptionRequest request)
{
if (!ModelState.IsValid)
{
return ValidationProblem(ModelState);
}
var userId = _identityService.GetUserIdentity();
var grantOk = await _grantUrlTester.TestGrantUrl(request.GrantUrl, request.Token ?? string.Empty);
if (grantOk)
{
var subscription = new WebhookSubscription()
{
Date = DateTime.UtcNow,
DestUrl = request.Url,
Token = request.Token,
Type = Enum.Parse<WebhookType>(request.Event, ignoreCase: true),
UserId = _identityService.GetUserIdentity()
};
_dbContext.Add(subscription);
await _dbContext.SaveChangesAsync();
return CreatedAtAction("Get", new { id = subscription.Id });
}
else
{
return StatusCode(418, "Grant url can't be validated");
}
}
[Authorize]
[HttpDelete("{id:int}")]
[ProducesResponseType((int)HttpStatusCode.Accepted)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> UnsubscribeWebhook(int id)
{
var userId = _identityService.GetUserIdentity();
var subscription = await _dbContext.Subscriptions.SingleOrDefaultAsync(s => s.Id == id && s.UserId == userId);
if (subscription != null)
{
_dbContext.Remove(subscription);
await _dbContext.SaveChangesAsync();
return Accepted();
}
return NotFound($"Subscriptions {id} not found");
}
}
}

+ 19
- 0
src/Services/Webhooks/Webhooks.API/Dockerfile View File

@ -0,0 +1,19 @@
FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
FROM microsoft/dotnet:2.2-sdk AS build
WORKDIR /src
COPY ["src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj", "src/Services/Webhooks/Webhooks.API/"]
RUN dotnet restore "src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
COPY . .
WORKDIR "/src/src/Services/Webhooks/Webhooks.API"
RUN dotnet build "Webhooks.API.csproj" -c Release -o /app
FROM build AS publish
RUN dotnet publish "Webhooks.API.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Webhooks.API.dll"]

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

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Webhooks.API.Exceptions
{
public class WebhooksDomainException : Exception
{
}
}

+ 13
- 0
src/Services/Webhooks/Webhooks.API/Infrastructure/ActionResult/InternalServerErrorObjectResult.cs View File

@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Webhooks.API.Infrastructure.ActionResult
{
class InternalServerErrorObjectResult : ObjectResult
{
public InternalServerErrorObjectResult(object error) : base(error)
{
StatusCode = StatusCodes.Status500InternalServerError;
}
}
}

+ 72
- 0
src/Services/Webhooks/Webhooks.API/Infrastructure/HttpGlobalExceptionFilter.cs View File

@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Webhooks.API.Exceptions;
using Webhooks.API.Infrastructure.ActionResult;
namespace Webhooks.API.Infrastructure
{
public class HttpGlobalExceptionFilter : IExceptionFilter
{
private readonly IHostingEnvironment env;
private readonly ILogger<HttpGlobalExceptionFilter> logger;
public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
{
this.env = env;
this.logger = logger;
}
public void OnException(ExceptionContext context)
{
logger.LogError(new EventId(context.Exception.HResult),
context.Exception,
context.Exception.Message);
if (context.Exception.GetType() == typeof(WebhooksDomainException))
{
var problemDetails = new ValidationProblemDetails()
{
Instance = context.HttpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Detail = "Please refer to the errors property for additional details."
};
problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() });
context.Result = new BadRequestObjectResult(problemDetails);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
else
{
var json = new JsonErrorResponse
{
Messages = new[] { "An error ocurred." }
};
if (env.IsDevelopment())
{
json.DeveloperMeesage = context.Exception;
}
context.Result = new InternalServerErrorObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
context.ExceptionHandled = true;
}
private class JsonErrorResponse
{
public string[] Messages { get; set; }
public object DeveloperMeesage { get; set; }
}
}
}

+ 18
- 0
src/Services/Webhooks/Webhooks.API/Infrastructure/WebhooksContext.cs View File

@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Webhooks.API.Model;
namespace Webhooks.API.Infrastructure
{
public class WebhooksContext : DbContext
{
public WebhooksContext(DbContextOptions<WebhooksContext> options) : base(options)
{
}
public DbSet<WebhookSubscription> Subscriptions { get; set; }
}
}

+ 18
- 0
src/Services/Webhooks/Webhooks.API/Model/WebhookSubscription.cs View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Webhooks.API.Model
{
public class WebhookSubscription
{
public int Id { get; set; }
public WebhookType Type { get; set; }
public DateTime Date { get; set; }
public string DestUrl { get; set; }
public string Token { get; set; }
public string UserId { get; set; }
}
}

+ 12
- 0
src/Services/Webhooks/Webhooks.API/Model/WebhookType.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Webhooks.API.Model
{
public enum WebhookType
{
CatalogItemPriceChange = 1
}
}

+ 27
- 0
src/Services/Webhooks/Webhooks.API/Program.cs View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Webhooks.API.Infrastructure;
namespace Webhooks.API
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build()
.MigrateDbContext<WebhooksContext>((_,__) => { })
.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}

+ 32
- 0
src/Services/Webhooks/Webhooks.API/Properties/launchSettings.json View File

@ -0,0 +1,32 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:62486",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Webhooks.API": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5000"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
}
}
}

+ 27
- 0
src/Services/Webhooks/Webhooks.API/Services/GrantUrlTesterService.cs View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Webhooks.API.Services
{
class GrantUrlTesterService : IGrantUrlTesterService
{
private readonly IHttpClientFactory _clientFactory;
public GrantUrlTesterService(IHttpClientFactory factory)
{
_clientFactory = factory;
}
public async Task<bool> TestGrantUrl(string url, string token)
{
var client = _clientFactory.CreateClient("GrantClient");
var msg = new HttpRequestMessage(HttpMethod.Options, url);
msg.Headers.Add("X-eshop-whtoken", token);
var response = await client.SendAsync(msg);
return response.IsSuccessStatusCode;
}
}
}

+ 12
- 0
src/Services/Webhooks/Webhooks.API/Services/IGrantUrlTesterService.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Webhooks.API.Services
{
public interface IGrantUrlTesterService
{
Task<bool> TestGrantUrl(string url, string token);
}
}

+ 7
- 0
src/Services/Webhooks/Webhooks.API/Services/IIdentityService.cs View File

@ -0,0 +1,7 @@
namespace Webhooks.API.Services
{
public interface IIdentityService
{
string GetUserIdentity();
}
}

+ 21
- 0
src/Services/Webhooks/Webhooks.API/Services/IdentityService.cs View File

@ -0,0 +1,21 @@

using Microsoft.AspNetCore.Http;
using System;
namespace Webhooks.API.Services
{
public class IdentityService : IIdentityService
{
private IHttpContextAccessor _context;
public IdentityService(IHttpContextAccessor context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public string GetUserIdentity()
{
return _context.HttpContext.User.FindFirst("sub").Value;
}
}
}

+ 333
- 0
src/Services/Webhooks/Webhooks.API/Startup.cs View File

@ -0,0 +1,333 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using HealthChecks.UI.Client;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.ServiceFabric;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.ServiceBus;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using Swashbuckle.AspNetCore.Swagger;
using Webhooks.API.Infrastructure;
using Webhooks.API.Services;
namespace Webhooks.API
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services
.AddAppInsight(Configuration)
.AddCustomMVC(Configuration)
.AddCustomDbContext(Configuration)
.AddSwagger(Configuration)
.AddCustomHealthCheck(Configuration)
.AddHttpClientServices(Configuration)
.AddIntegrationServices(Configuration)
.AddEventBus(Configuration)
.AddTransient<IIdentityService, IdentityService>()
.AddTransient<IGrantUrlTesterService, GrantUrlTesterService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddAzureWebAppDiagnostics();
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
app.UsePathBase(pathBase);
}
app.UseHealthChecks("/hc", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.UseHealthChecks("/liveness", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("self")
});
app.UseCors("CorsPolicy");
app.UseMvcWithDefaultRoute();
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Webhooks.API V1");
c.OAuthClientId("webhooksswaggerui");
c.OAuthAppName("WebHooks Service Swagger UI");
});
ConfigureEventBus(app);
}
protected virtual void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
// eventBus.Subscribe<OrderStatusChangedToAwaitingValidationIntegrationEvent, OrderStatusChangedToAwaitingValidationIntegrationEventHandler>();
}
}
static class CustomExtensionMethods
{
public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration)
{
services.AddApplicationInsightsTelemetry(configuration);
var orchestratorType = configuration.GetValue<string>("OrchestratorType");
if (orchestratorType?.ToUpper() == "K8S")
{
// Enable K8s telemetry initializer
services.EnableKubernetes();
}
if (orchestratorType?.ToUpper() == "SF")
{
// Enable SF telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
new FabricTelemetryInitializer());
}
return services;
}
public static IServiceCollection AddCustomMVC(this IServiceCollection services, IConfiguration configuration)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddControllersAsServices();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
return services;
}
public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<WebhooksContext>(options =>
{
options.UseSqlServer(configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
// Changing default behavior when client evaluation occurs to throw.
// Default in EF Core would be to log a warning when client evaluation is performed.
options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
});
return services;
}
public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "eShopOnContainers - Webhooks HTTP API",
Version = "v1",
Description = "The Webhooks Microservice HTTP API. This is a simple webhooks CRUD registration entrypoint",
TermsOfService = "Terms Of Service"
});
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ "webhooks", "Webhooks.API" }
}
});
});
return services;
}
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
{
var subscriptionClientName = configuration["SubscriptionClientName"];
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
{
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
});
}
else
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
{
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var retryCount = 5;
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
//services.AddTransient<OrderStatusChangedToAwaitingValidationIntegrationEventHandler>();
return services;
}
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
{
var accountName = configuration.GetValue<string>("AzureStorageAccountName");
var accountKey = configuration.GetValue<string>("AzureStorageAccountKey");
var hcBuilder = services.AddHealthChecks();
hcBuilder
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddSqlServer(
configuration["ConnectionString"],
name: "WebhooksApiDb-check",
tags: new string[] { "webhooksdb" });
return services;
}
public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//register delegating handlers
//services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
//InfinteTimeSpan -> See: https://github.com/aspnet/HttpClientFactory/issues/194
services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan);
//add http client services
services.AddHttpClient("GrantClient")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
//.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
return services;
}
public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
// services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IServiceBusPersisterConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultServiceBusPersisterConnection>>();
var serviceBusConnection = new ServiceBusConnectionStringBuilder(configuration["EventBusConnection"]);
return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger);
});
}
else
{
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = configuration["EventBusConnection"]
};
if (!string.IsNullOrEmpty(configuration["EventBusUserName"]))
{
factory.UserName = configuration["EventBusUserName"];
}
if (!string.IsNullOrEmpty(configuration["EventBusPassword"]))
{
factory.Password = configuration["EventBusPassword"];
}
var retryCount = 5;
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(configuration["EventBusRetryCount"]);
}
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
});
}
return services;
}
}
}

+ 31
- 0
src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.0-beta8" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.1.1-beta1" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj" />
</ItemGroup>
</Project>

+ 9
- 0
src/Services/Webhooks/Webhooks.API/appsettings.Development.json View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

+ 8
- 0
src/Services/Webhooks/Webhooks.API/appsettings.json View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}

+ 20
- 0
src/Web/WebhookClient/Dockerfile View File

@ -0,0 +1,20 @@
FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM microsoft/dotnet:2.2-sdk AS build
WORKDIR /src
COPY ["src/Web/WebhookClient/WebhookClient.csproj", "src/Web/WebhookClient/"]
RUN dotnet restore "src/Web/WebhookClient/WebhookClient.csproj"
COPY . .
WORKDIR "/src/src/Web/WebhookClient"
RUN dotnet build "WebhookClient.csproj" -c Release -o /app
FROM build AS publish
RUN dotnet publish "WebhookClient.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "WebhookClient.dll"]

+ 12
- 0
src/Web/WebhookClient/HeaderNames.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebhookClient
{
static class HeaderNames
{
public const string WebHookCheckHeader = "X-eshop-whtoken";
}
}

+ 26
- 0
src/Web/WebhookClient/Pages/Error.cshtml View File

@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

+ 23
- 0
src/Web/WebhookClient/Pages/Error.cshtml.cs View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebhookClient.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

+ 10
- 0
src/Web/WebhookClient/Pages/Index.cshtml View File

@ -0,0 +1,10 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

+ 17
- 0
src/Web/WebhookClient/Pages/Index.cshtml.cs View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebhookClient.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}

+ 8
- 0
src/Web/WebhookClient/Pages/Privacy.cshtml View File

@ -0,0 +1,8 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

+ 16
- 0
src/Web/WebhookClient/Pages/Privacy.cshtml.cs View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebhookClient.Pages
{
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}
}

+ 25
- 0
src/Web/WebhookClient/Pages/Shared/_CookieConsentPartial.cshtml View File

@ -0,0 +1,25 @@
@using Microsoft.AspNetCore.Http.Features
@{
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
var showBanner = !consentFeature?.CanTrack ?? false;
var cookieString = consentFeature?.CreateConsentCookie();
}
@if (showBanner)
{
<div id="cookieConsent" class="alert alert-info alert-dismissible fade show" role="alert">
Use this space to summarize your privacy and cookie use policy. <a asp-page="/Privacy">Learn More</a>.
<button type="button" class="accept-policy close" data-dismiss="alert" aria-label="Close" data-cookie-string="@cookieString">
<span aria-hidden="true">Accept</span>
</button>
</div>
<script>
(function () {
var button = document.querySelector("#cookieConsent button[data-cookie-string]");
button.addEventListener("click", function (event) {
document.cookie = button.dataset.cookieString;
}, false);
})();
</script>
}

+ 77
- 0
src/Web/WebhookClient/Pages/Shared/_Layout.cshtml View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebhookClient</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
crossorigin="anonymous"
integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
</environment>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">WebhookClient</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<partial name="_CookieConsentPartial" />
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2019 - WebhookClient - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
</script>
</environment>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>

+ 18
- 0
src/Web/WebhookClient/Pages/Shared/_ValidationScriptsPartial.cshtml View File

@ -0,0 +1,18 @@
<environment include="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha256-F6h55Qw6sweK+t7SiOJX+2bpSAa3b/fnlrVCJvmEj1A=">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha256-9GycpJnliUjJDVDqP0UEu/bsm9U+3dnQUH8+3W10vkY=">
</script>
</environment>

+ 3
- 0
src/Web/WebhookClient/Pages/_ViewImports.cshtml View File

@ -0,0 +1,3 @@
@using WebhookClient
@namespace WebhookClient.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

+ 3
- 0
src/Web/WebhookClient/Pages/_ViewStart.cshtml View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

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

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace WebhookClient
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}

+ 32
- 0
src/Web/WebhookClient/Properties/launchSettings.json View File

@ -0,0 +1,32 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51921",
"sslPort": 44398
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebhookClient": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
}
}
}

+ 81
- 0
src/Web/WebhookClient/Startup.cs View File

@ -0,0 +1,81 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Net;
namespace WebhookClient
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.Map("/check", capp =>
{
capp.Run(async (context) =>
{
if ("OPTIONS".Equals(context.Request.Method, StringComparison.InvariantCultureIgnoreCase))
{
var header = context.Request.Headers[HeaderNames.WebHookCheckHeader];
var value = header.FirstOrDefault();
if (value == Configuration["Token"])
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
}
else
{
await context.Response.WriteAsync("Invalid token");
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
});
});
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
}
}

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

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<UserSecretsId>36215d41-f31a-4aa6-9929-bd67d650e7b5</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
</ItemGroup>
</Project>

+ 10
- 0
src/Web/WebhookClient/appsettings.Development.json View File

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
},
"Token": "6168DB8D-DC58-4094-AF24-483278923590"
}
}

+ 8
- 0
src/Web/WebhookClient/appsettings.json View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}

Loading…
Cancel
Save