Skip to content

Commit a9cd4e5

Browse files
committed
feat: integrate Azure Blob Storage for file management and update image retrieval logic
1 parent e73e16f commit a9cd4e5

21 files changed

Lines changed: 68 additions & 21 deletions

File tree

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using System.Net.Mime;
12
using Ardalis.GuardClauses;
23
using MediatR;
34
using MicroCommerce.ApiService.Infrastructure;
5+
using MicroCommerce.ApiService.Services;
46
using Microsoft.EntityFrameworkCore;
57

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

16-
if (!File.Exists(path))
17-
{
18-
return Results.NotFound();
19-
}
20-
21-
return Results.File(path, "image/jpeg");
22-
});
18+
return TypedResults.File(stream, MediaTypeNames.Image.Jpeg);
19+
}).Produces<Stream>(contentType: MediaTypeNames.Image.Jpeg);
2320
}
2421
}

code/src/MicroCommerce.ApiService/MicroCommerce.ApiService.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,4 @@
3737
<PackageReference Include="MediatR" Version="12.4.1" />
3838
</ItemGroup>
3939

40-
<ItemGroup>
41-
<Content Include="Resources\Images\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
42-
</ItemGroup>
43-
4440
</Project>

code/src/MicroCommerce.ApiService/Services/FileService.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public interface IFileService
66
{
77
Task<string> UploadFileAsync(string fileName, Stream stream, CancellationToken cancellationToken = default);
88
Task CreateContainerIfNotExistsAsync(CancellationToken cancellationToken = default);
9+
Task<Stream> DownloadFileAsync(string fileName, CancellationToken cancellationToken = default);
910
}
1011

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

3537
return blobClient.Uri.ToString();
3638
}
3739

40+
public async Task<Stream> DownloadFileAsync(string fileName, CancellationToken cancellationToken = default)
41+
{
42+
var containerClient = _blobServiceClient.GetBlobContainerClient(ContainerName);
43+
var blobClient = containerClient.GetBlobClient(fileName);
44+
45+
var memoryStream = new MemoryStream();
46+
var response = await blobClient.DownloadToAsync(memoryStream, cancellationToken);
47+
48+
if (response.IsError)
49+
{
50+
_logger.LogError("Failed to download file {FileName} from blob storage {Info}", fileName, response.ToString());
51+
throw new Exception($"Failed to download file {fileName}");
52+
}
53+
54+
memoryStream.Position = 0;
55+
return memoryStream;
56+
}
57+
3858
public async Task CreateContainerIfNotExistsAsync(CancellationToken cancellationToken = default)
3959
{
4060
var containerClient = _blobServiceClient.GetBlobContainerClient(ContainerName);

code/src/MicroCommerce.AppHost/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
var migrationService = builder.AddProject<Projects.MicroCommerce_MigrationService>("migrationservice")
4545
.WithReference(db).WaitFor(db)
4646
.WithReference(rabbitmq).WaitFor(rabbitmq)
47+
.WithReference(blobs)
4748
.WithHttpHealthCheck("/health");
4849

4950
var apiService = builder.AddProject<Projects.MicroCommerce_ApiService>("apiservice")

code/src/MicroCommerce.MigrationService/DbInitializer.cs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using MicroCommerce.ApiService.Domain.Entities;
44
using MicroCommerce.ApiService.Features.DomainEvents;
55
using MicroCommerce.ApiService.Infrastructure;
6+
using MicroCommerce.ApiService.Services;
67
using Microsoft.EntityFrameworkCore;
78
using Microsoft.EntityFrameworkCore.Infrastructure;
89
using Microsoft.EntityFrameworkCore.Storage;
@@ -24,8 +25,10 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
2425
using var scope = serviceProvider.CreateScope();
2526
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
2627
var publishEndpoint = scope.ServiceProvider.GetRequiredService<IPublishEndpoint>();
28+
var fileService = scope.ServiceProvider.GetRequiredService<IFileService>();
29+
var environment = scope.ServiceProvider.GetRequiredService<IHostEnvironment>();
2730

28-
await InitializeDatabaseAsync(context, publishEndpoint, cancellationToken);
31+
await InitializeDatabaseAsync(context, publishEndpoint, fileService, environment, cancellationToken);
2932
}
3033
catch (Exception ex)
3134
{
@@ -34,20 +37,20 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken)
3437
}
3538
}
3639

37-
public async Task InitializeDatabaseAsync(ApplicationDbContext context, IPublishEndpoint publishEndpoint, CancellationToken cancellationToken = default)
40+
public async Task InitializeDatabaseAsync(ApplicationDbContext context, IPublishEndpoint publishEndpoint, IFileService fileService, IHostEnvironment environment, CancellationToken cancellationToken = default)
3841
{
3942
var sw = Stopwatch.StartNew();
4043

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

44-
await SeedDataAsync(context, cancellationToken);
47+
await SeedDataAsync(context, fileService, environment, cancellationToken);
4548
await IndexData(context, publishEndpoint, cancellationToken);
4649

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

50-
private static async Task SeedDataAsync(ApplicationDbContext context, CancellationToken cancellationToken)
53+
private static async Task SeedDataAsync(ApplicationDbContext context, IFileService fileService, IHostEnvironment environment, CancellationToken cancellationToken)
5154
{
5255
if (!context.Products.Any())
5356
{
@@ -69,7 +72,29 @@ static List<Product> GetPreconfiguredItems()
6972
];
7073
}
7174

72-
await context.Products.AddRangeAsync(GetPreconfiguredItems(), cancellationToken);
75+
var products = GetPreconfiguredItems();
76+
77+
var tasks = new List<Task>();
78+
foreach (var product in products)
79+
{
80+
var filePath = Path.Combine(environment.ContentRootPath, "Resources/Images", product.ImageUrl);
81+
if (!File.Exists(filePath))
82+
{
83+
continue;
84+
}
85+
86+
var uploadTask = Task.Run(async () =>
87+
{
88+
await using var stream = File.OpenRead(filePath);
89+
await fileService.UploadFileAsync(product.ImageUrl, stream, cancellationToken);
90+
}, cancellationToken);
91+
92+
tasks.Add(uploadTask);
93+
}
94+
95+
await Task.WhenAll(tasks);
96+
97+
await context.Products.AddRangeAsync(products, cancellationToken);
7398
}
7499

75100
await context.SaveChangesAsync(cancellationToken);

code/src/MicroCommerce.MigrationService/MicroCommerce.MigrationService.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
</ItemGroup>
2020

2121
<ItemGroup>
22-
<Folder Include="Migrations\" />
22+
<Content Include="Resources\Images\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
2323
</ItemGroup>
2424

2525
</Project>

code/src/MicroCommerce.MigrationService/Program.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using MassTransit;
22
using MassTransit.Transports;
33
using MicroCommerce.ApiService.Infrastructure;
4+
using MicroCommerce.ApiService.Services;
45
using MicroCommerce.MigrationService;
56
using MicroCommerce.ServiceDefaults;
67
using Microsoft.EntityFrameworkCore;
@@ -46,6 +47,8 @@
4647
});
4748
});
4849

50+
builder.AddAzureBlobClient("blobs");
51+
builder.Services.AddTransient<IFileService, FileService>();
4952
builder.Services.AddSingleton<DbInitializer>();
5053
builder.Services.AddHostedService(sp => sp.GetRequiredService<DbInitializer>());
5154
builder.Services.AddHealthChecks()
@@ -55,11 +58,16 @@
5558

5659
if (app.Environment.IsDevelopment())
5760
{
58-
app.MapPost("/reset", async (ApplicationDbContext dbContext, IPublishEndpoint publishEndpoint, DbInitializer dbInitializer, CancellationToken cancellationToken) =>
61+
app.MapGet("/reset", async (ApplicationDbContext dbContext, IPublishEndpoint publishEndpoint,
62+
DbInitializer dbInitializer,
63+
IFileService fileService,
64+
IHostEnvironment environment, CancellationToken cancellationToken) =>
5965
{
6066
// Delete and recreate the database. This is useful for development scenarios to reset the database to its initial state.
6167
await dbContext.Database.EnsureDeletedAsync(cancellationToken);
62-
await dbInitializer.InitializeDatabaseAsync(dbContext, publishEndpoint, cancellationToken);
68+
await dbInitializer.InitializeDatabaseAsync(dbContext, publishEndpoint, fileService, environment, cancellationToken);
69+
70+
return Results.Ok("ok");
6371
});
6472
}
6573

code/src/MicroCommerce.ApiService/Resources/Images/1.png renamed to code/src/MicroCommerce.MigrationService/Resources/Images/1.png

File renamed without changes.

code/src/MicroCommerce.ApiService/Resources/Images/10.png renamed to code/src/MicroCommerce.MigrationService/Resources/Images/10.png

File renamed without changes.

code/src/MicroCommerce.ApiService/Resources/Images/11.png renamed to code/src/MicroCommerce.MigrationService/Resources/Images/11.png

File renamed without changes.

0 commit comments

Comments
 (0)