File storage abstraction with Azure Blob Storage and local file system support. Covers IFileStorage interface, blob operations, and SAS token generation. Trigger: file storage, blob storage, Azure Blob, file upload, download.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Ingests video/audio from files, URLs, RTSP, desktop; indexes/searches moments with timestamps/clips; transcodes/edits timelines (subtitles/overlays/dubbing); generates assets and live alerts.
IFileStorage abstracts storage provider from application logic{container}/{entity-type}/{entity-id}/{filename}namespace {Company}.{Domain}.Application.Interfaces;
public interface IFileStorage
{
Task<string> UploadAsync(string path, Stream content,
string contentType, CancellationToken ct = default);
Task<Stream> DownloadAsync(string path, CancellationToken ct = default);
Task DeleteAsync(string path, CancellationToken ct = default);
Task<bool> ExistsAsync(string path, CancellationToken ct = default);
string GetPublicUrl(string path, TimeSpan? expiry = null);
}
namespace {Company}.{Domain}.Infrastructure.Storage;
public sealed class AzureBlobFileStorage(
BlobServiceClient blobClient,
IOptions<StorageOptions> options) : IFileStorage
{
public async Task<string> UploadAsync(string path, Stream content,
string contentType, CancellationToken ct)
{
var container = blobClient.GetBlobContainerClient(
options.Value.ContainerName);
await container.CreateIfNotExistsAsync(cancellationToken: ct);
var blob = container.GetBlobClient(path);
await blob.UploadAsync(content, new BlobHttpHeaders
{
ContentType = contentType
}, cancellationToken: ct);
return blob.Uri.ToString();
}
public async Task<Stream> DownloadAsync(string path, CancellationToken ct)
{
var container = blobClient.GetBlobContainerClient(
options.Value.ContainerName);
var blob = container.GetBlobClient(path);
var response = await blob.DownloadStreamingAsync(cancellationToken: ct);
return response.Value.Content;
}
public async Task DeleteAsync(string path, CancellationToken ct)
{
var container = blobClient.GetBlobContainerClient(
options.Value.ContainerName);
var blob = container.GetBlobClient(path);
await blob.DeleteIfExistsAsync(cancellationToken: ct);
}
public async Task<bool> ExistsAsync(string path, CancellationToken ct)
{
var container = blobClient.GetBlobContainerClient(
options.Value.ContainerName);
var blob = container.GetBlobClient(path);
return await blob.ExistsAsync(ct);
}
public string GetPublicUrl(string path, TimeSpan? expiry = null)
{
var container = blobClient.GetBlobContainerClient(
options.Value.ContainerName);
var blob = container.GetBlobClient(path);
var sasBuilder = new BlobSasBuilder
{
BlobContainerName = options.Value.ContainerName,
BlobName = path,
Resource = "b",
ExpiresOn = DateTimeOffset.UtcNow.Add(expiry ?? TimeSpan.FromHours(1))
};
sasBuilder.SetPermissions(BlobSasPermissions.Read);
return blob.GenerateSasUri(sasBuilder).ToString();
}
}
namespace {Company}.{Domain}.Infrastructure.Storage;
public sealed class LocalFileStorage(
IOptions<StorageOptions> options) : IFileStorage
{
private string BasePath => options.Value.LocalBasePath
?? Path.Combine(Path.GetTempPath(), "file-storage");
public async Task<string> UploadAsync(string path, Stream content,
string contentType, CancellationToken ct)
{
var fullPath = Path.Combine(BasePath, path);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
await using var fileStream = File.Create(fullPath);
await content.CopyToAsync(fileStream, ct);
return fullPath;
}
public Task<Stream> DownloadAsync(string path, CancellationToken ct)
{
var fullPath = Path.Combine(BasePath, path);
return Task.FromResult<Stream>(File.OpenRead(fullPath));
}
public Task DeleteAsync(string path, CancellationToken ct)
{
var fullPath = Path.Combine(BasePath, path);
if (File.Exists(fullPath)) File.Delete(fullPath);
return Task.CompletedTask;
}
public Task<bool> ExistsAsync(string path, CancellationToken ct)
=> Task.FromResult(File.Exists(Path.Combine(BasePath, path)));
public string GetPublicUrl(string path, TimeSpan? expiry = null)
=> Path.Combine(BasePath, path);
}
if (builder.Environment.IsDevelopment())
services.AddSingleton<IFileStorage, LocalFileStorage>();
else
{
services.AddSingleton(new BlobServiceClient(connectionString));
services.AddSingleton<IFileStorage, AzureBlobFileStorage>();
}
| Anti-Pattern | Correct Approach |
|---|---|
| Direct BlobClient in handlers | Use IFileStorage abstraction |
| No file size validation | Enforce limits before upload |
| Permanent public URLs | Use SAS tokens with expiry |
| No content type validation | Whitelist allowed content types |
# Find file storage interface
grep -r "IFileStorage\|IBlobStorage" --include="*.cs" src/
# Find Azure Blob usage
grep -r "BlobServiceClient\|BlobClient" --include="*.cs" src/
# Find storage configuration
grep -r "StorageOptions\|BlobStorage" --include="*.json" src/
IFileStorage interface