skills/infra/file-storage/SKILL.md
Use when implementing file upload/download with Azure Blob Storage or local file system.
npx skillsauth add faysilalshareef/dotnet-ai-kit file-storageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 interfacedata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.