Skip to content

Commit 82880fe

Browse files
committed
feat: enhance image upload functionality and update dependencies
1 parent e4e72dd commit 82880fe

7 files changed

Lines changed: 11 additions & 187 deletions

File tree

Shared/ImageUploadHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public static async Task<ImageInfo> ProcessAndUploadImageAsync(string imagePath,
1919
await containerClient.CreateIfNotExistsAsync(PublicAccessType.Blob);
2020

2121
// Process image and get metadata
22-
using var image = await Image.LoadAsync(imagePath); var imageInfo = new ImageInfo
22+
using var image = await Image.LoadAsync(imagePath);
23+
var imageInfo = new ImageInfo
2324
{
2425
FileName = fileName,
2526
ImageId = imageId,

Shared/Shared.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Azure.Data.Tables" Version="12.11.0" />
11+
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.1" />
1112
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.9" />
1213
<PackageReference Include="YamlDotNet" Version="16.3.0" />
1314
</ItemGroup>

Web/Extensions/ImageUrlRewriterExtension.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Markdig.Renderers.Html;
44
using Markdig.Syntax;
55
using Markdig.Syntax.Inlines;
6+
using Web.Services;
67

78
namespace Web.Extensions;
89

Web/Services/ContentService.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using YamlDotNet.Serialization.NamingConventions;
66
using Azure.Data.Tables;
77
using Shared;
8+
using Web.Extensions;
89

910
namespace Web.Services;
1011

@@ -33,9 +34,7 @@ public ContentService(
3334
_logger = logger;
3435
_environment = environment;
3536
_cache = cache;
36-
_imageService = imageService;
37-
38-
// Configure Markdig with image processing
37+
_imageService = imageService; // Configure Markdig with image processing
3938
_markdownPipeline = new MarkdownPipelineBuilder()
4039
.UseAutoLinks()
4140
.UseEmphasisExtras()
@@ -55,7 +54,7 @@ public ContentService(
5554
.UseFigures()
5655
.UseEmojiAndSmiley()
5756
.UseGenericAttributes()
58-
.Use<ImageUrlRewriterExtension>() // Custom extension for image processing
57+
.Use(new ImageUrlRewriterExtension(_imageService)) // Custom extension for image processing
5958
.Build();
6059

6160
// Configure YAML deserializer

Web/Services/IImageService.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,12 @@ namespace Web.Services;
77
/// </summary>
88
public interface IImageService
99
{
10-
/// <summary>
11-
/// Upload an image file to blob storage
12-
/// </summary>
13-
Task<ImageInfo> UploadImageAsync(Stream imageStream, string fileName, string? altText = null, string? caption = null);
1410

1511
/// <summary>
1612
/// Get an image by its ID
1713
/// </summary>
1814
Task<ImageInfo?> GetImageAsync(string imageId);
1915

20-
/// <summary>
21-
/// Delete an image from storage
22-
/// </summary>
23-
Task DeleteImageAsync(string imageId);
24-
25-
/// <summary>
26-
/// Update image metadata (alt text, caption)
27-
/// </summary>
28-
Task UpdateImageMetadataAsync(string imageId, string? altText = null, string? caption = null);
29-
3016
/// <summary>
3117
/// Get the public URL for an image
3218
/// </summary>

Web/Services/ImageService.cs

Lines changed: 3 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -28,68 +28,7 @@ public ImageService(
2828
_cdnEndpoint = configuration["Azure:CdnEndpoint"] ?? string.Empty;
2929
}
3030

31-
public async Task<ImageInfo> UploadImageAsync(Stream imageStream, string fileName, string? altText = null, string? caption = null)
32-
{
33-
try
34-
{
35-
// Create container if it doesn't exist
36-
var container = _blobServiceClient.GetBlobContainerClient(_containerName);
37-
await container.CreateIfNotExistsAsync(PublicAccessType.Blob);
38-
39-
// Generate a unique ID and path for the image
40-
var imageId = Guid.NewGuid().ToString("N");
41-
var blobPath = GenerateBlobPath(fileName, imageId);
42-
43-
// Calculate content hash
44-
var contentHash = await CalculateContentHashAsync(imageStream);
45-
imageStream.Position = 0;
46-
47-
// Get image dimensions and type
48-
using var image = await Image.LoadAsync(imageStream);
49-
imageStream.Position = 0;
50-
51-
var imageInfo = new ImageInfo
52-
{
53-
FileName = fileName,
54-
ImageId = imageId,
55-
BlobPath = blobPath,
56-
PublicUrl = GetImageUrl(imageId),
57-
AltText = altText ?? fileName,
58-
Caption = caption,
59-
Width = image.Width,
60-
Height = image.Height,
61-
ContentType = GetContentType(fileName),
62-
UploadedAt = DateTime.UtcNow,
63-
ContentHash = contentHash
64-
};
65-
66-
// Upload original
67-
var blobClient = container.GetBlobClient(blobPath);
68-
await blobClient.UploadAsync(imageStream, new BlobUploadOptions
69-
{
70-
Metadata = new Dictionary<string, string>
71-
{
72-
["ImageId"] = imageId,
73-
["FileName"] = fileName,
74-
["AltText"] = altText ?? fileName,
75-
["Caption"] = caption ?? string.Empty,
76-
["ContentHash"] = contentHash
77-
}
78-
});
79-
80-
// Generate and upload thumbnails
81-
await GenerateThumbnailsAsync(container, imageStream, blobPath);
82-
83-
return imageInfo;
84-
}
85-
catch (Exception ex)
86-
{
87-
_logger.LogError(ex, "Error uploading image {FileName}", fileName);
88-
throw;
89-
}
90-
}
91-
92-
public async Task<ImageInfo?> GetImageAsync(string imageId)
31+
public async Task<Shared.ImageInfo?> GetImageAsync(string imageId)
9332
{
9433
try
9534
{
@@ -103,7 +42,7 @@ public async Task<ImageInfo> UploadImageAsync(Stream imageStream, string fileNam
10342
var blobClient = container.GetBlobClient(blob.Name);
10443
var properties = await blobClient.GetPropertiesAsync();
10544

106-
return new ImageInfo
45+
return new Shared.ImageInfo
10746
{
10847
ImageId = imageId,
10948
FileName = properties.Value.Metadata["FileName"],
@@ -127,52 +66,7 @@ public async Task<ImageInfo> UploadImageAsync(Stream imageStream, string fileNam
12766
}
12867
}
12968

130-
public async Task DeleteImageAsync(string imageId)
131-
{
132-
try
133-
{
134-
var container = _blobServiceClient.GetBlobContainerClient(_containerName);
135-
var blobs = container.GetBlobsAsync(prefix: $"{imageId}/");
136-
137-
await foreach (var blob in blobs)
138-
{
139-
await container.DeleteBlobAsync(blob.Name);
140-
}
141-
}
142-
catch (Exception ex)
143-
{
144-
_logger.LogError(ex, "Error deleting image {ImageId}", imageId);
145-
throw;
146-
}
147-
}
148-
149-
public async Task UpdateImageMetadataAsync(string imageId, string? altText = null, string? caption = null)
150-
{
151-
try
152-
{
153-
var container = _blobServiceClient.GetBlobContainerClient(_containerName);
154-
var blobs = container.GetBlobsAsync(prefix: $"{imageId}/");
155-
156-
await foreach (var blob in blobs)
157-
{
158-
var blobClient = container.GetBlobClient(blob.Name);
159-
var properties = await blobClient.GetPropertiesAsync();
160-
var metadata = new Dictionary<string, string>(properties.Value.Metadata);
161-
162-
if (altText != null) metadata["AltText"] = altText;
163-
if (caption != null) metadata["Caption"] = caption;
164-
165-
await blobClient.SetMetadataAsync(metadata);
166-
}
167-
}
168-
catch (Exception ex)
169-
{
170-
_logger.LogError(ex, "Error updating image metadata {ImageId}", imageId);
171-
throw;
172-
}
173-
}
174-
175-
public string GetImageUrl(string imageId, ImageSize size = ImageSize.Original)
69+
public string GetImageUrl(string imageId, ImageSize size = ImageSize.Original)
17670
{
17771
var sizeFolder = size switch
17872
{
@@ -190,64 +84,6 @@ public string GetImageUrl(string imageId, ImageSize size = ImageSize.Original)
19084
return $"{_blobServiceClient.Uri}{_containerName}/{imageId}/{sizeFolder}";
19185
}
19286

193-
private async Task GenerateThumbnailsAsync(BlobContainerClient container, Stream originalStream, string originalPath)
194-
{
195-
originalStream.Position = 0;
196-
using var image = await Image.LoadAsync(originalStream);
197-
198-
// Define thumbnail sizes
199-
var sizes = new Dictionary<string, (int width, int height)>
200-
{
201-
["thumbnail"] = (150, 150),
202-
["medium"] = (400, 400),
203-
["large"] = (800, 800)
204-
};
205-
206-
foreach (var size in sizes)
207-
{
208-
var resized = image.Clone(ctx => ctx.Resize(new ResizeOptions
209-
{
210-
Mode = ResizeMode.Max,
211-
Size = new Size(size.Value.width, size.Value.height)
212-
}));
213-
214-
var thumbnailPath = originalPath.Replace("/original", $"/{size.Key}");
215-
var thumbnailBlob = container.GetBlobClient(thumbnailPath);
216-
217-
using var ms = new MemoryStream();
218-
await resized.SaveAsync(ms, image.Metadata.DecodedImageFormat);
219-
ms.Position = 0;
220-
221-
await thumbnailBlob.UploadAsync(ms, overwrite: true);
222-
}
223-
}
224-
225-
private string GenerateBlobPath(string fileName, string imageId)
226-
{
227-
var extension = Path.GetExtension(fileName);
228-
return $"{imageId}/original{extension}";
229-
}
230-
231-
private async Task<string> CalculateContentHashAsync(Stream stream)
232-
{
233-
using var sha256 = SHA256.Create();
234-
var hash = await sha256.ComputeHashAsync(stream);
235-
return Convert.ToBase64String(hash);
236-
}
237-
238-
private string GetContentType(string fileName)
239-
{
240-
var extension = Path.GetExtension(fileName).ToLowerInvariant();
241-
return extension switch
242-
{
243-
".jpg" or ".jpeg" => "image/jpeg",
244-
".png" => "image/png",
245-
".gif" => "image/gif",
246-
".webp" => "image/webp",
247-
_ => "application/octet-stream"
248-
};
249-
}
250-
25187
public string? GetImageIdByFilename(string filename)
25288
{
25389
try

Web/Web.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Aspire.Azure.Data.Tables" Version="9.3.0" />
11-
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.0" />
11+
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.1" />
1212
<PackageReference Include="LigerShark.WebOptimizer.Core" Version="3.0.436" />
1313
<PackageReference Include="Markdig" Version="0.37.0" />
1414
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.9" />

0 commit comments

Comments
 (0)