@ -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) }); | |||
} | |||
} | |||
} | |||
} |
@ -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"); | |||
} | |||
} | |||
} |
@ -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"] |
@ -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 | |||
{ | |||
} | |||
} |
@ -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; | |||
} | |||
} | |||
} |
@ -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; } | |||
} | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -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 | |||
} | |||
} |
@ -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>(); | |||
} | |||
} |
@ -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}" | |||
} | |||
} | |||
} |
@ -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; | |||
} | |||
} | |||
} |
@ -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); | |||
} | |||
} |
@ -0,0 +1,7 @@ | |||
namespace Webhooks.API.Services | |||
{ | |||
public interface IIdentityService | |||
{ | |||
string GetUserIdentity(); | |||
} | |||
} |
@ -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; | |||
} | |||
} | |||
} |
@ -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; | |||
} | |||
} | |||
} |
@ -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> |
@ -0,0 +1,9 @@ | |||
{ | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Debug", | |||
"System": "Information", | |||
"Microsoft": "Information" | |||
} | |||
} | |||
} |
@ -0,0 +1,8 @@ | |||
{ | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Warning" | |||
} | |||
}, | |||
"AllowedHosts": "*" | |||
} |
@ -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"] |
@ -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"; | |||
} | |||
} |
@ -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> |
@ -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; | |||
} | |||
} | |||
} |
@ -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> |
@ -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() | |||
{ | |||
} | |||
} | |||
} |
@ -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> |
@ -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() | |||
{ | |||
} | |||
} | |||
} |
@ -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> | |||
} |
@ -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"> | |||
© 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> |
@ -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> |
@ -0,0 +1,3 @@ | |||
@using WebhookClient | |||
@namespace WebhookClient.Pages | |||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
@ -0,0 +1,3 @@ | |||
@{ | |||
Layout = "_Layout"; | |||
} |
@ -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>(); | |||
} | |||
} |
@ -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}" | |||
} | |||
} | |||
} |
@ -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(); | |||
} | |||
} | |||
} |
@ -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> |
@ -0,0 +1,10 @@ | |||
{ | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Debug", | |||
"System": "Information", | |||
"Microsoft": "Information" | |||
}, | |||
"Token": "6168DB8D-DC58-4094-AF24-483278923590" | |||
} | |||
} |
@ -0,0 +1,8 @@ | |||
{ | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Warning" | |||
} | |||
}, | |||
"AllowedHosts": "*" | |||
} |