Skip to content

Commit 8749d2a

Browse files
authored
Add ContainerRootPath option (#20)
* Add ContainerRootPath option * Make sure the Umbraco media path is used in Umbraco, but the container root path is used for blob client
1 parent d422db5 commit 8749d2a

File tree

3 files changed

+41
-10
lines changed

3 files changed

+41
-10
lines changed

src/Umbraco.StorageProviders.AzureBlob/AzureBlobFileSystemMiddleware.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class AzureBlobFileSystemMiddleware : IMiddleware
2626
private readonly string _name;
2727
private readonly IAzureBlobFileSystemProvider _fileSystemProvider;
2828
private string _rootPath;
29+
private string _containerRootPath;
2930
private readonly TimeSpan? _maxAge = TimeSpan.FromDays(7);
3031

3132
/// <summary>
@@ -63,6 +64,7 @@ protected AzureBlobFileSystemMiddleware(string name, IOptionsMonitor<AzureBlobFi
6364

6465
var fileSystemOptions = options.Get(name);
6566
_rootPath = hostingEnvironment.ToAbsolute(fileSystemOptions.VirtualPath);
67+
_containerRootPath = fileSystemOptions.ContainerRootPath ?? _rootPath;
6668

6769
options.OnChange((o, n) => OptionsOnChange(o, n, hostingEnvironment));
6870
}
@@ -87,7 +89,8 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
8789
return;
8890
}
8991

90-
var blob = _fileSystemProvider.GetFileSystem(_name).GetBlobClient(request.Path);
92+
string containerPath = $"{_containerRootPath.TrimEnd('/')}/{(request.Path.Value.Remove(0, _rootPath.Length)).TrimStart('/')}";
93+
var blob = _fileSystemProvider.GetFileSystem(_name).GetBlobClient(containerPath);
9194

9295
var blobRequestConditions = GetAccessCondition(context.Request);
9396

@@ -98,13 +101,13 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
98101
{
99102
properties = await blob.GetPropertiesAsync(blobRequestConditions, context.RequestAborted).ConfigureAwait(false);
100103
}
101-
catch (RequestFailedException ex) when (ex.Status == (int) HttpStatusCode.NotFound)
104+
catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotFound)
102105
{
103106
// the blob or file does not exist, let other middleware handle it
104107
await next(context).ConfigureAwait(false);
105108
return;
106109
}
107-
catch (RequestFailedException ex) when (ex.Status == (int) HttpStatusCode.PreconditionFailed)
110+
catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.PreconditionFailed)
108111
{
109112
// If-Range or If-Unmodified-Since is not met
110113
// if the resource has been modified, we need to send the whole file back with a 200 OK
@@ -113,12 +116,12 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
113116
properties = await blob.GetPropertiesAsync().ConfigureAwait(false);
114117
response.Headers.Append("Content-Range", $"bytes */{properties.Value.ContentLength}");
115118
}
116-
catch (RequestFailedException ex) when (ex.Status == (int) HttpStatusCode.NotModified)
119+
catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotModified)
117120
{
118121
// If-None-Match or If-Modified-Since is not met
119122
// we need to pass the status code back to the client
120123
// so it knows it can reuse the cached data
121-
response.StatusCode = (int) HttpStatusCode.NotModified;
124+
response.StatusCode = (int)HttpStatusCode.NotModified;
122125
return;
123126
}
124127
// for some reason we get an internal exception type with the message
@@ -140,7 +143,7 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
140143
// If-None-Match or If-Modified-Since is not met
141144
// we need to pass the status code back to the client
142145
// so it knows it can reuse the cached data
143-
response.StatusCode = (int) HttpStatusCode.NotModified;
146+
response.StatusCode = (int)HttpStatusCode.NotModified;
144147
return;
145148
}
146149
}
@@ -174,7 +177,7 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
174177
{
175178
// no ranges could be parsed
176179
response.Clear();
177-
response.StatusCode = (int) HttpStatusCode.RequestedRangeNotSatisfiable;
180+
response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable;
178181
responseHeaders.ContentRange = new ContentRangeHeaderValue(properties.Value.ContentLength);
179182
return;
180183
}
@@ -376,6 +379,7 @@ private void OptionsOnChange(AzureBlobFileSystemOptions options, string name, IH
376379
if (name != _name) return;
377380

378381
_rootPath = hostingEnvironment.ToAbsolute(options.VirtualPath);
382+
_containerRootPath = options.ContainerRootPath ?? _rootPath;
379383
}
380384
}
381385
}

src/Umbraco.StorageProviders.AzureBlob/IO/AzureBlobFileSystem.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class AzureBlobFileSystem : IAzureBlobFileSystem
2121
private readonly IContentTypeProvider _contentTypeProvider;
2222
private readonly IIOHelper _ioHelper;
2323
private readonly string _rootUrl;
24+
private readonly string _containerRootPath;
2425

2526
/// <summary>
2627
/// Creates a new instance of <see cref="AzureBlobFileSystem" />.
@@ -40,6 +41,7 @@ public AzureBlobFileSystem(AzureBlobFileSystemOptions options, IHostingEnvironme
4041
_contentTypeProvider = contentTypeProvider ?? throw new ArgumentNullException(nameof(contentTypeProvider));
4142

4243
_rootUrl = EnsureUrlSeparatorChar(hostingEnvironment.ToAbsolute(options.VirtualPath)).TrimEnd('/');
44+
_containerRootPath = options.ContainerRootPath ?? _rootUrl;
4345

4446
var client = new BlobServiceClient(options.ConnectionString);
4547
_container = client.GetBlobContainerClient(options.ContainerName);
@@ -106,7 +108,7 @@ public void AddFile(string path, Stream stream, bool overrideIfExists)
106108
if (_contentTypeProvider.TryGetContentType(path, out var contentType)) headers.ContentType = contentType;
107109

108110
blob.Upload(stream, headers,
109-
conditions: overrideIfExists ? null : new BlobRequestConditions {IfNoneMatch = new ETag("*")});
111+
conditions: overrideIfExists ? null : new BlobRequestConditions { IfNoneMatch = new ETag("*") });
110112
}
111113

112114
/// <inheritdoc />
@@ -124,7 +126,7 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr
124126
var copyFromUriOperation = destinationBlob.StartCopyFromUri(sourceBlob.Uri,
125127
destinationConditions: overrideIfExists
126128
? null
127-
: new BlobRequestConditions {IfNoneMatch = new ETag("*")});
129+
: new BlobRequestConditions { IfNoneMatch = new ETag("*") });
128130

129131
if (copyFromUriOperation?.HasCompleted == false)
130132
Task.Run(async () => await copyFromUriOperation.WaitForCompletionAsync().ConfigureAwait(false))
@@ -255,7 +257,7 @@ public BlobClient GetBlobClient(string path)
255257
{
256258
if (path == null) throw new ArgumentNullException(nameof(path));
257259

258-
return _container.GetBlobClient(GetFullPath(path));
260+
return _container.GetBlobClient(GetBlobPath(path));
259261
}
260262

261263
/// <inheritdoc />
@@ -282,5 +284,25 @@ private IEnumerable<BlobHierarchyItem> ListBlobs(string path)
282284

283285
return _container.GetBlobsByHierarchy(prefix: path);
284286
}
287+
288+
private string GetBlobPath(string path)
289+
{
290+
if (path == null) throw new ArgumentNullException(nameof(path));
291+
292+
path = EnsureUrlSeparatorChar(path);
293+
294+
if (_ioHelper.PathStartsWith(path, _containerRootPath, '/'))
295+
{
296+
return path;
297+
}
298+
299+
if (_ioHelper.PathStartsWith(path, _rootUrl, '/'))
300+
{
301+
path = path[_rootUrl.Length..];
302+
}
303+
304+
path = $"{_containerRootPath}/{path.TrimStart('/')}";
305+
return path.Trim('/');
306+
}
285307
}
286308
}

src/Umbraco.StorageProviders.AzureBlob/IO/AzureBlobFileSystemOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public class AzureBlobFileSystemOptions
2424
[Required]
2525
public string ContainerName { get; set; } = null!;
2626

27+
/// <summary>
28+
/// The root path of the container.
29+
/// </summary>
30+
public string ContainerRootPath { get; set; } = null!;
31+
2732
/// <summary>
2833
/// The virtual path.
2934
/// </summary>

0 commit comments

Comments
 (0)