Skip to content

Commit a11508e

Browse files
authored
Azure Blob Storage feature is added. (#16)
* Azure Blob Storage support. * Max age is linked with configuration
1 parent e288cfb commit a11508e

File tree

13 files changed

+137
-90
lines changed

13 files changed

+137
-90
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.vs/
2+
.vscode/
23
hurimg.core/bin/
34
hurimg.core/obj/
45
ImageServer.Core/bin/

.vscode/launch.json

Lines changed: 0 additions & 47 deletions
This file was deleted.

.vscode/tasks.json

Lines changed: 0 additions & 30 deletions
This file was deleted.

Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
1+
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.201-buster AS build
22
WORKDIR /app/image-server
33

44
# copy everything and build app
55
COPY ./ImageServer.Core .
6-
RUN dotnet publish -c Release -o out
6+
RUN dotnet publish -c Release -r linux-x64 -o out
77

88
# build runtime image
9-
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
9+
FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.1.3-buster-slim as final
1010
COPY --from=build /app/image-server/out ./app
1111
WORKDIR /app
12-
ENTRYPOINT ["dotnet", "ImageServer.Core.dll"]
12+
ENTRYPOINT ["./ImageServer.Core"]

ImageServer.Core/Controllers/ImageController.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
2+
using System.Globalization;
23
using System.Linq;
34
using System.Net;
45
using System.Threading.Tasks;
56
using ImageServer.Core.Services;
67
using Microsoft.AspNetCore.Mvc;
78
using Microsoft.AspNetCore.WebUtilities;
9+
using Microsoft.Extensions.Configuration;
810
using Microsoft.Extensions.Logging;
911

1012
namespace ImageServer.Core.Controllers
@@ -14,12 +16,14 @@ public class ImageController : Controller
1416
private readonly IFileAccessService _fileService;
1517
private readonly IImageService _imageService;
1618
private readonly ILogger<ImageController> _logger;
19+
private readonly IConfiguration _configuration;
1720

18-
public ImageController(IFileAccessService fileService, IImageService imageService, ILogger<ImageController> logger)
21+
public ImageController(IFileAccessService fileService, IImageService imageService, ILogger<ImageController> logger, IConfiguration configuration)
1922
{
2023
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
2124
_imageService = imageService ?? throw new ArgumentNullException(nameof(imageService));
2225
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
26+
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
2327
}
2428

2529
[HttpGet("/i/{slug}/{quality:range(0,100)}/{w:range(0,5000)}x{h:range(0,5000)}/{options:opt}/{id:gridfs}")]
@@ -28,7 +32,11 @@ public ImageController(IFileAccessService fileService, IImageService imageServic
2832
[HttpGet("/i/{slug}/{quality:range(0,100)}/{w:range(0,5000)}x{h:range(0,5000)}/{*id}")]
2933
public async Task<IActionResult> ImageAsync(string id, string slug, int w, int h, int quality, string options = "")
3034
{
31-
Response.Headers.Add("Cache-Control", $"public, max-age={TimeSpan.FromDays(1).TotalSeconds}");
35+
if (!int.TryParse(_configuration.GetValue<string>("CacheControlMaxAgeDay"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int maxAgeDay))
36+
{
37+
maxAgeDay = 1;
38+
}
39+
Response.Headers.Add("Cache-Control", $"public, max-age={TimeSpan.FromDays(maxAgeDay).TotalSeconds}");
3240
Response.Headers.Add("Access-Control-Allow-Origin", "*");
3341

3442
if (string.IsNullOrWhiteSpace(id))
@@ -49,7 +57,11 @@ public async Task<IActionResult> ImageAsync(string id, string slug, int w, int h
4957
[HttpGet("/i/{slug}/{*filepath}")]
5058
public async Task<IActionResult> ImageFromFilePathAsync(string filepath, string slug)
5159
{
52-
Response.Headers.Add("Cache-Control", $"public, max-age={TimeSpan.FromDays(1).TotalSeconds}");
60+
if (!int.TryParse(_configuration.GetValue<string>("CacheControlMaxAgeDay"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int maxAgeDay))
61+
{
62+
maxAgeDay = 1;
63+
}
64+
Response.Headers.Add("Cache-Control", $"public, max-age={TimeSpan.FromDays(maxAgeDay).TotalSeconds}");
5365
Response.Headers.Add("Access-Control-Allow-Origin", "*");
5466
return await ImageResult(filepath, slug);
5567
}

ImageServer.Core/ImageServer.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<None Remove="wwwroot\**" />
1818
</ItemGroup>
1919
<ItemGroup>
20+
<PackageReference Include="Azure.Storage.Blobs" Version="12.4.1" />
2021
<PackageReference Include="Google.Cloud.Storage.V1" Version="2.5.0" />
2122
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.15.4" />
2223
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.2" />
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace ImageServer.Core.Model
6+
{
7+
public class AzureBlobContainer
8+
{
9+
public string ContainerClientName { get; }
10+
public string BlobClientName { get; }
11+
public AzureBlobContainer(HostConfig host, string fullQualifiedName)
12+
{
13+
if (string.IsNullOrEmpty(fullQualifiedName))
14+
{
15+
throw new ArgumentNullException(nameof(fullQualifiedName));
16+
}
17+
18+
var splitted = fullQualifiedName.Split("/");
19+
if (splitted == null || !splitted.Any() || splitted.Length < 2)
20+
{
21+
throw new Exception("Fully qualified name should have at least two parts. For instance; /container/client-path");
22+
}
23+
24+
ContainerClientName = string.IsNullOrWhiteSpace(host.Backend) ? splitted.FirstOrDefault() : host.Backend;
25+
BlobClientName = string.Join("/", string.IsNullOrWhiteSpace(host.Backend) ? splitted.Skip(1) : splitted);
26+
}
27+
}
28+
}

ImageServer.Core/Model/HostConfig.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public enum HostType
2727
GridFs = 1,
2828
Web = 2,
2929
GoogleBucket = 3,
30+
Azure = 4,
3031
}
3132

3233
}

ImageServer.Core/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public static IHostBuilder CreateHostBuilder(string[] args)
1818
var hostBuilder = Host.CreateDefaultBuilder(args)
1919
.ConfigureAppConfiguration((hostingContext, config) =>
2020
{
21-
config.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(),"conf"));
21+
config.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "conf"));
22+
config.AddEnvironmentVariables("IMAGESERVER_");
2223
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
2324
})
2425
.ConfigureWebHostDefaults(webBuilder =>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Azure.Storage.Blobs;
5+
using Google.Cloud.Storage.V1;
6+
using ImageServer.Core.Model;
7+
using Microsoft.Extensions.Configuration;
8+
9+
namespace ImageServer.Core.Services.FileAccess
10+
{
11+
public class AzureBlobStorageAccess : IFileAccessStrategy
12+
{
13+
public string ConnectionString { get; }
14+
public AzureBlobStorageAccess(IConfiguration configuration)
15+
{
16+
this.ConnectionString = configuration.GetValue<string>("AzureConnectionString");
17+
}
18+
19+
public async Task<byte[]> GetFileAsync(HostConfig host, string file)
20+
{
21+
try
22+
{
23+
AzureBlobContainer container = new AzureBlobContainer(host, file);
24+
25+
BlobServiceClient serviceClient = new BlobServiceClient(ConnectionString);
26+
var containerClient = serviceClient.GetBlobContainerClient(container.ContainerClientName);
27+
if (!await containerClient.ExistsAsync())
28+
{
29+
throw new FileNotFoundException(file);
30+
}
31+
32+
var blobClient = containerClient.GetBlobClient(container.BlobClientName);
33+
if (!await blobClient.ExistsAsync())
34+
{
35+
throw new FileNotFoundException(file);
36+
}
37+
38+
using var stream = new MemoryStream();
39+
var download = await blobClient.DownloadAsync();
40+
await download.Value.Content.CopyToAsync(stream);
41+
return stream.GetBuffer();
42+
}
43+
catch (Exception)
44+
{
45+
throw;
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)