Skip to content

Commit

Permalink
feat: integrate Azure Blob Storage for file management and update ima…
Browse files Browse the repository at this point in the history
…ge retrieval logic
  • Loading branch information
baotoq committed Dec 10, 2024
1 parent e73e16f commit a9cd4e5
Show file tree
Hide file tree
Showing 21 changed files with 68 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Net.Mime;
using Ardalis.GuardClauses;
using MediatR;
using MicroCommerce.ApiService.Infrastructure;
using MicroCommerce.ApiService.Services;
using Microsoft.EntityFrameworkCore;

namespace MicroCommerce.ApiService.Features.Products;
Expand All @@ -9,16 +11,11 @@ public class GetProductImage : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder builder)
{
builder.MapGet("/api/products/images/{url}", async (string url, IMediator mediator, IWebHostEnvironment environment) =>
builder.MapGet("/api/products/images/{url}", async (string url, IFileService fileService) =>
{
var path = Path.Combine(environment.ContentRootPath, "Resources/Images", url);
var stream = await fileService.DownloadFileAsync(url);

if (!File.Exists(path))
{
return Results.NotFound();
}

return Results.File(path, "image/jpeg");
});
return TypedResults.File(stream, MediaTypeNames.Image.Jpeg);
}).Produces<Stream>(contentType: MediaTypeNames.Image.Jpeg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,4 @@
<PackageReference Include="MediatR" Version="12.4.1" />
</ItemGroup>

<ItemGroup>
<Content Include="Resources\Images\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions code/src/MicroCommerce.ApiService/Services/FileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public interface IFileService
{
Task<string> UploadFileAsync(string fileName, Stream stream, CancellationToken cancellationToken = default);
Task CreateContainerIfNotExistsAsync(CancellationToken cancellationToken = default);
Task<Stream> DownloadFileAsync(string fileName, CancellationToken cancellationToken = default);
}

public class FileService : IFileService
Expand All @@ -30,11 +31,30 @@ public async Task<string> UploadFileAsync(string fileName, Stream stream, Cancel
if (response.GetRawResponse().IsError)
{
_logger.LogError("Failed to upload file {FileName} to blob storage {Info}", fileName, response.ToString());
throw new Exception($"Failed to upload file {fileName}");
}

return blobClient.Uri.ToString();
}

public async Task<Stream> DownloadFileAsync(string fileName, CancellationToken cancellationToken = default)
{
var containerClient = _blobServiceClient.GetBlobContainerClient(ContainerName);
var blobClient = containerClient.GetBlobClient(fileName);

var memoryStream = new MemoryStream();
var response = await blobClient.DownloadToAsync(memoryStream, cancellationToken);

if (response.IsError)
{
_logger.LogError("Failed to download file {FileName} from blob storage {Info}", fileName, response.ToString());
throw new Exception($"Failed to download file {fileName}");
}

memoryStream.Position = 0;
return memoryStream;
}

public async Task CreateContainerIfNotExistsAsync(CancellationToken cancellationToken = default)
{
var containerClient = _blobServiceClient.GetBlobContainerClient(ContainerName);
Expand Down
1 change: 1 addition & 0 deletions code/src/MicroCommerce.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
var migrationService = builder.AddProject<Projects.MicroCommerce_MigrationService>("migrationservice")
.WithReference(db).WaitFor(db)
.WithReference(rabbitmq).WaitFor(rabbitmq)
.WithReference(blobs)
.WithHttpHealthCheck("/health");

var apiService = builder.AddProject<Projects.MicroCommerce_ApiService>("apiservice")
Expand Down
35 changes: 30 additions & 5 deletions code/src/MicroCommerce.MigrationService/DbInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using MicroCommerce.ApiService.Domain.Entities;
using MicroCommerce.ApiService.Features.DomainEvents;
using MicroCommerce.ApiService.Infrastructure;
using MicroCommerce.ApiService.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
Expand All @@ -24,8 +25,10 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var publishEndpoint = scope.ServiceProvider.GetRequiredService<IPublishEndpoint>();
var fileService = scope.ServiceProvider.GetRequiredService<IFileService>();
var environment = scope.ServiceProvider.GetRequiredService<IHostEnvironment>();

await InitializeDatabaseAsync(context, publishEndpoint, cancellationToken);
await InitializeDatabaseAsync(context, publishEndpoint, fileService, environment, cancellationToken);
}
catch (Exception ex)
{
Expand All @@ -34,20 +37,20 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
}
}

public async Task InitializeDatabaseAsync(ApplicationDbContext context, IPublishEndpoint publishEndpoint, CancellationToken cancellationToken = default)
public async Task InitializeDatabaseAsync(ApplicationDbContext context, IPublishEndpoint publishEndpoint, IFileService fileService, IHostEnvironment environment, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();

var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(context.Database.MigrateAsync, cancellationToken);

await SeedDataAsync(context, cancellationToken);
await SeedDataAsync(context, fileService, environment, cancellationToken);
await IndexData(context, publishEndpoint, cancellationToken);

logger.LogInformation("Database initialization completed after {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
}

private static async Task SeedDataAsync(ApplicationDbContext context, CancellationToken cancellationToken)
private static async Task SeedDataAsync(ApplicationDbContext context, IFileService fileService, IHostEnvironment environment, CancellationToken cancellationToken)
{
if (!context.Products.Any())
{
Expand All @@ -69,7 +72,29 @@ static List<Product> GetPreconfiguredItems()
];
}

await context.Products.AddRangeAsync(GetPreconfiguredItems(), cancellationToken);
var products = GetPreconfiguredItems();

var tasks = new List<Task>();
foreach (var product in products)
{
var filePath = Path.Combine(environment.ContentRootPath, "Resources/Images", product.ImageUrl);
if (!File.Exists(filePath))
{
continue;
}

var uploadTask = Task.Run(async () =>
{
await using var stream = File.OpenRead(filePath);
await fileService.UploadFileAsync(product.ImageUrl, stream, cancellationToken);
}, cancellationToken);

tasks.Add(uploadTask);
}

await Task.WhenAll(tasks);

await context.Products.AddRangeAsync(products, cancellationToken);
}

await context.SaveChangesAsync(cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</ItemGroup>

<ItemGroup>
<Folder Include="Migrations\" />
<Content Include="Resources\Images\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
</ItemGroup>

</Project>
12 changes: 10 additions & 2 deletions code/src/MicroCommerce.MigrationService/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using MassTransit;
using MassTransit.Transports;
using MicroCommerce.ApiService.Infrastructure;
using MicroCommerce.ApiService.Services;
using MicroCommerce.MigrationService;
using MicroCommerce.ServiceDefaults;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -46,6 +47,8 @@
});
});

builder.AddAzureBlobClient("blobs");
builder.Services.AddTransient<IFileService, FileService>();
builder.Services.AddSingleton<DbInitializer>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<DbInitializer>());
builder.Services.AddHealthChecks()
Expand All @@ -55,11 +58,16 @@

if (app.Environment.IsDevelopment())
{
app.MapPost("/reset", async (ApplicationDbContext dbContext, IPublishEndpoint publishEndpoint, DbInitializer dbInitializer, CancellationToken cancellationToken) =>
app.MapGet("/reset", async (ApplicationDbContext dbContext, IPublishEndpoint publishEndpoint,
DbInitializer dbInitializer,
IFileService fileService,
IHostEnvironment environment, CancellationToken cancellationToken) =>
{
// Delete and recreate the database. This is useful for development scenarios to reset the database to its initial state.
await dbContext.Database.EnsureDeletedAsync(cancellationToken);
await dbInitializer.InitializeDatabaseAsync(dbContext, publishEndpoint, cancellationToken);
await dbInitializer.InitializeDatabaseAsync(dbContext, publishEndpoint, fileService, environment, cancellationToken);

return Results.Ok("ok");
});
}

Expand Down

0 comments on commit a9cd4e5

Please sign in to comment.