Merge pull request #1884 from szukuro/dev
Adding various language enhancements, removing unused/duplicated extension
This commit is contained in:
commit
59805331cd
@ -22,7 +22,7 @@
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
new()
|
||||
{
|
||||
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" }
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
new()
|
||||
{
|
||||
[ oAuthScheme ] = new[] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" }
|
||||
}
|
||||
|
@ -34,17 +34,15 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
|
||||
_persistentConnection.TryConnect();
|
||||
}
|
||||
|
||||
using (var channel = _persistentConnection.CreateModel())
|
||||
{
|
||||
channel.QueueUnbind(queue: _queueName,
|
||||
exchange: BROKER_NAME,
|
||||
routingKey: eventName);
|
||||
using var channel = _persistentConnection.CreateModel();
|
||||
channel.QueueUnbind(queue: _queueName,
|
||||
exchange: BROKER_NAME,
|
||||
routingKey: eventName);
|
||||
|
||||
if (_subsManager.IsEmpty)
|
||||
{
|
||||
_queueName = string.Empty;
|
||||
_consumerChannel.Close();
|
||||
}
|
||||
if (_subsManager.IsEmpty)
|
||||
{
|
||||
_queueName = string.Empty;
|
||||
_consumerChannel.Close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,32 +64,30 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
|
||||
|
||||
_logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, eventName);
|
||||
|
||||
using (var channel = _persistentConnection.CreateModel())
|
||||
using var channel = _persistentConnection.CreateModel();
|
||||
_logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id);
|
||||
|
||||
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
|
||||
|
||||
var body = JsonSerializer.SerializeToUtf8Bytes(@event, @event.GetType(), new JsonSerializerOptions
|
||||
{
|
||||
_logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id);
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
|
||||
|
||||
var body = JsonSerializer.SerializeToUtf8Bytes(@event, @event.GetType(), new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
policy.Execute(() =>
|
||||
{
|
||||
var properties = channel.CreateBasicProperties();
|
||||
properties.DeliveryMode = 2; // persistent
|
||||
policy.Execute(() =>
|
||||
{
|
||||
var properties = channel.CreateBasicProperties();
|
||||
properties.DeliveryMode = 2; // persistent
|
||||
|
||||
_logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);
|
||||
|
||||
channel.BasicPublish(
|
||||
exchange: BROKER_NAME,
|
||||
routingKey: eventName,
|
||||
mandatory: true,
|
||||
basicProperties: properties,
|
||||
body: body);
|
||||
});
|
||||
}
|
||||
channel.BasicPublish(
|
||||
exchange: BROKER_NAME,
|
||||
routingKey: eventName,
|
||||
mandatory: true,
|
||||
basicProperties: properties,
|
||||
body: body);
|
||||
});
|
||||
}
|
||||
|
||||
public void SubscribeDynamic<TH>(string eventName)
|
||||
@ -244,30 +240,27 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
|
||||
|
||||
if (_subsManager.HasSubscriptionsForEvent(eventName))
|
||||
{
|
||||
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
|
||||
using var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME);
|
||||
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
|
||||
foreach (var subscription in subscriptions)
|
||||
if (subscription.IsDynamic)
|
||||
{
|
||||
if (subscription.IsDynamic)
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
||||
if (handler == null) continue;
|
||||
using dynamic eventData = JsonDocument.Parse(message);
|
||||
await Task.Yield();
|
||||
await handler.Handle(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
if (handler == null) continue;
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive= true});
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
|
||||
using dynamic eventData = JsonDocument.Parse(message);
|
||||
await Task.Yield();
|
||||
await handler.Handle(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
if (handler == null) continue;
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
|
||||
await Task.Yield();
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
}
|
||||
await Task.Yield();
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,32 +155,29 @@ public class EventBusServiceBus : IEventBus, IDisposable
|
||||
var processed = false;
|
||||
if (_subsManager.HasSubscriptionsForEvent(eventName))
|
||||
{
|
||||
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
|
||||
var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME);
|
||||
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
|
||||
foreach (var subscription in subscriptions)
|
||||
{
|
||||
var subscriptions = _subsManager.GetHandlersForEvent(eventName);
|
||||
foreach (var subscription in subscriptions)
|
||||
if (subscription.IsDynamic)
|
||||
{
|
||||
if (subscription.IsDynamic)
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
||||
if (handler == null) continue;
|
||||
|
||||
using dynamic eventData = JsonDocument.Parse(message);
|
||||
await handler.Handle(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
if (handler == null) continue;
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonSerializer.Deserialize(message, eventType);
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
}
|
||||
if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
|
||||
|
||||
using dynamic eventData = JsonDocument.Parse(message);
|
||||
await handler.Handle(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
if (handler == null) continue;
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonSerializer.Deserialize(message, eventType);
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
}
|
||||
}
|
||||
processed = true;
|
||||
}
|
||||
processed = true;
|
||||
return processed;
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,9 @@ public class ResilientTransaction
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
using (var transaction = _context.Database.BeginTransaction())
|
||||
{
|
||||
await action();
|
||||
transaction.Commit();
|
||||
}
|
||||
using var transaction = _context.Database.BeginTransaction();
|
||||
await action();
|
||||
transaction.Commit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -21,48 +21,46 @@ namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
var underK8s = webHost.IsInKubernetes();
|
||||
|
||||
using (var scope = webHost.Services.CreateScope())
|
||||
using var scope = webHost.Services.CreateScope();
|
||||
var services = scope.ServiceProvider;
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
var context = services.GetService<TContext>();
|
||||
|
||||
try
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
var context = services.GetService<TContext>();
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
try
|
||||
if (underK8s)
|
||||
{
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
if (underK8s)
|
||||
{
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
var retries = 10;
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(
|
||||
retryCount: retries,
|
||||
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||
onRetry: (exception, timeSpan, retry, ctx) =>
|
||||
{
|
||||
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
var retries = 10;
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(
|
||||
retryCount: retries,
|
||||
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||
onRetry: (exception, timeSpan, retry, ctx) =>
|
||||
{
|
||||
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
new()
|
||||
{
|
||||
[ oAuthScheme ] = new [] { "basketapi" }
|
||||
}
|
||||
|
@ -6,45 +6,39 @@ public class BasketScenarios
|
||||
[Fact]
|
||||
public async Task Post_basket_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var content = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
|
||||
var response = await server.CreateClient()
|
||||
.PostAsync(Post.Basket, content);
|
||||
using var server = CreateServer();
|
||||
var content = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
|
||||
var response = await server.CreateClient()
|
||||
.PostAsync(Post.Basket, content);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_basket_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.GetBasket(1));
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.GetBasket(1));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Send_Checkout_basket_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
|
||||
using var server = CreateServer();
|
||||
var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
|
||||
|
||||
await server.CreateClient()
|
||||
.PostAsync(Post.Basket, contentBasket);
|
||||
await server.CreateClient()
|
||||
.PostAsync(Post.Basket, contentBasket);
|
||||
|
||||
var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json");
|
||||
var contentCheckout = new StringContent(BuildCheckout(), UTF8Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await server.CreateIdempotentClient()
|
||||
.PostAsync(Post.CheckoutOrder, contentCheckout);
|
||||
var response = await server.CreateIdempotentClient()
|
||||
.PostAsync(Post.CheckoutOrder, contentCheckout);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
string BuildBasket()
|
||||
|
@ -9,21 +9,19 @@ namespace Basket.FunctionalTests
|
||||
[Fact]
|
||||
public async Task UpdateBasket_return_and_add_basket()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
using var server = CreateServer();
|
||||
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
|
||||
|
||||
var redisBasketRepository = BuildBasketRepository(redis);
|
||||
|
||||
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
|
||||
{
|
||||
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
|
||||
BuyerId = "buyerId",
|
||||
Items = BuildBasketItems()
|
||||
});
|
||||
|
||||
var redisBasketRepository = BuildBasketRepository(redis);
|
||||
|
||||
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
|
||||
{
|
||||
BuyerId = "buyerId",
|
||||
Items = BuildBasketItems()
|
||||
});
|
||||
|
||||
Assert.NotNull(basket);
|
||||
Assert.Single(basket.Items);
|
||||
}
|
||||
Assert.NotNull(basket);
|
||||
Assert.Single(basket.Items);
|
||||
|
||||
|
||||
}
|
||||
@ -32,25 +30,23 @@ namespace Basket.FunctionalTests
|
||||
public async Task Delete_Basket_return_null()
|
||||
{
|
||||
|
||||
using (var server = CreateServer())
|
||||
using var server = CreateServer();
|
||||
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
|
||||
|
||||
var redisBasketRepository = BuildBasketRepository(redis);
|
||||
|
||||
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
|
||||
{
|
||||
var redis = server.Host.Services.GetRequiredService<ConnectionMultiplexer>();
|
||||
BuyerId = "buyerId",
|
||||
Items = BuildBasketItems()
|
||||
});
|
||||
|
||||
var redisBasketRepository = BuildBasketRepository(redis);
|
||||
var deleteResult = await redisBasketRepository.DeleteBasketAsync("buyerId");
|
||||
|
||||
var basket = await redisBasketRepository.UpdateBasketAsync(new CustomerBasket("customerId")
|
||||
{
|
||||
BuyerId = "buyerId",
|
||||
Items = BuildBasketItems()
|
||||
});
|
||||
var result = await redisBasketRepository.GetBasketAsync(basket.BuyerId);
|
||||
|
||||
var deleteResult = await redisBasketRepository.DeleteBasketAsync("buyerId");
|
||||
|
||||
var result = await redisBasketRepository.GetBasketAsync(basket.BuyerId);
|
||||
|
||||
Assert.True(deleteResult);
|
||||
Assert.Null(result);
|
||||
}
|
||||
Assert.True(deleteResult);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
RedisBasketRepository BuildBasketRepository(ConnectionMultiplexer connMux)
|
||||
|
@ -47,40 +47,18 @@ public class PicController : ControllerBase
|
||||
|
||||
private string GetImageMimeTypeFromImageFileExtension(string extension)
|
||||
{
|
||||
string mimetype;
|
||||
|
||||
switch (extension)
|
||||
string mimetype = extension switch
|
||||
{
|
||||
case ".png":
|
||||
mimetype = "image/png";
|
||||
break;
|
||||
case ".gif":
|
||||
mimetype = "image/gif";
|
||||
break;
|
||||
case ".jpg":
|
||||
case ".jpeg":
|
||||
mimetype = "image/jpeg";
|
||||
break;
|
||||
case ".bmp":
|
||||
mimetype = "image/bmp";
|
||||
break;
|
||||
case ".tiff":
|
||||
mimetype = "image/tiff";
|
||||
break;
|
||||
case ".wmf":
|
||||
mimetype = "image/wmf";
|
||||
break;
|
||||
case ".jp2":
|
||||
mimetype = "image/jp2";
|
||||
break;
|
||||
case ".svg":
|
||||
mimetype = "image/svg+xml";
|
||||
break;
|
||||
default:
|
||||
mimetype = "application/octet-stream";
|
||||
break;
|
||||
}
|
||||
|
||||
".png" => "image/png",
|
||||
".gif" => "image/gif",
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".bmp" => "image/bmp",
|
||||
".tiff" => "image/tiff",
|
||||
".wmf" => "image/wmf",
|
||||
".jp2" => "image/jp2",
|
||||
".svg" => "image/svg+xml",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
return mimetype;
|
||||
}
|
||||
}
|
||||
|
@ -1,122 +0,0 @@
|
||||
namespace Catalog.API.Extensions;
|
||||
|
||||
public static class HostExtensions
|
||||
{
|
||||
public static bool IsInKubernetes(this IHost host)
|
||||
{
|
||||
var cfg = host.Services.GetService<IConfiguration>();
|
||||
var orchestratorType = cfg.GetValue<string>("OrchestratorType");
|
||||
return orchestratorType?.ToUpper() == "K8S";
|
||||
}
|
||||
|
||||
public static IHost MigrateDbContext<TContext>(this IHost host, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
|
||||
{
|
||||
var underK8s = host.IsInKubernetes();
|
||||
|
||||
using (var scope = host.Services.CreateScope())
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
|
||||
var context = services.GetService<TContext>();
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
if (underK8s)
|
||||
{
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(new TimeSpan[]
|
||||
{
|
||||
TimeSpan.FromSeconds(3),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(8),
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
public static IWebHost MigrateDbContext<TContext>(this IWebHost host, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
|
||||
{
|
||||
var underK8s = host.IsInKubernetes();
|
||||
|
||||
using (var scope = host.Services.CreateScope())
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
|
||||
var context = services.GetService<TContext>();
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
if (underK8s)
|
||||
{
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(new TimeSpan[]
|
||||
{
|
||||
TimeSpan.FromSeconds(3),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(8),
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)
|
||||
where TContext : DbContext
|
||||
{
|
||||
context.Database.Migrate();
|
||||
seeder(context, services);
|
||||
}
|
||||
}
|
@ -13,48 +13,46 @@ public static class WebHostExtensions
|
||||
{
|
||||
var underK8s = host.IsInKubernetes();
|
||||
|
||||
using (var scope = host.Services.CreateScope())
|
||||
using var scope = host.Services.CreateScope();
|
||||
var services = scope.ServiceProvider;
|
||||
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
|
||||
var context = services.GetService<TContext>();
|
||||
|
||||
try
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
|
||||
var context = services.GetService<TContext>();
|
||||
|
||||
try
|
||||
if (underK8s)
|
||||
{
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
if (underK8s)
|
||||
{
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(new TimeSpan[]
|
||||
{
|
||||
TimeSpan.FromSeconds(3),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(8),
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(new TimeSpan[]
|
||||
{
|
||||
TimeSpan.FromSeconds(3),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(8),
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,11 +92,11 @@ public class CatalogContextSeed
|
||||
{
|
||||
return new List<CatalogBrand>()
|
||||
{
|
||||
new CatalogBrand() { Brand = "Azure"},
|
||||
new CatalogBrand() { Brand = ".NET" },
|
||||
new CatalogBrand() { Brand = "Visual Studio" },
|
||||
new CatalogBrand() { Brand = "SQL Server" },
|
||||
new CatalogBrand() { Brand = "Other" }
|
||||
new() { Brand = "Azure"},
|
||||
new() { Brand = ".NET" },
|
||||
new() { Brand = "Visual Studio" },
|
||||
new() { Brand = "SQL Server" },
|
||||
new() { Brand = "Other" }
|
||||
};
|
||||
}
|
||||
|
||||
@ -147,10 +147,10 @@ public class CatalogContextSeed
|
||||
{
|
||||
return new List<CatalogType>()
|
||||
{
|
||||
new CatalogType() { Type = "Mug"},
|
||||
new CatalogType() { Type = "T-Shirt" },
|
||||
new CatalogType() { Type = "Sheet" },
|
||||
new CatalogType() { Type = "USB Memory Stick" }
|
||||
new() { Type = "Mug"},
|
||||
new() { Type = "T-Shirt" },
|
||||
new() { Type = "Sheet" },
|
||||
new() { Type = "USB Memory Stick" }
|
||||
};
|
||||
}
|
||||
|
||||
@ -297,18 +297,18 @@ public class CatalogContextSeed
|
||||
{
|
||||
return new List<CatalogItem>()
|
||||
{
|
||||
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" },
|
||||
new CatalogItem { CatalogTypeId = 1, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureFileName = "2.png" },
|
||||
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" },
|
||||
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" },
|
||||
new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" },
|
||||
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" },
|
||||
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" },
|
||||
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" },
|
||||
new CatalogItem { CatalogTypeId = 1, CatalogBrandId = 5, AvailableStock = 100, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureFileName = "9.png" },
|
||||
new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" },
|
||||
new CatalogItem { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureFileName = "11.png" },
|
||||
new CatalogItem { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" },
|
||||
new() { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" },
|
||||
new() { CatalogTypeId = 1, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureFileName = "2.png" },
|
||||
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" },
|
||||
new() { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" },
|
||||
new() { CatalogTypeId = 3, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" },
|
||||
new() { CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" },
|
||||
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" },
|
||||
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" },
|
||||
new() { CatalogTypeId = 1, CatalogBrandId = 5, AvailableStock = 100, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureFileName = "9.png" },
|
||||
new() { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" },
|
||||
new() { CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureFileName = "11.png" },
|
||||
new() { CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" },
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,135 +6,113 @@ public class CatalogScenarios
|
||||
[Fact]
|
||||
public async Task Get_get_all_catalogitems_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Items());
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Items());
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_catalogitem_by_id_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemById(1));
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemById(1));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_catalogitem_by_id_and_response_bad_request_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemById(int.MinValue));
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemById(int.MinValue));
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_catalogitem_by_id_and_response_not_found_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemById(int.MaxValue));
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemById(int.MaxValue));
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_catalogitem_by_name_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemByName(".NET"));
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemByName(".NET"));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_paginated_catalogitem_by_name_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
const bool paginated = true;
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemByName(".NET", paginated));
|
||||
using var server = CreateServer();
|
||||
const bool paginated = true;
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.ItemByName(".NET", paginated));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_paginated_catalog_items_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
const bool paginated = true;
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Items(paginated));
|
||||
using var server = CreateServer();
|
||||
const bool paginated = true;
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Items(paginated));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_filtered_catalog_items_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Filtered(1, 1));
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Filtered(1, 1));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_get_paginated_filtered_catalog_items_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
const bool paginated = true;
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Filtered(1, 1, paginated));
|
||||
using var server = CreateServer();
|
||||
const bool paginated = true;
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Filtered(1, 1, paginated));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_catalog_types_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Types);
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Types);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_catalog_brands_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Brands);
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Brands);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
global using Catalog.API.Extensions;
|
||||
global using Microsoft.AspNetCore.Hosting;
|
||||
global using Microsoft.AspNetCore.Hosting;
|
||||
global using Microsoft.AspNetCore.TestHost;
|
||||
global using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||
global using Microsoft.eShopOnContainers.Services.Catalog.API.Extensions;
|
||||
global using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||
global using Microsoft.eShopOnContainers.Services.Catalog.API;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
|
@ -25,11 +25,9 @@ public class CatalogControllerTest
|
||||
.UseInMemoryDatabase(databaseName: "in-memory")
|
||||
.Options;
|
||||
|
||||
using (var dbContext = new CatalogContext(_dbOptions))
|
||||
{
|
||||
dbContext.AddRange(GetFakeCatalog());
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
using var dbContext = new CatalogContext(_dbOptions);
|
||||
dbContext.AddRange(GetFakeCatalog());
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -66,7 +64,7 @@ public class CatalogControllerTest
|
||||
{
|
||||
return new List<CatalogItem>()
|
||||
{
|
||||
new CatalogItem()
|
||||
new()
|
||||
{
|
||||
Id = 1,
|
||||
Name = "fakeItemA",
|
||||
@ -74,7 +72,7 @@ public class CatalogControllerTest
|
||||
CatalogBrandId = 1,
|
||||
PictureFileName = "fakeItemA.png"
|
||||
},
|
||||
new CatalogItem()
|
||||
new()
|
||||
{
|
||||
Id = 2,
|
||||
Name = "fakeItemB",
|
||||
@ -82,7 +80,7 @@ public class CatalogControllerTest
|
||||
CatalogBrandId = 1,
|
||||
PictureFileName = "fakeItemB.png"
|
||||
},
|
||||
new CatalogItem()
|
||||
new()
|
||||
{
|
||||
Id = 3,
|
||||
Name = "fakeItemC",
|
||||
@ -90,7 +88,7 @@ public class CatalogControllerTest
|
||||
CatalogBrandId = 1,
|
||||
PictureFileName = "fakeItemC.png"
|
||||
},
|
||||
new CatalogItem()
|
||||
new()
|
||||
{
|
||||
Id = 4,
|
||||
Name = "fakeItemD",
|
||||
@ -98,7 +96,7 @@ public class CatalogControllerTest
|
||||
CatalogBrandId = 1,
|
||||
PictureFileName = "fakeItemD.png"
|
||||
},
|
||||
new CatalogItem()
|
||||
new()
|
||||
{
|
||||
Id = 5,
|
||||
Name = "fakeItemE",
|
||||
@ -106,7 +104,7 @@ public class CatalogControllerTest
|
||||
CatalogBrandId = 1,
|
||||
PictureFileName = "fakeItemE.png"
|
||||
},
|
||||
new CatalogItem()
|
||||
new()
|
||||
{
|
||||
Id = 6,
|
||||
Name = "fakeItemF",
|
||||
@ -120,7 +118,7 @@ public class CatalogControllerTest
|
||||
|
||||
public class TestCatalogSettings : IOptionsSnapshot<CatalogSettings>
|
||||
{
|
||||
public CatalogSettings Value => new CatalogSettings
|
||||
public CatalogSettings Value => new()
|
||||
{
|
||||
PicBaseUrl = "http://image-server.com/",
|
||||
AzureStorageEnabled = true
|
||||
|
@ -12,24 +12,20 @@
|
||||
* real environment the certificate should be created and stored in a secure way, which is out
|
||||
* of the scope of this project.
|
||||
**********************************************************************************************/
|
||||
using (var stream = assembly.GetManifestResourceStream("Identity.API.Certificate.idsrv3test.pfx"))
|
||||
{
|
||||
return new X509Certificate2(ReadStream(stream), "idsrv3test");
|
||||
}
|
||||
using var stream = assembly.GetManifestResourceStream("Identity.API.Certificate.idsrv3test.pfx");
|
||||
return new X509Certificate2(ReadStream(stream), "idsrv3test");
|
||||
}
|
||||
|
||||
private static byte[] ReadStream(Stream input)
|
||||
{
|
||||
byte[] buffer = new byte[16 * 1024];
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
using MemoryStream ms = new MemoryStream();
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
ms.Write(buffer, 0, read);
|
||||
}
|
||||
return ms.ToArray();
|
||||
ms.Write(buffer, 0, read);
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -191,23 +191,21 @@
|
||||
string imagePath = Path.Combine(webroot, "images");
|
||||
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
|
||||
|
||||
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
|
||||
using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read);
|
||||
foreach (ZipArchiveEntry entry in zip.Entries)
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in zip.Entries)
|
||||
if (imageFiles.Contains(entry.Name))
|
||||
{
|
||||
if (imageFiles.Contains(entry.Name))
|
||||
string destinationFilename = Path.Combine(imagePath, entry.Name);
|
||||
if (File.Exists(destinationFilename))
|
||||
{
|
||||
string destinationFilename = Path.Combine(imagePath, entry.Name);
|
||||
if (File.Exists(destinationFilename))
|
||||
{
|
||||
File.Delete(destinationFilename);
|
||||
}
|
||||
entry.ExtractToFile(destinationFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
|
||||
File.Delete(destinationFilename);
|
||||
}
|
||||
entry.ExtractToFile(destinationFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,52 +13,50 @@ namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
var underK8s = webHost.IsInKubernetes();
|
||||
|
||||
using (var scope = webHost.Services.CreateScope())
|
||||
using var scope = webHost.Services.CreateScope();
|
||||
var services = scope.ServiceProvider;
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
var context = services.GetService<TContext>();
|
||||
|
||||
try
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
var logger = services.GetRequiredService<ILogger<TContext>>();
|
||||
var context = services.GetService<TContext>();
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
try
|
||||
if (underK8s)
|
||||
{
|
||||
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
|
||||
if (underK8s)
|
||||
{
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
var retries = 10;
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(
|
||||
retryCount: retries,
|
||||
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||
onRetry: (exception, timeSpan, retry, ctx) =>
|
||||
{
|
||||
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
InvokeSeeder(seeder, context, services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
var retries = 10;
|
||||
var retry = Policy.Handle<SqlException>()
|
||||
.WaitAndRetry(
|
||||
retryCount: retries,
|
||||
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||
onRetry: (exception, timeSpan, retry, ctx) =>
|
||||
{
|
||||
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
|
||||
});
|
||||
|
||||
//if the sql server container is not created on run docker compose this
|
||||
//migration can't fail for network related exception. The retry options for DbContext only
|
||||
//apply to transient exceptions
|
||||
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
|
||||
retry.Execute(() => InvokeSeeder(seeder, context, services));
|
||||
}
|
||||
|
||||
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
|
||||
if (underK8s)
|
||||
{
|
||||
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
|
||||
}
|
||||
}
|
||||
|
||||
return webHost;
|
||||
return webHost;
|
||||
}
|
||||
|
||||
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)
|
||||
|
@ -35,7 +35,7 @@ public class TransactionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequ
|
||||
{
|
||||
Guid transactionId;
|
||||
|
||||
using (var transaction = await _dbContext.BeginTransactionAsync())
|
||||
using var transaction = await _dbContext.BeginTransactionAsync();
|
||||
using (LogContext.PushProperty("TransactionContext", transaction.TransactionId))
|
||||
{
|
||||
_logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request);
|
||||
|
@ -13,12 +13,11 @@ public class OrderQueries
|
||||
|
||||
public async Task<Order> GetOrderAsync(int id)
|
||||
{
|
||||
using (var connection = new SqlConnection(_connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
connection.Open();
|
||||
|
||||
var result = await connection.QueryAsync<dynamic>(
|
||||
@"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
|
||||
var result = await connection.QueryAsync<dynamic>(
|
||||
@"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
|
||||
o.Address_City as city, o.Address_Country as country, o.Address_State as state, o.Address_Street as street, o.Address_ZipCode as zipcode,
|
||||
os.Name as status,
|
||||
oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl
|
||||
@ -26,23 +25,21 @@ public class OrderQueries
|
||||
LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid
|
||||
LEFT JOIN ordering.orderstatus os on o.OrderStatusId = os.Id
|
||||
WHERE o.Id=@id"
|
||||
, new { id }
|
||||
);
|
||||
, new { id }
|
||||
);
|
||||
|
||||
if (result.AsList().Count == 0)
|
||||
throw new KeyNotFoundException();
|
||||
if (result.AsList().Count == 0)
|
||||
throw new KeyNotFoundException();
|
||||
|
||||
return MapOrderItems(result);
|
||||
}
|
||||
return MapOrderItems(result);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId)
|
||||
{
|
||||
using (var connection = new SqlConnection(_connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
connection.Open();
|
||||
|
||||
return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
|
||||
return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
|
||||
FROM [ordering].[Orders] o
|
||||
LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid
|
||||
LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
|
||||
@ -50,17 +47,14 @@ public class OrderQueries
|
||||
WHERE ob.IdentityGuid = @userId
|
||||
GROUP BY o.[Id], o.[OrderDate], os.[Name]
|
||||
ORDER BY o.[Id]", new { userId });
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CardType>> GetCardTypesAsync()
|
||||
{
|
||||
using (var connection = new SqlConnection(_connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
connection.Open();
|
||||
|
||||
return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes");
|
||||
}
|
||||
return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes");
|
||||
}
|
||||
|
||||
private Order MapOrderItems(dynamic result)
|
||||
|
@ -20,7 +20,7 @@ public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
new()
|
||||
{
|
||||
[ oAuthScheme ] = new [] { "orderingapi" }
|
||||
}
|
||||
|
@ -63,23 +63,21 @@ namespace Ordering.BackgroundTasks.Services
|
||||
{
|
||||
IEnumerable<int> orderIds = new List<int>();
|
||||
|
||||
using (var conn = new SqlConnection(_settings.ConnectionString))
|
||||
using var conn = new SqlConnection(_settings.ConnectionString);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
conn.Open();
|
||||
orderIds = conn.Query<int>(
|
||||
@"SELECT Id FROM [ordering].[orders]
|
||||
WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime
|
||||
AND [OrderStatusId] = 1",
|
||||
new { _settings.GracePeriodTime });
|
||||
}
|
||||
catch (SqlException exception)
|
||||
{
|
||||
_logger.LogCritical(exception, "FATAL ERROR: Database connections could not be opened: {Message}", exception.Message);
|
||||
}
|
||||
|
||||
conn.Open();
|
||||
orderIds = conn.Query<int>(
|
||||
@"SELECT Id FROM [ordering].[orders]
|
||||
WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime
|
||||
AND [OrderStatusId] = 1",
|
||||
new { _settings.GracePeriodTime });
|
||||
}
|
||||
catch (SqlException exception)
|
||||
{
|
||||
_logger.LogCritical(exception, "FATAL ERROR: Database connections could not be opened: {Message}", exception.Message);
|
||||
}
|
||||
|
||||
|
||||
return orderIds;
|
||||
}
|
||||
|
@ -14,39 +14,33 @@ namespace Ordering.FunctionalTests
|
||||
[Fact]
|
||||
public async Task Get_get_all_stored_orders_and_response_ok_status_code()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Orders);
|
||||
using var server = CreateServer();
|
||||
var response = await server.CreateClient()
|
||||
.GetAsync(Get.Orders);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Cancel_order_no_order_created_bad_request_response()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
|
||||
var response = await server.CreateIdempotentClient()
|
||||
.PutAsync(Put.CancelOrder, content);
|
||||
using var server = CreateServer();
|
||||
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
|
||||
var response = await server.CreateIdempotentClient()
|
||||
.PutAsync(Put.CancelOrder, content);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ship_order_no_order_created_bad_request_response()
|
||||
{
|
||||
using (var server = CreateServer())
|
||||
{
|
||||
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
|
||||
var response = await server.CreateIdempotentClient()
|
||||
.PutAsync(Put.ShipOrder, content);
|
||||
using var server = CreateServer();
|
||||
var content = new StringContent(BuildOrder(), UTF8Encoding.UTF8, "application/json");
|
||||
var response = await server.CreateIdempotentClient()
|
||||
.PutAsync(Put.ShipOrder, content);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
string BuildOrder()
|
||||
|
@ -20,7 +20,7 @@ public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
new()
|
||||
{
|
||||
[ oAuthScheme ] = new [] { "webhooksapi" }
|
||||
}
|
||||
|
@ -11,45 +11,43 @@ public class IntegrationEventsScenarios
|
||||
decimal priceModification = 0.15M;
|
||||
string userId = "JohnId";
|
||||
|
||||
using (var catalogServer = new CatalogScenariosBase().CreateServer())
|
||||
using (var basketServer = new BasketScenariosBase().CreateServer())
|
||||
using var catalogServer = new CatalogScenariosBase().CreateServer();
|
||||
using var basketServer = new BasketScenariosBase().CreateServer();
|
||||
var catalogClient = catalogServer.CreateClient();
|
||||
var basketClient = basketServer.CreateClient();
|
||||
|
||||
// GIVEN a product catalog list
|
||||
var originalCatalogProducts = await GetCatalogAsync(catalogClient);
|
||||
|
||||
// AND a user basket filled with products
|
||||
var basket = ComposeBasket(userId, originalCatalogProducts.Data.Take(3));
|
||||
var res = await basketClient.PostAsync(
|
||||
BasketScenariosBase.Post.CreateBasket,
|
||||
new StringContent(JsonSerializer.Serialize(basket), UTF8Encoding.UTF8, "application/json")
|
||||
);
|
||||
|
||||
// WHEN the price of one product is modified in the catalog
|
||||
var itemToModify = basket.Items[2];
|
||||
var oldPrice = itemToModify.UnitPrice;
|
||||
var newPrice = oldPrice + priceModification;
|
||||
var pRes = await catalogClient.PutAsync(CatalogScenariosBase.Put.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), UTF8Encoding.UTF8, "application/json"));
|
||||
|
||||
var modifiedCatalogProducts = await GetCatalogAsync(catalogClient);
|
||||
|
||||
var itemUpdated = await GetUpdatedBasketItem(newPrice, itemToModify.ProductId, userId, basketClient);
|
||||
|
||||
if (itemUpdated == null)
|
||||
{
|
||||
var catalogClient = catalogServer.CreateClient();
|
||||
var basketClient = basketServer.CreateClient();
|
||||
Assert.False(true, $"The basket service has not been updated.");
|
||||
}
|
||||
else
|
||||
{
|
||||
//THEN the product price changes in the catalog
|
||||
Assert.Equal(newPrice, modifiedCatalogProducts.Data.Single(it => it.Id == itemToModify.ProductId).Price);
|
||||
|
||||
// GIVEN a product catalog list
|
||||
var originalCatalogProducts = await GetCatalogAsync(catalogClient);
|
||||
|
||||
// AND a user basket filled with products
|
||||
var basket = ComposeBasket(userId, originalCatalogProducts.Data.Take(3));
|
||||
var res = await basketClient.PostAsync(
|
||||
BasketScenariosBase.Post.CreateBasket,
|
||||
new StringContent(JsonSerializer.Serialize(basket), UTF8Encoding.UTF8, "application/json")
|
||||
);
|
||||
|
||||
// WHEN the price of one product is modified in the catalog
|
||||
var itemToModify = basket.Items[2];
|
||||
var oldPrice = itemToModify.UnitPrice;
|
||||
var newPrice = oldPrice + priceModification;
|
||||
var pRes = await catalogClient.PutAsync(CatalogScenariosBase.Put.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), UTF8Encoding.UTF8, "application/json"));
|
||||
|
||||
var modifiedCatalogProducts = await GetCatalogAsync(catalogClient);
|
||||
|
||||
var itemUpdated = await GetUpdatedBasketItem(newPrice, itemToModify.ProductId, userId, basketClient);
|
||||
|
||||
if (itemUpdated == null)
|
||||
{
|
||||
Assert.False(true, $"The basket service has not been updated.");
|
||||
}
|
||||
else
|
||||
{
|
||||
//THEN the product price changes in the catalog
|
||||
Assert.Equal(newPrice, modifiedCatalogProducts.Data.Single(it => it.Id == itemToModify.ProductId).Price);
|
||||
|
||||
// AND the products in the basket reflects the changed priced and the original price
|
||||
Assert.Equal(newPrice, itemUpdated.UnitPrice);
|
||||
Assert.Equal(oldPrice, itemUpdated.OldUnitPrice);
|
||||
}
|
||||
// AND the products in the basket reflects the changed priced and the original price
|
||||
Assert.Equal(newPrice, itemUpdated.UnitPrice);
|
||||
Assert.Equal(oldPrice, itemUpdated.OldUnitPrice);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,35 +5,33 @@ public class OrderingScenarios : OrderingScenariosBase
|
||||
[Fact]
|
||||
public async Task Cancel_basket_and_check_order_status_cancelled()
|
||||
{
|
||||
using (var orderServer = new OrderingScenariosBase().CreateServer())
|
||||
using (var basketServer = new BasketScenariosBase().CreateServer())
|
||||
{
|
||||
// Expected data
|
||||
var cityExpected = $"city-{Guid.NewGuid()}";
|
||||
var orderStatusExpected = "cancelled";
|
||||
using var orderServer = new OrderingScenariosBase().CreateServer();
|
||||
using var basketServer = new BasketScenariosBase().CreateServer();
|
||||
// Expected data
|
||||
var cityExpected = $"city-{Guid.NewGuid()}";
|
||||
var orderStatusExpected = "cancelled";
|
||||
|
||||
var basketClient = basketServer.CreateIdempotentClient();
|
||||
var orderClient = orderServer.CreateIdempotentClient();
|
||||
var basketClient = basketServer.CreateIdempotentClient();
|
||||
var orderClient = orderServer.CreateIdempotentClient();
|
||||
|
||||
// GIVEN a basket is created
|
||||
var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
|
||||
await basketClient.PostAsync(BasketScenariosBase.Post.CreateBasket, contentBasket);
|
||||
// GIVEN a basket is created
|
||||
var contentBasket = new StringContent(BuildBasket(), UTF8Encoding.UTF8, "application/json");
|
||||
await basketClient.PostAsync(BasketScenariosBase.Post.CreateBasket, contentBasket);
|
||||
|
||||
// AND basket checkout is sent
|
||||
await basketClient.PostAsync(BasketScenariosBase.Post.CheckoutOrder, new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json"));
|
||||
// AND basket checkout is sent
|
||||
await basketClient.PostAsync(BasketScenariosBase.Post.CheckoutOrder, new StringContent(BuildCheckout(cityExpected), UTF8Encoding.UTF8, "application/json"));
|
||||
|
||||
// WHEN Order is created in Ordering.api
|
||||
var newOrder = await TryGetNewOrderCreated(cityExpected, orderClient);
|
||||
// WHEN Order is created in Ordering.api
|
||||
var newOrder = await TryGetNewOrderCreated(cityExpected, orderClient);
|
||||
|
||||
// AND Order is cancelled in Ordering.api
|
||||
await orderClient.PutAsync(OrderingScenariosBase.Put.CancelOrder, new StringContent(BuildCancelOrder(newOrder.OrderNumber), UTF8Encoding.UTF8, "application/json"));
|
||||
// AND Order is cancelled in Ordering.api
|
||||
await orderClient.PutAsync(OrderingScenariosBase.Put.CancelOrder, new StringContent(BuildCancelOrder(newOrder.OrderNumber), UTF8Encoding.UTF8, "application/json"));
|
||||
|
||||
// AND the requested order is retrieved
|
||||
var order = await TryGetOrder(newOrder.OrderNumber, orderClient);
|
||||
// AND the requested order is retrieved
|
||||
var order = await TryGetOrder(newOrder.OrderNumber, orderClient);
|
||||
|
||||
// THEN check status
|
||||
Assert.Equal(orderStatusExpected, order.Status);
|
||||
}
|
||||
// THEN check status
|
||||
Assert.Equal(orderStatusExpected, order.Status);
|
||||
}
|
||||
|
||||
async Task<Order> TryGetOrder(string orderNumber, HttpClient orderClient)
|
||||
|
@ -56,23 +56,21 @@ public class WebContextSeed
|
||||
string imagePath = Path.Combine(webroot, "images");
|
||||
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
|
||||
|
||||
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
|
||||
using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read);
|
||||
foreach (ZipArchiveEntry entry in zip.Entries)
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in zip.Entries)
|
||||
if (imageFiles.Contains(entry.Name))
|
||||
{
|
||||
if (imageFiles.Contains(entry.Name))
|
||||
string destinationFilename = Path.Combine(imagePath, entry.Name);
|
||||
if (File.Exists(destinationFilename))
|
||||
{
|
||||
string destinationFilename = Path.Combine(imagePath, entry.Name);
|
||||
if (File.Exists(destinationFilename))
|
||||
{
|
||||
File.Delete(destinationFilename);
|
||||
}
|
||||
entry.ExtractToFile(destinationFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
|
||||
File.Delete(destinationFilename);
|
||||
}
|
||||
entry.ExtractToFile(destinationFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,23 +39,21 @@ public class WebContextSeed
|
||||
}
|
||||
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
|
||||
|
||||
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
|
||||
using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read);
|
||||
foreach (ZipArchiveEntry entry in zip.Entries)
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in zip.Entries)
|
||||
if (!imageFiles.Contains(entry.Name))
|
||||
{
|
||||
if (!imageFiles.Contains(entry.Name))
|
||||
string destinationFilename = Path.Combine(imagePath, entry.Name);
|
||||
if (File.Exists(destinationFilename))
|
||||
{
|
||||
string destinationFilename = Path.Combine(imagePath, entry.Name);
|
||||
if (File.Exists(destinationFilename))
|
||||
{
|
||||
File.Delete(destinationFilename);
|
||||
}
|
||||
entry.ExtractToFile(destinationFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
|
||||
File.Delete(destinationFilename);
|
||||
}
|
||||
entry.ExtractToFile(destinationFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user